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

时间:2022-02-12 14:27:25

My question is based on this article.

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

Basically a class can implement a Freezable method to make sure that no properties can be changed once the object enters the Frozen state.

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

I have an interface that follow this design

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

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

the objective is to make sure that once the Freeze method is called, the IsFrozen property is set to True and the properties of the object cannot be changed anymore.

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

To simplify, I will be using an abstract base class:

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

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");
    }
}

this way I can have a class like

这样我就可以上课了

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

This is all nice and simple, but which strategy can I adopt to make sure all properties follow the pattern above? (apart from writing setters and getters)

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

These are the alternatives I came up with:

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

  • Find a mechanism to inject a method into each property setter using emit perhaps. But I have no idea how to do it, what potential issues I may encounter (and therefore how long it will take). If someone knows this and can point me in that direction, it would be great.

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

  • Use some templates during the build process so that the Call to OnCheckFrozen is inserted just before compile time. This has the advantage of being really simple to understand and can work for similar scenarios.

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

  • Find a framework that can do all of this for me, but it just as an extreme case as I am not allowed to use external framework on this project.

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

What solutions would you use to accomplish this?

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

4 个解决方案

#1


17  

You're entering the world of Aspect Oriented Programming here. You could knock together this kind of functionality in 5 minutes using PostSharp - but it seems you're not allowed to use external frameworks. So then your choice comes down to implementing your own very simple AOP framework, or just biting the bullet and adding checks to every property setter.

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

Personally I'd just write checks in ever property setter. This may not be as painful as you expect. You could write a visual studio code snippet to speed up the process.. You could also write a smart unit test class which would, using reflection, scan through all the properties of a frozen object and attempt to set a value - with the test failing if no exception was thrown..

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

EDIT In response to VoodooChilds request.. Here's a quick example of a unit test class, using NUnit and the excellent FluentAssertions library.

编辑响应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>();
    }
}

This method is using a parameterized unit test to pass in the types being tested, but you could equally encapsulate all of this code into a generic base class (where T : IFreezable) and create extended classes for each type being tested, but some test runners don't like having tests in base classes.. *ahem*Resharper!ahem

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

EDIT 2 and, just for fun, here's an example of a Gherkin script which could be used to create much more flexible tests for this kind of thing :)

编辑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

#2


8  

As Matt already mentioned, you can use aspect oriented programming. Another possibility is to use a technique called interception, as it is provided by the Unity application block.

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

#3


5  

as Matt said with the addition of writing an FxCop rule to check for the method call

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

#4


3  

how about an extra bit of indirection using the proxy pattern so you can inject the frozen check there? if the object the proxy refers to is frozen throw, if not proceed. however, this means you need a proxy for every IFreezableModel (though maybe generics could overcome this) and it will apply for every class member you're accessing (or the proxy needs more complexity).

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

#1


17  

You're entering the world of Aspect Oriented Programming here. You could knock together this kind of functionality in 5 minutes using PostSharp - but it seems you're not allowed to use external frameworks. So then your choice comes down to implementing your own very simple AOP framework, or just biting the bullet and adding checks to every property setter.

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

Personally I'd just write checks in ever property setter. This may not be as painful as you expect. You could write a visual studio code snippet to speed up the process.. You could also write a smart unit test class which would, using reflection, scan through all the properties of a frozen object and attempt to set a value - with the test failing if no exception was thrown..

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

EDIT In response to VoodooChilds request.. Here's a quick example of a unit test class, using NUnit and the excellent FluentAssertions library.

编辑响应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>();
    }
}

This method is using a parameterized unit test to pass in the types being tested, but you could equally encapsulate all of this code into a generic base class (where T : IFreezable) and create extended classes for each type being tested, but some test runners don't like having tests in base classes.. *ahem*Resharper!ahem

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

EDIT 2 and, just for fun, here's an example of a Gherkin script which could be used to create much more flexible tests for this kind of thing :)

编辑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

#2


8  

As Matt already mentioned, you can use aspect oriented programming. Another possibility is to use a technique called interception, as it is provided by the Unity application block.

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

#3


5  

as Matt said with the addition of writing an FxCop rule to check for the method call

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

#4


3  

how about an extra bit of indirection using the proxy pattern so you can inject the frozen check there? if the object the proxy refers to is frozen throw, if not proceed. however, this means you need a proxy for every IFreezableModel (though maybe generics could overcome this) and it will apply for every class member you're accessing (or the proxy needs more complexity).

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