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

mht*_*ttk 19 .net c#

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

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

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

public interface IFreezableModel
{
    void Freeze();
    bool IsFrozen{get;}
}
Run Code Online (Sandbox Code Playgroud)

目标是确保一旦调用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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这样我就可以上课了

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

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

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

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

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

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

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

Mat*_*vey 17

您将在此处进入面向方面编程的世界.你可以使用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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

此方法使用参数化单元测试来传递正在测试的类型,但您可以将所有这些代码同样封装到通用基类(其中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
Run Code Online (Sandbox Code Playgroud)

  • 我将介绍使用PostSharp的概念.它很可能是我见过的最好的AOP框架.但是,如果您的公司对使用第三方框架感到不安,那么IL Weaving的想法可能会让他们的头脑爆炸. (2认同)

PVi*_*itt 8

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


Lee*_*ale 5

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