我正在阅读有关do的MSDN上的一些文档,而不是关于某些内容是作为属性还是作为方法实现的。我特别遇到一条规则,我有一个问题。
如果“操作返回一个数组”,请使用方法(而不是属性)。
页面位于:Choosing Between Properties and Methods
使用操作返回的方法 数组因为要保留内部数组,所以必须返回 数组的深层副本,而不是对数组使用的数组的引用 属性。这一事实,加上开发人员使用的事实 属性就像它们是字段一样,可能导致效率非常低 代码。
我知道属性的get方法会返回对数组的引用,这样即使没有set也可以更改数组。在他们提供的示例中,每次访问属性时,他们都会对数组进行深层复制,我想这是为了避免发生这种情况的可能性,而这反过来又效率很低。
如果属性刚刚返回引用并且没有完成所有复制,那么效率不高,对吧?并且使用方法而不是属性不会自动保护列表不被修改。这几乎是相同的情况,你仍然需要一个深层复制。
使用属性并且只返回对数组的引用总是不好的做法?如果您希望调用者能够修改数组,或者您不关心它们是否修改它,该怎么办?它仍然是坏的,为什么,如果是这样,什么是允许调用者修改的正确方法?
答案 0 :(得分:10)
您是否允许调用者通过属性修改内部数组?是的,当然,但你会遇到一系列可能的问题。你如何处理这些问题以及你可以忍受的事情取决于你。
MSDN建议在严格意义上是正确的。这就是说我在课前看到了List<T>
和T[]
属性。如果你的类是一个非常简单的POCO,这不是一个大问题,因为那些类只是原始数据而且没有真正的业务逻辑可以影响。
那就是说,如果我返回一个列表,并且我不希望任何人弄乱内部列表,我每次都要返回一个深层拷贝,或者ReadOnlyCollection
或迭代器。例如,我有很多地方缓存Web服务请求调用,当我返回缓存项时,我不希望调用者修改该数据,或者他们将修改我正在缓存的内容。因此,我制作了深层副本(仍然比Web服务调用的开销更快)。
您只需知道您的使用是否需要安全。该课程仅供内部消费吗?或者它是否被更广泛的受众消费,你不知道他们将用它做什么?这些类型的问题可能会引起您的反响。
对于“取决于”答案感到抱歉,但这确实取决于您的目标是什么,以及该课程的内部是否对变化敏感。
UPDATE 你也可以返回一个迭代器,我会避免将IEnumerable作为超类up-cast返回,因为它可以被强制转换,但如果你返回一个迭代器(比如使用Skip( 0))你是安全的(除了当然还能修改包含的对象)。
例如:
public IEnumerable<T> SomeList
{
get { return _internalList.Skip(0); }
}
优于:
public IEnumerable<T> SomeList
{
get { return _internalList; }
}
因为后者仍然可以转回List<T>
或其他任何东西,而第一个是迭代器而且无法修改。
答案 1 :(得分:2)
如果您的类的内部状态不依赖于您公开的数组或列表,则可以按原样返回它。所以不应该通过更改列表或数组来破坏你的类。
如果您信任调用代码(即它是由您编写的),那么如果它可以提高您的性能(可衡量且与您的应用相关)也可以。
如果要提供只读访问权限,则可以返回包含要公开的列表或数组的ReadonlyCollection
。
答案 2 :(得分:2)
这几乎总是一件坏事,但它并没有阻止人们这样做(包括我)。这是一个在使用场景中使用合适案例的问题。
但是,保护数组封装最简单的方法是只公开IEnumerable或ICollection以及改变内部数组的方法。请参阅以下示例。我在这里使用了List,因为语义更简单。
class YourClass {
List<string> list = new List<string>();
public IEnumerable<string> List {
get { return list; }
}
public void Add(string str) {
list.Add(str);
}
}
由于返回类型是IEnumerable / ICollection,因此除了通过类上的方法/属性之外,不可能改变内部状态。
最终的封装是在属性访问中复制整个集合,但在大多数情况下这都是低效的。
从语义上讲,应该为没有太多成本的,幂等和有效的东西分配财产。变异数据或执行成本高的事情应该是一种方法。
答案 3 :(得分:2)
你的假设是正确的,如果你只是从属性get中返回现有数组,而不是执行副本,那么它就不再是效率问题,因为这样做与返回任何其他引用类型一样有效。
至于它是一种“坏习惯”,很大程度上源于他们描述的原因:即使你只在你的属性上提供了一个get访问器,用户仍然可以修改数组的内容,从而改变你的类的状态没有你的类有一种验证变化的方法 - 最糟糕的是,除非你已经对C#数组语义有足够深入的了解,否则设计的这种结果并不是很明显。
如果您可以接受用户将能够改变数组的内容,如果您可以接受您的课程无法使用验证或防止这些更改,然后返回数组引用可能是可以接受的,只需确保记录该决定及其背后的基本原理。
但是,除非你确实需要原始数组的性能,否则你的属性可能更好地返回一个集合类实例。您的类的使用者仍然可以使用相同的语法通过索引引用项目,但您保留使返回的集合不可变或提供更新验证的能力。
答案 4 :(得分:1)
我会说,通常使用属性返回数组或集合并不是一种坏习惯。特别是如果每次都返回相同的缓存实例。但是,如果每次都创建一个新实例,那么一个名为CreateItems()的方法会使这个事实更加清晰。
答案 5 :(得分:1)
我不同意你想要to preserve the internal array, you would have to return a deep copy of the array, not a reference to the array used by the property.
事实上,如果某个属性确实返回了一个深层副本,它将非常"astonishing"。
有时您确实希望保留内部结构,只返回它的只读视图(以IEnumerable<T>
的形式),但它与属性正交。
他们认为,对于外部世界,属性应该像字段一样,并且字段将允许修改。如果他们说这是不好的做法将数组/列表作为字段,这是与属性无关的完全不同的事情。