我有一个类Bar
,其中包含一个包含引用类型Foo
的私有字段。我想在公共财产中公开Foo
,但我不希望财产的消费者能够改变Foo
......但是它应该由Bar
内部改变。 ,即我无法创建字段readonly
。
所以我想要的是:
private _Foo;
public Foo
{
get { return readonly _Foo; }
}
......这当然无效。我可以返回Foo
的克隆(假设它是IClonable
),但这对消费者来说并不明显。我应该将属性的名称更改为FooCopy
??它应该是GetCopyOfFoo
方法吗?你认为最佳做法是什么?谢谢!
答案 0 :(得分:11)
这听起来像是在等待C ++的“const”之后。这在C#中不存在。没有办法表明消费者不能修改对象的属性,但其他东西可以(假设变异成员是公共的,当然)。
您可以按照建议将Foo的克隆返回,或者可以将视图返回到Foo上,如ReadOnlyCollection对集合所做的那样。当然,如果你能使Foo
成为一个不可变的类型,那会让生活更简单......
请注意,在字段只读和使对象本身不可变之间存在很大差异。
目前,类型本身可以通过两种方式改变事物。它可以做到:
_Foo = new Foo(...);
或
_Foo.SomeProperty = newValue;
如果它只需要能够做第二个,那么该字段可能是只读的,但你仍然有人提取属性能够改变对象的问题。如果它只需要做第一个,并且实际上Foo
已经是不可变的或者可以变成不可变的,你可以只提供一个只有“getter”的属性,你就可以了。
非常重要,您了解更改字段值(使其引用不同实例)与更改字段引用的对象内容之间的区别。
答案 1 :(得分:3)
不幸的是,目前在C#中没有简单的方法。您可以在界面中提取Foo
的“只读部分”,并让您的属性返回而不是Foo
。
答案 2 :(得分:2)
制作副本,ReadOnlyCollection
或Foo
是不可变的通常是三条最佳路线,正如您已经推测的那样。
每当我做一些比简单地返回基础字段更重要的事情时,我有时会喜欢方法而不是属性,具体取决于涉及的工作量。方法意味着当他们使用您的API时,某些事情正在发生并为消费者提供更多的旗帜。
答案 3 :(得分:1)
“克隆”您收到的Foo对象并将其退回是一种称为防御性复制的常规做法。除非对用户可见的克隆存在一些看不见的副作用,否则绝对没有理由不这样做。它通常是保护类的内部私有数据的唯一方法,尤其是在C#或Java中,const
的C ++概念不可用。 (IE,必须这样做才能在这两种语言中正确创建真正不可变的对象。)
只是为了澄清,可能的副作用可能是你的用户(合理地)期望返回原始对象,或者Foo持有的某些资源无法正确克隆。 (在这种情况下,它在实现IClonable的目的是什么?!)
答案 4 :(得分:1)
如果你不想让任何人搞乱你的状态......不要暴露它!正如其他人所说,如果某些东西需要查看你的内部状态,请提供它的不可变表示。或者,让客户告诉你做某事(Google为“告诉不要问”),而不是自己做。
答案 5 :(得分:0)
为了澄清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; }}
}
答案 6 :(得分:0)
你实际上可以在C#中重现C ++ const的行为 - 你只需要手动完成它。
无论Foo
是什么,调用者可以修改其状态的唯一方法是调用方法或设置属性。
例如,Foo
的类型为FooClass
:
class FooClass
{
public void MutateMyStateYouBadBoy() { ... }
public string Message
{
get { ... }
set { ... }
}
}
所以在你的情况下,你很高兴他们获得Message
属性,但没有设置它,你肯定不满意他们调用那种方法。
因此,定义一个描述允许他们做什么的界面:
interface IFooConst
{
public string Message
{
get { ... }
}
}
我们遗漏了变异方法,只留在了属性的getter中。
然后将该接口添加到FooClass
的基本列表。
现在,在您的班级中使用Foo
属性,您有一个字段:
private FooClass _foo;
属性获取者:
public IFooConst Foo
{
get { return _foo; }
}
这基本上可以手动重现C ++ const
关键字自动执行的操作。在psuedo-C ++术语中,类型const Foo &
的引用类似于自动生成的类型,仅包含标记为Foo
成员的const
成员。将其转换为C#的理论未来版本,您可以像这样声明FooClass
:
class FooClass
{
public void MutateMyStateYouBadBoy() { ... }
public string Message
{
get const { ... }
set { ... }
}
}
我所做的全部工作是将IFooConst
中的信息合并回FooClass
,方法是使用新的const
关键字标记一个安全成员。所以在某种程度上,除了正式的方法之外,添加一个const关键字不会对语言增加太多。
然后,如果您对const
对象有FooClass
引用:
const FooClass f = GetMeAFooClass();
您只能在f
上调用const成员。
请注意,如果FooClass
定义是公开的,则调用方可以将IFooConst
转换为FooClass
。但他们也可以在C ++中做到这一点 - 它被称为“抛弃const
”并涉及一个名为const_cast<T>(const T &)
的特殊运算符。
还有一个问题是接口在产品版本之间不易发展。如果第三方可以实现您定义的接口(如果他们可以看到它们可以自由执行),那么在将来的版本中无法向其添加新方法,而无需其他人重新编译其代码。但是,如果您正在为其他人构建可扩展的库,那么这只是一个问题。也许内置的const
功能可以解决这个问题。
答案 7 :(得分:0)
我在想类似的安全事情。可能有一种方法。很清楚但不短。一般的想法很简单。但是我总是找到一些方法,所以从来没有测试过。但你可以检查它 - 也许它会对你有用。
这是伪代码,但我希望它背后的想法很清楚
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对您来说非常重要,那么这是另一种选择。
然而,正如我所说,这只是理论。