最近我遇到了很多需要类的开放和只读版本的场景。一个常见的场景是用户可以为其设置属性的设置类,但当设置经过验证并经过长时间运行的操作时,他们应该只能访问只读版本。
这些类不是泛型存储,并且是强类型的。
目前,我只是从读/写版本继承,并在写尝试时抛出异常,我想知道是否有一种更精简的方式。
首先,请注意"只读"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");
}
}