How Do Arrays in C# Partially Implement Ilist<T>

How do arrays in C# partially implement IList T ?

New answer in the light of Hans's answer

Thanks to the answer given by Hans, we can see the implementation is somewhat more complicated than we might think. Both the compiler and the CLR try very hard to give the impression that an array type implements IList<T> - but array variance makes this trickier. Contrary to the answer from Hans, the array types (single-dimensional, zero-based anyway) do implement the generic collections directly, because the type of any specific array isn't System.Array - that's just the base type of the array. If you ask an array type what interfaces it supports, it includes the generic types:

foreach (var type in typeof(int[]).GetInterfaces())
{
Console.WriteLine(type);
}

Output:

System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]

For single-dimensional, zero-based arrays, as far as the language is concerned, the array really does implement IList<T> too. Section 12.1.2 of the C# specification says so. So whatever the underlying implementation does, the language has to behave as if the type of T[] implements IList<T> as with any other interface. From this perspective, the interface is implemented with some of the members being explicitly implemented (such as Count). That's the best explanation at the language level for what's going on.

Note that this only holds for single-dimensional arrays (and zero-based arrays, not that C# as a language says anything about non-zero-based arrays). T[,] doesn't implement IList<T>.

From a CLR perspective, something funkier is going on. You can't get the interface mapping for the generic interface types. For example:

typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))

Gives an exception of:

Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.

So why the weirdness? Well, I believe it's really due to array covariance, which is a wart in the type system, IMO. Even though IList<T> is not covariant (and can't be safely), array covariance allows this to work:

string[] strings = { "a", "b", "c" };
IList<object> objects = strings;

... which makes it look like typeof(string[]) implements IList<object>, when it doesn't really.

The CLI spec (ECMA-335) partition 1, section 8.7.1, has this:

A signature type T is compatible-with a signature type U if and only if at least one of the following holds

...

T is a zero-based rank-1 array V[], and U is IList<W>, and V is array-element-compatible-with W.

(It doesn't actually mention ICollection<W> or IEnumerable<W> which I believe is a bug in the spec.)

For non-variance, the CLI spec goes along with the language spec directly. From section 8.9.1 of partition 1:

Additionally, a created vector with element type T, implements the interface System.Collections.Generic.IList<U>, where U := T. (§8.7)

(A vector is a single-dimensional array with a zero base.)

Now in terms of the implementation details, clearly the CLR is doing some funky mapping to keep the assignment compatibility here: when a string[] is asked for the implementation of ICollection<object>.Count, it can't handle that in quite the normal way. Does this count as explicit interface implementation? I think it's reasonable to treat it that way, as unless you ask for the interface mapping directly, it always behaves that way from a language perspective.

What about ICollection.Count?

So far I've talked about the generic interfaces, but then there's the non-generic ICollection with its Count property. This time we can get the interface mapping, and in fact the interface is implemented directly by System.Array. The documentation for the ICollection.Count property implementation in Array states that it's implemented with explicit interface implementation.

If anyone can think of a way in which this kind of explicit interface implementation is different from "normal" explicit interface implementation, I'd be happy to look into it further.

Old answer around explicit interface implementation

Despite the above, which is more complicated because of the knowledge of arrays, you can still do something with the same visible effects through explicit interface implementation.

Here's a simple standalone example:

public interface IFoo
{
void M1();
void M2();
}

public class Foo : IFoo
{
// Explicit interface implementation
void IFoo.M1() {}

// Implicit interface implementation
public void M2() {}
}

class Test
{
static void Main()
{
Foo foo = new Foo();

foo.M1(); // Compile-time failure
foo.M2(); // Fine

IFoo ifoo = foo;
ifoo.M1(); // Fine
ifoo.M2(); // Fine
}
}

How do Arrays implement IList T without implementing the property Count in C#?

This is known as an explicit interface member implementation. The interface member is not exposed as a public member of the type, but it is available by casting the reference to the interface type.

This can be done in C# like this:

interface I
{
void M();
}

class C : I
{
public int P { get; set; }
void I.M() { Console.WriteLine("M!"); }
}

Then you can use these types like this:

C obj = new C();
obj.P = 3;
((I)obj).M();

But this won't compile:

obj.M();

As JeffN825 notes, one reason for implementing the interface members explicity is that they're not supported by the type. For example, Add throws an exception (relevant discussion). Another reason for implementing a member explicity is that it duplicates another public member with a different name. That's the reason Count is implemented explicitly; the corresponding public member is Length. Finally, some members are implemented implicitly, namely, the indexer. Both of these lines work (assuming arr is an array of int):

arr[0] = 8;
((IList<int>)arr)[0] = 8;

Does T[] implement IList T ?

An array with element type T derives from IList<T>.

This is not visible in the meta-data in mscorlib.dll, but the inheritance relationship is created at runtime in the CLR. C# and the CLR are aware of the array type and treat it specially.

C# and .Net why do all arrays inherit from IList?

It is often useful to be able to pass any IList to some algorithm. That algorithm might just care about being able to index into this collection. Many things fulfill this requirement: Arrays, List<T>, many custom collections.

Why does ((IList T )array).ReadOnly = True but ((IList)array).ReadOnly = False?

From MSDN:

Array implements the IsReadOnly property because it is required by the
System.Collections.IList interface. An array that is read-only does
not allow the addition, removal, or modification of elements after the
array is created.

If you require a read-only collection, use a System.Collections class
that implements the System.Collections.IList interface.

If you cast or convert an array to an IList interface object, the
IList.IsReadOnly property returns false. However, if you cast or
convert an array to a IList<T> interface, the IsReadOnly property
returns true.

Read-only here means that the items in the array cannot be modified and that's why it returns false.

Also have a look at Array.IsReadOnly inconsistent depending on interface implementation.

Auto implemented interfaces in Arrays


There is no implementation of IEnumerable and others!

Nope. And yet, IList<Stream> streamList = fsArray; will work. And you can use streamList as you'd expect, though with runtime exceptions if you try to do something not valid on an array (as long as the array is zero-based and has a single dimension—"SZ arrays" in Microsoft parlance—otherwise it's not allowed).

