我的问题基于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的调用。这具有易于理解的优点,并且可以用于类似的场景。
找到一个可以为我完成所有这些工作的框架,但这只是一个极端情况,因为我不允许在这个项目中使用外部框架。
您将使用哪些解决方案来实现此目标?
答案 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的代理(尽管泛型可以克服这个),它将适用于你正在访问的每个类成员(或代理需要更多的复杂性)。