如何制作类的只读版本?

我有一个具有各种公共属性的类,我允许用户通过属性网格进行编辑。 对于持久性,此类还通过DataContractSerializer与XML文件进行序列化/反序列化。

有时我希望用户能够将他们所做的更改保存(序列化)到类的实例中。 但在其他时候,我不想让用户保存他们的更改,而应该将属性网格中的所有属性都看作只读。 我不想让用户进行以后永远无法保存的更改。 类似于MS Word将允许用户打开当前由其他人打开但仅作为只读的文档。

我的类有一个布尔属性,用于确定该类是否应该是只读的,但是是否可以使用此属性以某种方式在运行时动态地向类属性添加只读属性? 如果不是什么是替代解决方案? 我应该将我的类包装在只读包装类中吗?

不变性是C#仍有改进空间的领域。 虽然创建具有readonly属性的简单不可变类型是可能的,但是一旦您需要对类型可变的更复杂的控制,您就开始遇到障碍。

您有三种选择,具体取决于您需要“强制执行”只读行为的强度:

  1. 在您的类型中使用只读标志(就像您正在做的那样)并让调用者负责不尝试更改类型的属性 – 如果进行了写入尝试,则抛出exception。

  2. 创建一个只读接口,让您的类型实现它。 这样,您可以通过该接口将类型传递给只应执行读取的代码。

  3. 创建一个聚合您的类型的包装类,只显示读取操作。

第一种选择通常是最简单的,因为它可以减少对现有代码的重构,但是为类型的作者提供了最少的机会,以便在实例不可变时通知消费者而不是实例。 此选项还提供编译器在检测不当使用方面的最少支持 – 并将错误检测降级到运行时。

第二个选项很方便,因为实现一个接口是可能的,没有太多的重构努力。 不幸的是,调用者仍然可以转换为基础类型并尝试对其进行编写。 通常,此选项与只读标志组合以确保不违反不变性。

就强制执行而言,第三种选择是最强的,但它可能导致代码重复,更多的是重构工作。 通常,组合选项2和3非常有用,可以建立只读包装器和可变类型多态的关系。

就个人而言,在编写需要强制执行不变性的新代码时,我倾向于使用第三种选择。 我喜欢这样一个事实:不可能“抛弃”不可变包装器,它通常允许你避免在每个setter中编写凌乱的if-read-only-throw-exception检查。

为什么不是这样的:

 private int someValue; public int SomeValue { get { return someValue; } set { if(ReadOnly) throw new InvalidOperationException("Object is readonly"); someValue= value; } 

如果要创建库,则可以使用私有/内部类定义公共接口。 任何需要将只读类的实例返回给外部使用者的方法应该改为返回只读接口的实例。 现在,由于类型没有公开暴露,因此不可能向下浇注到混凝土类型。

实用程序库

 public interface IReadOnlyClass { string SomeProperty { get; } int Foo(); } public interface IMutableClass { string SomeProperty { set; } void Foo( int arg ); } 

你的图书馆

 internal MyReadOnlyClass : IReadOnlyClass, IMutableClass { public string SomeProperty { get; set; } public int Foo() { return 4; // chosen by fair dice roll // guaranteed to be random } public void Foo( int arg ) { this.SomeProperty = arg.ToString(); } } public SomeClass { private MyThing = new MyReadOnlyClass(); public IReadOnlyClass GetThing { get { return MyThing as IReadOnlyClass; } } public IMutableClass GetATotallyDifferentThing { get { return MyThing as IMutableClass } } } 

现在,任何使用SomeClass都会看到两个不同的对象。 当然,他们可以使用reflection来查看底层类型,这会告诉他们这实际上是同一类型的相同对象。 但是该类型的定义在外部库中是私有的。 此时,技术上仍然可以达到定义,但它需要Heavy Wizardry才能完成。

根据您的项目,您可以将上述库合并为一个。 什么都没有阻止; 只是不要在你想限制权限的任何DLL中包含上面的代码。

感谢XKCD的评论。

我会使用一个包装类来保持所有内容都是只读的。 这是为了可扩展性,可靠性和一般可读性。

我没有预见到这样做的任何其他方法将提供上述三个好处以及更多 。 在我看来,绝对使用包装类。

通过在运行时将属性更改为只读,您无法获得编译时检查(如使用关键字readonly给出的)。 因此,没有其他方法可以手动检查并抛出exception。

但可取的是,重新设计对课程的访问权限会更好。 例如,创建一个“编写器类”,它检查当前是否可以写入基础“数据类”。

您可以使用PostSharp创建OnFieldAccessAspect,当_readOnly设置为true时,它不会将新值传递给任何字段。 随着方面代码重复的消失,将不会忘记任何字段。

这样的事情会有所帮助:

 class Class1 { private bool _isReadOnly; private int _property1; public int Property1 { get { return _property1; } set { if (_isReadOnly) throw new Exception("At the moment this is ready only property."); _property1 = value; } } } 

设置属性时需要捕获exception。

我希望这是你正在寻找的东西。