When does IEnumerable.GetEnumerator get called instead of IEnumerable<T>.GetEnumerator?
Asked Answered
S

3

9

Looking at this code :

public class myWords : IEnumerable<string>
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    public IEnumerator<string> GetEnumerator()
    {
        return f.Select(s => s + "2").GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

Running :

 myWords m = new myWords();
 foreach (var s in m)
 {
     Console.WriteLine(s);
 }

Yields

I 2
ve you2    // notice "2", so the generic Ienumerator has executed.

I understand that the non-generic IEnumerator version is for compatibility.

Question:

  1. In what scenario will the non-generic be invoked?
  2. How can I force my code to be run with the non-generic IEnumerator?
Stipitate answered 16/2, 2013 at 10:34 Comment(0)
F
4

The other answers sort of miss the point.

Interfaces do not matter at all if the (compile-time) type of what is beeing foreached has a public non-generic non-static method called GetEnumerator which takes zero arguments. (The return type of this method can be anything, generic or non-generic: interfaces will not matter.)

So the reason why the first of your methods is called, is that this is the public method.

You can change that:

public class myWords : IEnumerable<string>
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return f.Select(s => s + "2").GetEnumerator();
    }

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

To prove that interfaces are not needed, try this:

public class myWords // no interfaces!
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

However, it is wise to implement IEnumerable<>. Then your type can be used with Linq (extension methods on IEnumerable<>), and can be used as argument to other methods that simply require an IEnumerable<>.

Also, it is wise to have the method (or explicit interface implementation) that returns the non-generic IEnumerator just call through to the method (or explicit interface implementation) that returns IEnumerator<>. Having the two return distinct sequences is really confusing (but nice for asking and answering questions on how things work).

Fechter answered 2/2, 2015 at 19:20 Comment(5)
If you're trying to show that interfaces aren't needed, I'd suggest implementing a structure which has a Current property and MoveNext method but doesn't implement any interfaces, and have myWords' GetEnumerator method return that type rather than IEnumerable.Valer
OK, "rather than IEnumerator" you mean, I guess. What I was trying to say, was the C# compiler will go for the public method on the conditions I specified, so whether there are interfaces, and what the interfaces map to, will not matter. I was not saying that the foreach would proceed as intended/expected. I am about to try your suggestion.Fechter
@Valer What I wrote worked. This: class DoMe { public BadE GetEnumerator() { return default(BadE); } } struct BadE { ushort state; public bool MoveNext() { ++state; return true; } public string Current { get { return arr[state % 4]; } } static readonly string[] arr = { "a", "b", "c", "d", }; } Creating a new DoMe and foreaching it works. What have I shown?Fechter
Formatting in a comment is ugly, but that looks like what I had in mind. In your answer, type myWords has a GetEnumerator method that returns IEnumerator; I think it's helpful to show that not only does the thing with the GetEnumerator method not have to implement an interface, but the enumerator itself doesn't have to implement an interface either.Valer
@Valer OK, I see your point. Note that if you have public void GetEnumerator() { }, and additionally implement IEnumerable<> explicitly, the C# compiler will still go for the public method. The return type void is not suitable, so it gives a compile-time error. But it still uses the public method and disregard any interfaces the type implements.Fechter
P
7

The non-generic IEnumerator will be executed whenever code casts the class to the non-generic interface:

((IEnumerable)myWords).GetEnumerator(); // this calls the non-generic one

This is mostly relevant if you pass your class to some legacy function that requires a non-generic IEnumerator.

So if you have some library that contains a function and you pass your class to this function, it will use the non-generic IEnumerator

DoSomeStuffWithAnIEnumerable(IEnumerable x)
{
   var foo = x.GetEnumerator();

   // or, as stackx said, an example with foreach:
   foreach (var foo2 in x)
   Console.WriteLine(foo2);
}


DoSomeStuffWithAnIEnumerable(new myWords());


Note that it is perfectly valid to simply implement the non-generic IEnumerator using the generic one:

public class myWords : IEnumerable<string>
{
    ....    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

That way you can be sure that they both have the same effects.

Poetics answered 16/2, 2013 at 10:38 Comment(1)
Or, to give an example using foreach: foreach (object word in (IEnumerable)myWords) …Petit
T
4

The non-generic version of the IEnumerable is implemented with an explicit interface implementation. This means that you can only call the explicitly implemented function by casting to the interface.

The reason IEnumerable is implemented explicitly is that the method signatures are the same except for the return type.

Casting myWords explicitly to IEnumerable allows you to call the non-generic version like this: (IEnumerable)myWords.

The C# Guide explains how this works: Explicit interface implementation.

Turgor answered 16/2, 2013 at 10:47 Comment(0)
F
4

The other answers sort of miss the point.

Interfaces do not matter at all if the (compile-time) type of what is beeing foreached has a public non-generic non-static method called GetEnumerator which takes zero arguments. (The return type of this method can be anything, generic or non-generic: interfaces will not matter.)

So the reason why the first of your methods is called, is that this is the public method.

You can change that:

public class myWords : IEnumerable<string>
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return f.Select(s => s + "2").GetEnumerator();
    }

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

To prove that interfaces are not needed, try this:

public class myWords // no interfaces!
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

However, it is wise to implement IEnumerable<>. Then your type can be used with Linq (extension methods on IEnumerable<>), and can be used as argument to other methods that simply require an IEnumerable<>.

Also, it is wise to have the method (or explicit interface implementation) that returns the non-generic IEnumerator just call through to the method (or explicit interface implementation) that returns IEnumerator<>. Having the two return distinct sequences is really confusing (but nice for asking and answering questions on how things work).

Fechter answered 2/2, 2015 at 19:20 Comment(5)
If you're trying to show that interfaces aren't needed, I'd suggest implementing a structure which has a Current property and MoveNext method but doesn't implement any interfaces, and have myWords' GetEnumerator method return that type rather than IEnumerable.Valer
OK, "rather than IEnumerator" you mean, I guess. What I was trying to say, was the C# compiler will go for the public method on the conditions I specified, so whether there are interfaces, and what the interfaces map to, will not matter. I was not saying that the foreach would proceed as intended/expected. I am about to try your suggestion.Fechter
@Valer What I wrote worked. This: class DoMe { public BadE GetEnumerator() { return default(BadE); } } struct BadE { ushort state; public bool MoveNext() { ++state; return true; } public string Current { get { return arr[state % 4]; } } static readonly string[] arr = { "a", "b", "c", "d", }; } Creating a new DoMe and foreaching it works. What have I shown?Fechter
Formatting in a comment is ugly, but that looks like what I had in mind. In your answer, type myWords has a GetEnumerator method that returns IEnumerator; I think it's helpful to show that not only does the thing with the GetEnumerator method not have to implement an interface, but the enumerator itself doesn't have to implement an interface either.Valer
@Valer OK, I see your point. Note that if you have public void GetEnumerator() { }, and additionally implement IEnumerable<> explicitly, the C# compiler will still go for the public method. The return type void is not suitable, so it gives a compile-time error. But it still uses the public method and disregard any interfaces the type implements.Fechter

© 2022 - 2024 — McMap. All rights reserved.