Want to see something worse?

var listMap = typeof(List<FileStream>).GetInterfaceMap(typeof(IList<FileStream>)); // This works fine.
var arrMap = typeof(typeof(FileStream[]).GetInterfaceMap(typeof(IList<FileStream>)); // This throws `ArgumentException`

So in this regard, FileStream[] doesn't even implement IList<FileStream>; if it does then surely the above line should work.

We get an interesting clue as of .NET 4.0. Prior to that, the ArgumentException would have a message of "Interface not found", the same as it would if we'd tried to get that interface on int or string[]. Now it is "Interface maps for generic interfaces on arrays cannot be retrived." [sic]

And it also give us this if we try to get the interface map for IList<Stream> but not for a completely unsupported interface like IList<bool>.

Something unusual is happening here.

And what it is, is that FileStream[] does not directly support any generic interface at all, in the same way that a class or struct would.

Instead there is a stub class called SZArrayHelper which provides these interfaces at runtime for zero-based one-dimension arrays. The comment on the .NET Core Version is informative:

//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
//
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]".
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
// ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it.
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------

And that's what happens. If you try to cast fsArray to IList<Stream> then you get this class doing the calls for you. If you call GetInterfaces() you get similar stub code providing just those related to the type of the array. In either case fsArray does implement all the interfaces mentioned in the book you quote, but it doesn't do it in the same way as a class or struct can.

(Consider for analogy how an int can be both four bytes of the 32-bit value and also a "full" object with interface implementations, method overrides, etc.)

So the book is correct, but you aren't missing anything either, because some of the things we'd expect to happen when a type implements an interface don't.

Furthermore, I think it is mean-less. Because Arrays support co-variance.

Supporting co-variance does not mean they'll implement a given interface, or vice-versa. Especially since arrays' (arguably broken) covariance is very different to that in interfaces, and predates it, and indeed having arrays implement generic interfaces also predates interface covariance.

However, that it was decided that FileStream[] should indeed implement Stream[] does relate to arrays being covariant (the decision would have been just bizarrely wrong otherwise), but it needs the extra help that SZArrayHelper provides, rather than being automatically entailed by it.

Restrict an IList T extension method to exclude Arrays

You can add your extension method to List<T> not on IList<T>

Initialize List T with array in CLR C++?

You need to use safe_cast. According to the MSDN documentation on System::Array,

Important

Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic::IList<T>, System.Collections.Generic::ICollection<T>, and System.Collections.Generic::IEnumerable<T> generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

As you can see, the cast must be done explicitly in C++ at runtime, e.g.

List<Process^>^ processList = gcnew List<Process^>(
safe_cast<IEnumerable<T> ^>(
Process::GetProcessesByName(this->processName)));


Related Topics



Leave a reply



Submit