0

Given the following struct:

struct Foo : IEnumerable<int>, IEnumerable<string> {
    public IEnumerator<int> GetEnumerator() { yield return 1; }
    public IEnumerator<string> IEnumerable<string>.GetEnumerator() {
        yield return "One"
    }
    public IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

and the following generic method:

void Bar<TEnum, T>(TEnum iterable) where TEnum : IEnumerable<T> {
    foreach (T item in iterable) Console.WriteLine(item);
}

I tried the following:

Foo foo = new Foo();
Bar<Foo, int>(foo); //prints 1
Bar<Foo, string>(foo); //prints One (How does this work)
foreach(int i in foo) Console.WriteLine(i); //prints 1
foreach (string s in foo) Console.WriteLine(s); 
// Error: connot convert from string to int

Now my question would be: what code does the Compiler generate, when it evalueates these generics to a certain type? AFAIK the generic type would just be replaced with the concrete type. However as can be seen above, this wouldn't work for this example. So how can the compiler choose the correct type in the foreach? My only guess would be that it adds a cast to it. i.e.:

foreach(string item in (IEnumerable<string>)iterable) ...

But I chose to declare the Method like this to avoid a cast. Otherwise I could have just done it like this:

void Bar<T>(IEnumerable<T> iterable) { ... }

So I really hope it works without casting...

Edit: My question here is not why I get an error in the last foreach (I know that I can't access explicit interface implementations directly on the type), but how it works in the generic method, which AFAIK should evaluate to the exact same code as the last foreach.

Velocirobtor
  • 188
  • 7
  • `foreach` prefer to use duck-typing. If your type have public `GetEnumerator()` method, then it will be used instead of `IEnumerable.GetEnumerator()` implementation. – user4003407 Oct 12 '16 at 17:18
  • 1
    See the marked duplicate for explanation of the behavior. The only thing that matter is what public `GetEnumerator()` method is available. Casting is the only way to expose the `IEnumerable.GetEnumerator()` method to `foreach`. More generally, if you want to know _"what code does the Compiler generate"_, the way to get that answer is to look, e.g. by using ildasm.exe or your favorite decompiler (dotPeek, Reflector, etc.). See also e.g. https://stackoverflow.com/questions/31342847/c-sharp-foreach-behaviour-clarification – Peter Duniho Oct 12 '16 at 17:20
  • @PetSerAl thanks, but that wasn't really what I was driving at. I edited my post, hope it's clearer now. – Velocirobtor Oct 12 '16 at 17:32
  • @PeterDuniho thanks, I'll take a look at the decompilers. – Velocirobtor Oct 12 '16 at 17:34
  • @Velocirobtor *last foreach* know that `Foo` have public `GetEnumerator()` method, so it use that method. While *generic method* only know that `TEnum : IEnumerable`, so it use `IEnumerable.GetEnumerator()` implementation. – user4003407 Oct 12 '16 at 17:41
  • @PetSerAl thanks, but how does the compiler get to the `IEnumerable.GetEnumerator` implementation. Does it cast the TEnum to an IEnumerable or can it access it immediately (I really would like to avoid a cast.) – Velocirobtor Oct 12 '16 at 17:46
  • 1
    @Velocirobtor Compiler use [constrained](https://msdn.microsoft.com/library/system.reflection.emit.opcodes.constrained.aspx) prefix, so it does not need to cast. – user4003407 Oct 12 '16 at 17:51
  • @PetSerAl Thanks!! That's what I wanted to know. (Now I only hope the compiler will be able to automatically infer type arguments of these kinds of methods in some future version) – Velocirobtor Oct 12 '16 at 18:00
  • @Velocirobtor Are you mean you want to specify only `string` or `int` and have `Foo` inferred? Something like that: `static class Baz { public static void Bar(TEnum iterable) where TEnum : IEnumerable { foreach (T item in iterable) Console.WriteLine(item); } }`, `Baz.Bar(foo)`? – user4003407 Oct 12 '16 at 18:09
  • @PetSerAl I would like to call it directly like this: `Bar(foo)`. Of course, since Foo implements two different IEnumerables, it would be difficult, but if Ido: `int[] arr = {1}; Bar(arr);` I think it should be able to infer that I mean `Bar` automatically. But thanks for that tip. Not always having to type the TEnum type when calling is already a great help, awesome! – Velocirobtor Oct 12 '16 at 18:24

0 Answers0