我刚刚阅读了Eric Lippert的"Arrays considered somewhat harmful"文章。他告诉读者们,他们“可能不应该返回数组作为公共方法或属性的值”,并给出了以下理由(略有改动):
现在,调用者可以取出该数组并将其内容替换为任何他们想要的内容。合理的做法是返回
本质上,
一个数组是变量的集合。调用者不想要变量,但如果这是获取值的唯一方式,它们就会接受。但是,调用方和被调用方都不希望这些变量发生任何变化。
为了理解他所说的变量与值的区别,我创建了一个演示类,其中包含
如果我理解正确,
但我不理解他的区分 -
很明显,我相信Eric Lippert知道他在说什么,那么我哪里误解了他呢?以何种方式返回一个
现在,调用者可以取出该数组并将其内容替换为任何他们想要的内容。合理的做法是返回
IList<>
。你可以构建一个漂亮的只读集合对象,然后随意传递它的引用。本质上,
一个数组是变量的集合。调用者不想要变量,但如果这是获取值的唯一方式,它们就会接受。但是,调用方和被调用方都不希望这些变量发生任何变化。
为了理解他所说的变量与值的区别,我创建了一个演示类,其中包含
Array
和IList<>
字段,并且有返回对两者引用的方法。这是在C# Pad上的链接。
class A {
private readonly int[] arr = new []
{
10, 20, 30, 40, 50
};
private readonly List<int> list = new List<int>(new []
{
10, 20, 30, 40, 50
});
public int[] GetArr()
{
return arr;
}
public IList<int> GetList()
{
return list;
}
}
如果我理解正确,
GetArr()
方法是Lippert不良实践,GetList()
是他的明智做法。以下是 GetArr()
的错误调用者可变性行为示例:var a = new A();
var arr1 = a.GetArr();
var arr2 = a.GetArr();
Console.WriteLine("arr1[2]: " + arr1[2].ToString()); // > arr1[2]: 30
Console.WriteLine("arr2[2]: " + arr2[2].ToString()); // > arr2[2]: 30
// ONE CALLER MUTATES
arr1[2] = 99;
// BOTH CALLERS AFFECTED
Console.WriteLine("arr1[2]: " + arr1[2].ToString()); // > arr1[2]: 99
Console.WriteLine("arr2[2]: " + arr2[2].ToString()); // > arr2[2]: 99
但我不理解他的区分 -
IList<>
引用也存在相同的调用者突变问题:
var a = new A();
var list1 = a.GetList();
var list2 = a.GetList();
Console.WriteLine("list1[2]: " + list1[2].ToString()); // > list1[2]: 30
Console.WriteLine("list2[2]: " + list2[2].ToString()); // > list2[2]: 30
// ONE CALLER MUTATES
list1[2] = 99;
// BOTH CALLERS AFFECTED
Console.WriteLine("list1[2]: " + list1[2].ToString()); // > list1[2]: 99
Console.WriteLine("list2[2]: " + list2[2].ToString()); // > list2[2]: 99
很明显,我相信Eric Lippert知道他在说什么,那么我哪里误解了他呢?以何种方式返回一个
iList
)的原因是其适应性(即,您可以以许多不同的方式满足给定的要求,而不像string[]
那样只接受非常特定的格式);这就是这里要表达的观点。与数组本身无关(而是与其不太适应的本质有关)。在我看来(并且非常清楚的是,Eric Lippert对.NET语言的了解肯定比我更多),这种泛泛而谈的陈述很少是好的。数组在某些情况下是理想的方法,而iLists(甚至是Lists)在其他情况下则更合适。 - varocarbasIList<T>
,你必须返回一个List<T>
,但你也可以返回数组,因为数组实现了IList<T>
;但是要小心,这种情况下不能使用Add()
方法。 - Olivier Jacot-DescombesIList<T>
类型的只读集合,虽然从技术上讲这是正确的(因为我们有IList<T>.IsReadOnly
标识),但我认为最好将其类型定义为IReadOnlyList<T>
(假设你使用的是 .NET 4.5 或更高版本)。原因是IList<T>
通常被视为可变集合,并具有可变 API。很多时候,人们会认为如果他们拥有一个IList<T>
或List<T>
,那么它就可以改变。 - Chris Sinclair