维护相同 class 的开放和只读版本

Maintaining open and readonly versions of the same class

最近我遇到了很多需要 class 的开放和只读版本的情况。一个常见的场景是用户可以为其设置属性的设置 class,但是当设置已经过验证并经过长时间 运行 操作时,他们应该只能访问只读版本。

这些 class 不是通用存储,而是强类型的。

目前我只是从 read/write 版本继承并在写入尝试时抛出异常,并且想知道人们是否有更简化的方式来做到这一点。

我的第一个想法是拥有一个具有默认值属性的结构 FooDefaults,并将其传递给 Foo 的构造函数(您的 属性 class ),它使用默认值(验证后)来初始化从只读属性返回的成员。

为什么不使用接口?将对象作为只读接口传递,但将其作为可读写的具体类型传递。

    public interface IUserSettings
    {
        int Value1 { get; }
        string Value2 { get; }
    }

    public class UserSettings : IUserSettings
    {
        public int Value1 { get; set; }
        public string Value2 { get; set; }
    }

然后您还可以更新 UI 以不同方式显示 UserSettings 和 IUserSettings(即有 1 个模板显示编辑控件和 1 个模板显示只读控件。)

Microsoft 自己的 Freezable 层次结构在 WPF 中使用的模式完全符合您的描述。例如参见 [​​=12=]:

        if (IsFrozenInternal)
        {
            throw new InvalidOperationException(
                SR.Get(SRID.Freezable_CantBeFrozen,GetType().FullName));
        }

Freezable 使用“IsFrozen”让客户端确定对象是否不可变。

为什么不直接关闭设备?

private string prop1 = string.Empty;
public string Prop1 
{
    get { return prop1; }
    set 
    {
        if (ValueIsValid(prop1))
        {
            NotifyPropertyChanged("Prop1");
            return;   // or you can throw an exeption 
        }
        if (prop1 == value)  return;
        prop1 = value;
        NotifyPropertyChanged("Prop1");
    }
}

首先,请注意 "read-only" 和 "immutable" 之间存在差异。假设您给 r ("receiver") 一个对象的引用 o ("object"):

  • 如果您只想确保 r 不会更改 o 的值,那么像 这样基于接口的解决方案就足够了并且可能会尽可能简单。

    var o = new List<int> { 1, 2, 3 };
    r.LookAtList((IEnumerable<int>)o);
    

    r.LookAtList 会将 o 视为只读序列,因为 IEnumerable<> 抽象是只读的。

  • 如果您还想确保 r 始终观察到 o 的相同值,那么基于接口的解决方案是不够的。考虑一下:

    var o = new List<int> { 1, 2, 3 };
    r.LookAtList((IEnumerable<int>)o);
    o.Add(4);
    r.LookAtList((IEnumerable<int>)o);
    

    虽然 r.LookAtList 无法更改原始对象 o(除非它使用反射或将 IEnumerable<> 转换回 List<>),但它第二次将观察到不同的序列,即使它传递的是完全相同的 IEnumerable<> 引用。

如果您真的想要某种类型的读写版本和不可变版本,那么您最终得到两种不同的类型。我建议你考虑 Builder pattern:

sealed class FooBuilder
{
    public TA A { get; set; }
    public TB B { get; set; }
    …
    public Foo Build() { return new Foo(A, B, …); }
}

sealed class Foo
{
    public Foo(TA a, TB b, …)
    {
        … // validate arguments
        this.a = a;
        this.b = b;
        …
    }

    private readonly TA a;
    private readonly TB b;
    …

    public TA A { get { return a; } }
    public TB B { get { return b; } }
    …
}

但这相当冗长,您可能希望避免所有这些重复。不幸的是,在当前版本的 C# 编程语言中,实现真正不可变的类型需要大量冗长的代码。

在即将发布的 C# 版本 (6) 中,这可能会有所改变。