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

时间:2011-09-27 12:37:16

标签: c# .net

我的问题基于this article

基本上,类可以实现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的调用。这具有易于理解的优点,并且可以用于类似的场景。

  • 找到一个可以为我完成所有这些工作的框架,但这只是一个极端情况,因为我不允许在这个项目中使用外部框架。

您将使用哪些解决方案来实现此目标?

4 个答案:

答案 0 :(得分:17)

你在这里进入了Aspect Oriented Programming的世界。您可以使用PostSharp在5分钟内将这种功能组合在一起 - 但似乎您不允许使用外部框架。因此,您的选择归结为实现您自己非常简单的AOP框架,或者只是咬住子弹并向每个属性设置器添加检查。

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

编辑响应VoodooChilds请求.. 这是一个单元测试类的快速示例,使用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<InvalidOperationException>();
    }
}

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

编辑2 ,为了好玩,这里有一个Gherkin脚本的例子,可用于为这类事情创建更灵活的测试:)

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

答案 1 :(得分:8)

Matt已经提到过,你可以使用面向方面的编程。另一种可能性是使用一种名为interception, as it is provided by the Unity application block的技术。

答案 2 :(得分:5)

正如Matt所说,增加了写一个FxCop规则来检查方法调用

答案 3 :(得分:3)

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