返回数组和IList<>?之间的区别是什么? (RE:Eric Lippert的有害数组)

时间:2015-11-04 18:26:15

标签: c# arrays collections

我刚读过Eric Lippert's "Arrays considered somewhat harmful"文章。他告诉他的读者,他们可能不应该将一个数组作为公共方法或财产的价值返回,"用以下推理(稍微解释):

  

现在调用者可以使用该数组并用他们喜欢的内容替换它的内容。返回的明智之举是IList<>。你可以自己构建一个很好的只读集合对象,然后只需要根据需要传递对它的引用。

实质上,

  

数组是变量的集合。调用者不需要变量,但是如果这是获取值的唯一方法,它将采用它们。但是被调用者和调用者都不希望这些变量发生变化。

为了理解变量与值的含义,我构建了具有ArrayIList<>字段的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引用更安全?

3 个答案:

答案 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并认为你已经完成了!那仍然是传递变量;调用者可以简单地转换回数组!只有在读取数据时才传递出一个数组 - 唯一的对象。)