6

I noticed something strange and there is a possibility I am wrong.
I have an interface IA and class A:

interface IA { .... }
class A : IA { .... }

In other class I have this:

private IList<A> AList;

public IList<IA> {
    get { return AList; }
}

But I get compilation error. But if I change it to:

public IList<IA> {
    get { return AList.ToArray(); }
}

Everything is fine.

Why is it?

Naor
  • 23,465
  • 48
  • 152
  • 268

3 Answers3

8

Why this doesn't work

private IList<A> AList;  
public IList<IA> { get { return AList; } } 

Exposing the property as IList<IA> would allow you to try to add class B : IA to the list, but the underlying list is really IList<A>, B is not A, so this would blow up in your face. Thus, it is not allowed.

Why this works:

public IList<IA> { get { return AList.ToArray(); } } 

Array variance is broken. You can return the list as an array, it will still blow up in your face at runtime if you tried an Add operation (or try to replace an object at a given index with something other than an object of type A, but it's legal at compile time. A different example of this variance at play:

string[] array = new string[10];
object[] objs = array; // legal
objs[0] = new Foo(); // will bite you at runtime 

From comments:

So what you suggest to use? How can I make the property return valid object? How can I make the return value read only?

If consumers only need to iterate over the sequence and not have random, indexed access to it, you can expose the property as an IEnumerable<IA>.

public IEnumerable<IA> TheList
{
     get { return AList.Select(a => a); }
}

(The Select is actually not technically needed, but using this will prevent consumers from being able to cast the result to its true underlying List<> type.) If the consumers decide they want a list or an array, they are free to call ToList() or ToArray() on it, and whatever they do with it (in terms of adding, removing, replacing items) will not affect your list. (Changes to the items' properties would be visible.) Similarly, you could also expose the collection an IList<IA> yourself in a safe way

public IList<IA> TheList
{
    get { return AList.ToList<IA>(); }
}

Again, this would return a copy of the list, so any changes to it would not affect your underlying list.

Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • So what you suggest to use? How can I make the property return valid object? How can I make the return value read only? – Naor Jul 19 '11 at 17:10
  • @Naor, depends on what you want to do. If users need to be able access elements by index, you'll need to return a list or array of some sort. If consumers will just be iterating over the results, you can instead expose an `IEnumerable`, which is readonly and covariant. – Anthony Pegram Jul 19 '11 at 17:12
0

Because native arrays are broken. This code is bad, you shouldn't do it, and the C# designers wish desperately they could undo it.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 4
    Let's not get too histrionic here. I consider this to be an unfortunate flaw in the type system. Particularly since it imposes a runtime safety check cost for a scenario which is both common and safe. If I could wave a magic wand and go back in history and convince the CLR (or Java!) designers that this was a bad idea, sure, I would. But I don't think anyone sitting in this hallway is *desperate* about it. There's no *despair* on the subject. You just accept that the type system is slightly flawed and move on, you know? – Eric Lippert Jul 19 '11 at 16:58
  • @Naor: He's criticizing the .NET CLR, not necessarily your code. – Yuck Jul 19 '11 at 17:12
  • @Naor: Array covariance is broken. – jason Jul 19 '11 at 17:53
  • 1
    @Naor: The Java and CLR type systems both allow an array of turtles to be treated as an array of animals. This means that an operation which appears to be safe at compile time -- adding a tiger to an array of animals -- actually might fail at runtime. It also means that every time you add a tiger to an array of animals, we have to run a check to see if the array of animals is actually one that can accept a tiger. This imposes a performance penalty for correct code in a common case, which is what we want to avoid. It's a lousy feature and I wish neither the JVM nor the CLR did it. – Eric Lippert Jul 20 '11 at 17:44
-1

Arrays are covariant but lists are not.

Ben Robinson
  • 21,601
  • 5
  • 62
  • 79