Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between returning an Array and an IList<>? (RE: Eric Lippert's harmful arrays)

I just read Eric Lippert's "Arrays considered somewhat harmful" article. He tells his readers that they "probably should not return an array as the value of a public method or property," with the following reasoning (slightly paraphrased):

Now the caller can take that array and replace the contents of it with whatever they please. The sensible thing to return is IList<>. You can build yourself a nice read-only collection object once, and then just pass out references to it as much as you want.

In essence,

An array is a collection of variables. The caller doesn’t want variables, but it’ll take them if that’s the only way to get the values. But neither the callee nor the caller wants those variables to ever vary.

In an attempt to understand what he meant by variables vs. values, I built demo class that has Array and IList<> fields, and methods that return references to both. 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;
    }
}

If I understand correctly, the GetArr() method is Lippert's bad practice, and GetList() is his sensible practice. Here is a demo of the bad caller mutability behavior of 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

But I don't understand his distinction - the IList<> reference has the same caller mutation problem:

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

Obviously, I trust that Eric Lippert knows what he's talking about, so where have I misinterpreted him? In what way is a returned IList<> reference safer than an Array reference?

like image 694
kdbanman Avatar asked Nov 04 '15 18:11

kdbanman


1 Answers

Had that article been written today it would have almost certainly suggested using IReadOnlyList<T>, but that type didn't exist in 2008 when that article was written. The only supported means of having a read-only indexable list interface was to use an IList<T> and simply not implement the mutation operations. This does let you return an object that the caller can't mutate, although it's not necessarily clear (particularly from the API) that the object is in fact immutable. That drawback in .NET has been remedied since then.

like image 109
Servy Avatar answered Oct 10 '22 02:10

Servy