3

As an example use the Join() method from class String. When you call it on a byte array, e.g.

 byte[] bytes = {3,4,5};
 string str = string.Join(",", bytes);

C# compiler maps Join to this signature

 public static String Join<T>(String separator, IEnumerable<T> values);

However, byte[] derives implicitly from class Array which does not derive from the generic IEnumerable, instead it derives from the non-generic IEnumerable, i.e.

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

If I do the same with my own interfaces and class that resemble this case, I will get a compiler error as expected because according to the rules you cannot cast a class deriving from an interface IA (non-generic IEnumerable) into an interface IB (generic IEnumerable) that derives from IA. This means that C# compiler simply hardcodes the specific name IEnumerable. Where is this explained?

Lola Wecv
  • 109
  • 5
  • 1
    There is not special processing of IEnumerable, however there is special processing of Array for covariance. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/arrays#array-covariance – Scott Chamberlain Aug 02 '17 at 15:50
  • 1
    `byte[]` actually implements `IEnumerable` (more specifically, [it implements `IList`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/arrays#arrays-and-the-generic-ilist-interface)). The magic is not in `IEnumerable`, but in the way arrays are treated. Looking at the metadata of `Array` will not tell you everything. – Jeroen Mostert Aug 02 '17 at 15:51
  • @ScottChamberlain There's no array covariance going on here. – Servy Aug 02 '17 at 16:13
  • @Servy which is why that was a comment and not an answer. It was more of a side FYI for the OP. – Scott Chamberlain Aug 02 '17 at 16:16
  • 1
    [The documentation](https://msdn.microsoft.com/library/system.array) **clearly** explains that implementation of the generic forms of certain interfaces, including `IEnumerable` are provided by the runtime. And, your question has already been well-addressed in previous Stack Overflow posts. – Peter Duniho Aug 02 '17 at 16:35

2 Answers2

8

However, byte[] derives implicitly from class Array which does not derive from the generic IEnumerable, instead it derives from the non-generic IEnumerable, i.e.

True, but byte[] implements IEnumerable<byte> itself, which makes perfectly sense. I guess that is a compiler feature on compiling a specific type for Foo[] (so everything deriving from Array basically).

See the implemented interfaces on byte[] (obtained using Type t = typeof(byte[]);, see property ImplementedInterfaces):

byte array implemented interfaces

The first 6 interfaces are probably from Array, the last 5 interfaces are the generic versions of 1-6.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Interestingly, the fact that `foo[]` implements `IReadOnlyList` doesn't seem to be documented anywhere. The other interfaces come from `Array` and `IList` (that the latter is implemented *is* documented, and no, `IList` does not inherit from `IReadOnlyList`). – Jeroen Mostert Aug 02 '17 at 16:06
  • There is an SO post about that as far as I know. – Patrick Hofman Aug 02 '17 at 16:07
-3

Array is special in the CLR. In particular only reference type arrays are covariant. Value types such as byte are not. That's just the way it is. Eric Lippert did a blog post on this a decade ago.

Of the available options for String.Join the only version that matches byte[] is String Join<T>(String, IEnumerable<T>)

byte[] can not be converted to string[], object[] or IEnumerable<string>.

Available options...

String Join(String, IEnumerable<String>) 
String Join(String, Object[])
String Join(String, String[]) 
String Join(String, String[], Int32, Int32) 
String Join<T>(String, IEnumerable<T>)

Example of type errors ...

object[] test1 = new byte[0];
string[] test2 = new byte[0];
IEnumerable<string> test3 = new byte[0];
IEnumerable<byte> test4 = new byte[0];

... compiler errors for above ...

Cannot implicitly convert type 'byte[]' to 'object[]'
Cannot implicitly convert type 'byte[]' to 'string[]'
Cannot implicitly convert type 'byte[]' to  to 'System.Collections.Generic.IEnumerable<string>' 

More Conversion examples

Foo[] test = new OtherFoo[0]; // allowed
OtherFoo[] test = new Foo[0]; // not allowed

public class Foo
{
}
public class OtherFoo : Foo
{
}
Matthew Whited
  • 22,160
  • 4
  • 52
  • 69
  • So why does it match to `IEnumerable` if it implements only `IEnumerable`? That is the question. – Patrick Hofman Aug 02 '17 at 16:07
  • because `System.Byte[]` is a type in mscorlib. `System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089` – Matthew Whited Aug 02 '17 at 16:14
  • So `Foo[]` doesn't implement `IEnumerable`? – Patrick Hofman Aug 02 '17 at 16:15
  • 3
    Note that array types are defined on demand by the CLR. Their assembly is always set to the assembly the element type comes from even though that is not literally true. – Mike Zboray Aug 02 '17 at 16:26
  • 1
    Yep, they even get their own `Opcode.NewArr` versus the standard `NewObj` https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.newarr(v=vs.110).aspx – Matthew Whited Aug 02 '17 at 16:29
  • 1
    _"the only version that matches `byte[]` is `String Join(String, IEnumerable)`"_ -- true, but the OP already knows that. They are asking **why**. And saying something like _"only reference type arrays are covariant"_ doesn't in any way address the question that was asked. Variance has nothing to do with why `IEnumerable` is implemented. In other words, your posted answer does absolutely nothing to address the question that was asked and thus is not useful. – Peter Duniho Aug 02 '17 at 16:39
  • It really does. `byte` is a value type and by the rules can not be converted. That means `byte[]` is not an `object[]` leaving the only type option to be `IEnumerable` which is handled by the runtime. TL;DR... it's magic voodoo from the runtime. – Matthew Whited Aug 02 '17 at 16:56
  • There is no magic going on here. The on-the-fly generated type just implements `IEnumerable`. – Patrick Hofman Aug 02 '17 at 16:58
  • From the devs perspective there is magic going on. Otherwise OP would not have asked the question. – Matthew Whited Aug 02 '17 at 17:18
  • 2
    Array covariance isn't relevant *in this particular case*, because 1) `String.Join` has an overload for `IEnumerable` for any `T`, and 2) every array `T[]` implements `IEnumerable` at runtime. The question is based on the *incorrect premise* that the compiler performs some sort of unsafe conversion (a downcast of `IEnumerable`, not an array conversion), which it does not. The other overloads of `String.Join` are red herrings. If array covariance were removed from the language, the outcome/surprise would be the same, which shows that it has no explanatory power. – Jeroen Mostert Aug 02 '17 at 17:20
  • Holy crap people. Put your thinking caps on. It does make sense in the fact that since it can't convert from `byte[]` to `object[]` none of the signatures that accept arrays are possible. And if `byte[]` could convert to `object[]` then that would be a possible type conversion (though the static type checker still considers generics closer than downcast.) – Matthew Whited Aug 02 '17 at 17:24
  • 3
    None of what you say is wrong, it's just not *an answer to the question*. You seem to be answering the question "why doesn't the compiler use the overload that takes an `object[]` when a `Foo[]` is passed but everything works when a `byte[]` is passed", but that wasn't asked. The OP assumed and wanted to know why there was special treatment for `IEnumerable`, and the answer is "there isn't". In another universe, where the OP made different incorrect assumptions and drew different incorrect conclusions, your answer might have been fine. – Jeroen Mostert Aug 02 '17 at 17:30