What Interfaces Do All Arrays Implement in C#

What interfaces do all arrays implement in C#?

From the documentation (emphasis mine):

[...] 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.

EDIT: as Jb Evain points out in his comment, only vectors (one-dimensional arrays) implement the generic interfaces. As to why multi-dimensional arrays don't implement the generic interfaces, I'm not quite sure since they do implement the non-generic counterparts (see the class declaration below).

The System.Array class (i.e. every array) also implements these non-generic interfaces:

public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable

How do arrays in C# partially implement IListT?

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
}
}

Relation between IEnumerable and arrays

Since you came from Java, I will start by telling you that in C#, there is a unified type system, where every type derives from object, unlike in Java where there are special "primitives".

So in C#, arrays are just another type. Who says they can't implement interfaces? They can! All the compiler provides is some syntax for creating them. In actuality, arrays' type is System.Array.

This is its declaration:

public abstract class Array : ICloneable, IList, ICollection, 
IEnumerable, IStructuralComparable, IStructuralEquatable

See IEnumerable in there?

EDIT:

For IEnumerable<T>, we can find it in the language spec:

Section 12.1.2:

A one-dimensional array T[] implements the interface
System.Collections.Generic.IList (IList for short) and its base
interfaces.

IList<T> implements IEnumerable<T>.

Can C# interfaces implement lists or arrays? If so, how?

C# interfaces cannot directly inherit from Lists (because they are classes) - but they can inherit from any interface such as IList or IEnumerable:

Array is a class which inherits from IList and IEnumerable. So you can inherit from IList<T>

    public interface Test<T> : IList<T>
{
// code here
}

Or you can inherit from IEnumerable:

    public interface Test<T> : IEnumerable<T>
{
// code here
}

Is there a way to pass arrays of different types to an interface implementation

Your current idea (of using Array (not Array[]) and Byte[]) is... ill-informed and ill-advised.

For one, .NET requires implementations of an interface to exactly match the interface definition, so if you have a method void ParseFile(Array fileContents) then the implementation must also have void ParseFile(Array fileContents) - you cannot have ParseFile(Byte[] fileContents). Here's why you can't:

Supposing that this actually compiles:

interface IFileBuilder {
void ParseFile(Array fileContents);
}

class OctetFileBuilder : IFileBuilder {
public void ParseFile(Byte[] fileContents) {
// ...
}
}

...then run this:

void Main() {

Byte[] byteArray = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
String[] strArray = new String[] { "foo", "bar", "baz" };

OctetFileBuilder ofb = new OctetFileBuilder();
ofb.ParseFile( byteArray ); // OK, so far so good.

ofb.ParseFile( strArray ); // Compiler rejects this because `String[]` isn't `Byte[]`. That's also OK.

IFileBuilder fb = ofb;
fb.ParseFile( byteArray ); // This would be okay because a `Byte[]` can be the argument for an `Array` parameter.

fb.ParseFile( strArray ); // But what happens here?

}

That last line there is the problem: fb.ParseFile( strArray ).

The implementation expects Byte[] but it's being passed a String[].

I imagine you would expect .NET to throw a runtime error - or that it would somehow magically know how to convert String[] to Byte[] (no, it won't). Instead that entire program simply won't compile.

Now, in reality, your implementation would have to look like this in order for it to build:

class OctetFileBuilder : IFileBuilder {
public void ParseFile(Array fileContents) {
Byte[] byteArray = (Byte[])fileContents; // Runtime cast.
}
}

...so now your code will compile and start to run, but it will crash before it can complete because when ParseFile is given a String[] it will cause an InvalidCastException because you can't meaningfully directly cast from String[] to Byte[].

Solution: Covariant generics!

This is what generic types (and contravariance and covariance) are all about and why when designing polymorphic interfaces you need to think carefully about the direction of the movement of data inside your program (as generally speaking, "forward"/"input" data can be "shrunk" and "output" data can be "grown"/"expanded"/"extended" - but not vice-versa. This is what the in and out generic-type modifiers are for.

So I'd do it like this: (disclaimer: I have no idea what your interface is actually for, nor why you would want anything other than Byte[] for your ParseFile method parameter...)

interface IFileBuilder<in TInput>
{
void ParseFile( IReadOnlyList<TInput> fileContents )
}

class OctetFileBuilder : IFileBuilder<Byte> {
public void ParseFile(Byte[] fileContents) {
// ...
}
}

(Note that in this particular case the use of contravariance/covariance modifiers (the in TInput part) is pointless because the primitive and base-types in .NET (Byte, Int32, String, etc) are either structs without inheritance or are sealed-types).

Usage:

void Main() {

Byte[] byteArray = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
String[] strArray = new String[] { "foo", "bar", "baz" };

OctetFileBuilder ofb = new OctetFileBuilder();
ofb.ParseFile( byteArray ); // OK, so far so good.

ofb.ParseFile( strArray ); // Compiler rejects this because `String[]` isn't `Byte[]`. That's also OK.

IFileBuilder<Byte> fb = ofb;
fb.ParseFile( byteArray ); // OK

fb.ParseFile( strArray ); // Compiler error: `String[]` is not `IReadOnlyList<Byte>`.

}

So your hypothetical runtime error is now a guaranteed compile-time error.

And compile-time errors are much better than runtime errors - and this approach also means you can reason about the direction of data-flow in your application.

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.

Array doesn't implement ICollectionT but is assignable

I see Array implements IList and ICollection but as far as I know it
never implements ICollection

It does implement ICollection<T>, the implementation is simply injected at run-time.

From the documentation:

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 as a result,
the generic interfaces do not appear in the declaration syntax for the
Array class.
In addition, 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.

C# Interface to allow array as parameter

To make it work with arrays, you should make an explicit operator. For example:

public static explicit operator ArrayLike<T>(T[] array) => new ArrayLikeImplementation<T>(array);

Make sure to also change ArrayLikeImplementation to a specific class implementation in the method implementation.

ArrayLike should be a class since it cannot be an interface too.

Source: Microsoft Documentation



Related Topics



Leave a reply



Submit