我刚读过Eric Lippert's "Arrays considered somewhat harmful"文章。他告诉他的读者,他们可能不应该将一个数组作为公共方法或财产的价值返回,"用以下推理(稍微解释):
现在调用者可以使用该数组并用他们喜欢的内容替换它的内容。返回的明智之举是
IList<>
。你可以自己构建一个很好的只读集合对象,然后只需要根据需要传递对它的引用。
实质上,
数组是变量的集合。调用者不需要变量,但是如果这是获取值的唯一方法,它将采用它们。但是被调用者和调用者都不希望这些变量发生变化。
为了理解变量与值的含义,我构建了具有Array
和IList<>
字段的demo类,以及返回对两者的引用的方法。 Here it is on 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<>
引用以何种方式比Array
引用更安全?
答案 0 :(得分:5)
如果今天写过这篇文章几乎肯定会建议使用IReadOnlyList<T>
,但是那篇文章在撰写该文章时并不存在于2008年。具有只读可索引列表接口的唯一受支持的方法是使用IList<T>
并且根本不实现变异操作。这个确实让你返回一个调用者无法改变的对象,尽管它不一定清楚(特别是来自API)该对象实际上是不可变的。从那以后,.NET中的这个缺点就得到了补救。
答案 1 :(得分:3)
将List<T>
对象作为IList<T>
返回并不是他推荐的内容。相反,您应该创建一个实现IList<T>
的只读对象并返回该对象。例如,您可以使用ReadOnlyCollection<T>
对象。
答案 2 :(得分:2)
他指的是使用只读具体类型实现IList<T>
的能力:
如果您正在编写这样的API,请将数组包装在ReadOnlyCollection中并返回IEnumerable或IList或其他内容,但不返回数组。 (当然,不要简单地将数组转换为IEnumerable并认为你已经完成了!那仍然是传递变量;调用者可以简单地转换回数组!只有在读取数据时才传递出一个数组 - 唯一的对象。)