24

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

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

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

4

7 回答 7

28

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

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

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

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

  3. 创建一个包装类来聚合您的类型并且只公开读取操作。

第一个选项通常是最简单的,因为它可以减少对现有代码的重构,但为类型的作者提供了最少的机会来通知消费者什么时候实例是不可变的,什么时候不可变。此选项还提供了编译器对检测不当使用的最少支持 - 并将错误检测委托给运行时。

第二个选项很方便,因为无需太多重构工作就可以实现接口。不幸的是,调用者仍然可以转换为基础类型并尝试对其进行写入。通常,此选项与只读标志结合使用,以确保不违反不变性。

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

就个人而言,在编写我希望需要强制不变性的新代码时,我倾向于选择第三种选择。 我喜欢这样一个事实,即不可能“抛弃”不可变包装器,并且它通常允许您避免将混乱的 if-read-only-throw-exception 检查写入每个 setter。

于 2010-04-27T20:29:27.747 回答
3

如果您正在创建库,则可以使用私有/内部类定义公共接口。任何需要将只读类的实例返回给外部使用者的方法都应该改为返回只读接口的实例。现在,向下转换为具体类型是不可能的,因为该类型没有公开公开。

实用程序库

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都会得到看起来像两个不同对象的东西。当然,他们可以使用反射来查看底层类型,这会告诉他们这实际上是具有相同类型的同一个对象。但是该类型的定义在外部库中是私有的。在这一点上,在技术上仍然可以得到定义,但它需要重型巫术才能完成。

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

感谢 XKCD的评论。

于 2018-02-23T18:00:09.390 回答
2

为什么不这样:

private int someValue;
public int SomeValue
{
    get
    {
         return someValue;
    }
    set
    {
         if(ReadOnly)
              throw new InvalidOperationException("Object is readonly");
         someValue= value;
    }
于 2010-04-27T20:20:06.527 回答
1

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

我预计不会有任何其他方法可以提供上述三个好处以及更多好处。在我看来,绝对在这里使用包装类。

于 2010-04-27T20:19:38.557 回答
0

像这样的帮助:

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

设置属性时需要捕获异常。

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

于 2010-04-27T20:21:48.893 回答
0

您无法readonly通过在运行时将属性更改为只读来获得编译时检查(如使用关键字给出)。所以没有其他办法,只能手动检查并抛出异常。

但是可能最好重新设计对类的访问。例如,创建一个“写入器类”,它检查当前是否可以写入底层“数据类”。

于 2010-04-27T20:26:32.100 回答
0

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

于 2010-04-27T21:29:20.177 回答