如何使引用类型属性“readonly”

我有一个类Bar ,其中包含一个包含引用类型Foo的私有字段。 我想在公共财产中暴露Foo ,但我不希望财产的消费者能够改变Foo ……但是它应该由Bar内部改变,即我不能使该字段readonly

所以我想要的是:

  private _Foo; public Foo { get { return readonly _Foo; } } 

……当然无效。 我可以返回一个Foo的克隆(假设它是IClonable ),但这对消费者来说并不明显。 我应该将属性的名称更改为FooCopy吗? 它应该是GetCopyOfFoo方法吗? 你认为最佳做法是什么? 谢谢!

这听起来像是在等待C ++的“const”之后。 这在C#中不存在。 没有办法表明消费者不能修改对象的属性,但其他东西可以(假设变异成员当然是公开的)。

您可以按照建议返回Foo的克隆,或者可能返回Foo的视图 ,就像ReadOnlyCollection对集合一样。 当然,如果你能让Foo成为一个不可变的类型,那会让生活变得更简单……

请注意,使字段只读并使对象本身不可变之间存在很大差异。

目前,类型本身可以以两种方式改变事物。 它可以做到:

 _Foo = new Foo(...); 

要么

 _Foo.SomeProperty = newValue; 

如果它只需要能够做第二个,那么该字段可能是只读的,但你仍然有人提取属性能够改变对象的问题。 如果它只需要做第一个,并且实际上Foo已经是不可变的或者可以变成不可变的,你可以只提供一个只有“getter”的属性,你就可以了。

理解更改字段值(使其引用不同实例)和更改字段引用的对象内容之间的区别非常重要

不幸的是,目前在C#中没有简单的方法。 您可以在界面中提取Foo的“只读部分”,并让您的属性返回而不是Foo

正如您已经推测的那样,制作副本, ReadOnlyCollection或让Foo成为不可变通常是最好的三条路线。

每当我做一些比简单地返回底层字段更重要的事情时,我有时会喜欢方法而不是属性,具体取决于涉及的工作量。 方法意味着当他们使用您的API时,某些事情正在发生并为消费者提供更多的旗帜。

“克隆”您收到的Foo对象并将其退回是一种称为防御性复制的常规做法。 除非对用户可见的克隆有一些看不见的副作用,否则绝对没有理由不这样做。 它通常是保护类的内部私有数据的唯一方法,特别是在C#或Java中,其中const的C ++概念不可用。 (IE,必须这样做才能在这两种语言中正确创建真正不可变的对象。)

只是为了澄清,可能的副作用可能是你的用户(合理地)期望返回原始对象,或者Foo持有的某些资源无法正确克隆。 (在这种情况下,它在实施IClonable是什么?!)

如果你不希望任何人搞乱你的状态……不要暴露它! 正如其他人所说,如果某些东西需要查看你的内部状态,请提供它的不可变表示。 或者,让客户告诉做某事(谷歌“告诉不要问”),而不是自己做。

为了澄清Jon Skeet的评论,你可以创建一个视图,这是一个可变的Foo的不可变包装类。 这是一个例子:

 class Foo{ public string A{get; set;} public string B{get; set;} //... } class ReadOnlyFoo{ Foo foo; public string A { get { return foo.A; }} public string B { get { return foo.B; }} } 

实际上,你可以在C#中重现C ++ const的行为 – 你只需要手动完成它。

无论Foo是什么,调用者可以修改其状态的唯一方法是调用其上的方法或设置属性。

例如, Foo的类型为FooClass

 class FooClass { public void MutateMyStateYouBadBoy() { ... } public string Message { get { ... } set { ... } } } 

所以在你的情况下,你很高兴他们获得Message属性,但没有设置它,你肯定不满意他们调用该方法。

因此,定义一个描述他们允许做什么的界面:

 interface IFooConst { public string Message { get { ... } } } 

我们省略了变异方法,只留在了物业的吸气剂中。

然后将该接口添加到FooClass的基本列表中。

现在在你的Foo属性类中,你有一个字段:

 private FooClass _foo; 

和财产获取者:

 public IFooConst Foo { get { return _foo; } } 

这基本上可以手动重现C ++ const关键字自动执行的操作。 在psuedo-C ++术语中,类型为const Foo &的引用类似于自动生成的类型,仅包含标记为const成员的Foo成员。 将其转换为C#的理论未来版本,你可以像这样声明FooClass

 class FooClass { public void MutateMyStateYouBadBoy() { ... } public string Message { get const { ... } set { ... } } } 

实际上我所做的就是将IFooConst的信息合并回FooClass ,方法是用一个新的const关键字标记一个安全成员。 因此,在某种程度上,添加const关键字除了对此模式的正式方法外,不会对语言添加太多内容。

然后,如果你有一个对FooClass对象的const引用:

 const FooClass f = GetMeAFooClass(); 

你只能在f上调用const成员。

请注意,如果FooClass定义是公共的,则调用者可以将IFooConst转换为FooClass 。 但他们也可以在C ++中做到这一点 – 它被称为“抛弃const ”并涉及一个名为const_cast(const T &)的特殊运算符。

还有一个问题是接口在产品版本之间不易发展。 如果第三方可以实现您定义的接口(如果他们可以看到它们,则可以自由地执行),那么在将来的版本中无法向其添加新方法,而无需其他人重新编译其代码。 但是,如果您正在为其他人构建可扩展的库,那么这只是一个问题。 也许内置的constfunction可以解决这个问题。

我在考虑类似的安全问题。 可能有一种方法。 相当清楚但不短。 一般的想法很简单。 但是我总是找到一些方法,所以从来没有测试过。 但你可以检查它 – 也许它会对你有用。

这是伪代码,但我希望它背后的想法很清楚

 public delegate void OnlyRuller(string s1, string s2); public delegate void RullerCoronation(OnlyRuller d); class Foo { private Foo(); public Foo(RullerCoronation followMyOrders) { followMyOrders(SetMe); } private SetMe(string whatToSet, string whitWhatValue) { //lot of unclear but private code } } 

因此,在创建此属性的类中,您可以访问SetMe方法,但它仍然是私有的,因此除了创建者Foo看起来不可变。

对于任何大于几个属性的东西,这可能很快变得非常混乱 – 这就是为什么我总是喜欢其他封装方式。 但是,如果不允许客户更改Foo对您来说非常重要,那么这是另一种选择。

但是,正如我所说,这只是理论。