以编程方式在类的每个属性上插入方法调用

我的问题是基于这篇文章 。

基本上,类可以实现Freezable方法,以确保在对象进入Frozen状态后不能更改任何属性。

我有一个遵循这种设计的界面

public interface IFreezableModel { void Freeze(); bool IsFrozen{get;} } 

目标是确保一旦调用Freeze方法,IsFrozen属性设置为True,并且不再更改对象的属性。

为简化起见,我将使用一个抽象基类:

 public abstract class BaseFreezableModel : IFreezableModel { public void Freeze() { _isFrozen = true; } public bool IsFrozen { get {return _isFrozen;} } protected ThrowIfFrozen() { if (IsFrozen) throw new Exception("Attempted to change a property of a frozen model"); } } 

这样我就可以上课了

 public class MyModel : BaseFreezableModel { private string _myProperty; public string MyProperty { get{return _myProperty;} set { ThrowIfFrozen(); _myProperty = value; } } } 

这一切都很简单,但我可以采用哪种策略来确保所有属性都遵循上述模式? (除了写限制者和吸气剂)

这些是我提出的替代方案:

  • 找到一种机制,可以使用emit将方法注入每个属性setter。 但我不知道该怎么做,我可能遇到的潜在问题(因此需要多长时间)。 如果有人知道这一点并且可以指出我的方向,那就太好了。

  • 在构建过程中使用一些模板,以便在编译时间之前插入对OnCheckFrozen的调用。 这具有易于理解的优点,并且可以用于类似的场景。

  • 找到一个可以为我做所有这一切的框架,但它只是一个极端的情况,因为我不允许在这个项目上使用外部框架。

你会用什么解决方案来实现这个目标?

您将在此处进入面向方面编程的世界。 你可以使用PostSharp在5分钟内完成这种function – 但似乎你不允许使用外部框架。 因此,您的选择归结为实现您自己的非常简单的AOP框架,或者只是咬住子弹并向每个属性设置器添加检查。

就个人而言,我只是在财产制定者中写支票。 这可能不像你期望的那样痛苦。 您可以编写一个可视化工作室代码片段来加速该过程。您还可以编写一个智能unit testing类,它将使用reflection扫描冻结对象的所有属性并尝试设置值 – 测试失败如果没有抛出exception..

编辑响应VoodooChilds请求..这是一个unit testing类的快速示例,使用NUnit和优秀的FluentAssertions库。

 [TestFixture] public class PropertiesThrowWhenFrozenTest { [TestCase(typeof(Foo))] [TestCase(typeof(Bar))] [TestCase(typeof(Baz))] public void AllPropertiesThrowWhenFrozen(Type type) { var target = Activator.CreateInstance(type) as IFreezable; target.Freeze(); foreach(var property in type.GetProperties()) { this.AssertPropertyThrowsWhenChanged(target, property); } } private void AssertPropertyThrowsWhenChanged(object target, PropertyInfo property) { // In the case of reference types, setting the property to null should be sufficient // to test the behaviour... object value = null; // In the case of value types, just create a default instance... if (property.PropertyType.IsValueType) value = Activator.CreateInstance(property.PropertyType); Action setter = () => property.GetSetMethod().Invoke(target, new object[] { value }); // ShouldThrow is a handy extension method of the FluentAssetions library... setter.ShouldThrow(); } } 

此方法使用参数化unit testing来传递正在测试的类型,但您可以将所有这些代码同样封装到通用基类(其中T:IFreezable)中,并为每个要测试的类型创建扩展类,但是一些测试运行器不喜欢在基类中进行测试.. * ahem * Resharper! 啊哈

编辑2 ,只是为了好玩,这里是一个小黄瓜脚本的例子,可以用来为这种事情创建更灵活的测试:)

 Feature: AllPropertiesThrowWhenFrozen In order to make sure I haven't made any oversights in my code As a software developer I want to be able to assert that all properties of a class throw an exception when the object is frozen Scenario: Setting the Bar property on the Foo type Given I have an instance of the class MyNamespace.MyProject.Foo And it is frozen When I set the property Bar with a value of 10 Then a System.InvalidOperationException should be thrown 

正如Matt已经提到的,您可以使用面向方面的编程。 另一种可能性是使用一种称为拦截的技术,因为它是由Unity应用程序块提供的 。

正如马特所说的那样,增加了写一个FxCop规则来检查方法调用

如何使用代理模式进行额外的间接访问,以便在那里注入冻结检查? 如果代理引用的对象是冻结抛出,如果不继续。 但是,这意味着您需要为每个IFreezableModel设置一个代理(尽管generics可以克服这个问题)并且它将适用于您正在访问的每个类成员(或代理需要更多复杂性)。