维护同一类的开放版本和只读版本



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

这些类不是泛型存储,并且是强类型的。

目前,我只是从读/写版本继承,并在写尝试时抛出异常,我想知道是否有一种更精简的方式。

首先,请注意"只读"one_answers"不可变"之间有区别。假设您给r("接收器")一个对对象o("对象")的引用:

  • 如果您只想确保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模式:

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)中,这种情况可能会有所改变。

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

    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个模板显示编辑控件和一个模板显示只读控件)。

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

微软自己的Freezable层次结构在WPF中使用的模式正是您所描述的。例如参见Freezable.WritePreamble:

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

最新更新