Home > front end >  foreach of IEnumerable<T> always calls non-generic Enumerator
foreach of IEnumerable<T> always calls non-generic Enumerator

Time:08-29

I have created an object and implemented the interface IEnumerable<Options>. If I try to loop through my object it is working fine but the variables aren't casted to a Options but object.

public class MyClass : IEnumerable<Options>
{
    // I just cut the non-relevant code. The real class is over 500 lines ...

    public List<Options> options = new List<Options>();

    IEnumerator<Options> IEnumerable<Options>.GetEnumerator()
    {
        return options.GetEnumerator();
    }

    public IEnumerator GetEnumerator()
    {
        return options.GetEnumerator();
    }
}

How I loop through:

foreach (var option in myClass)
{
    // Do something
}

typeof(option) returns object. But since I implemented the generic interface I want it to be returned as Options. It is indeed an Options object I can explicitly cast it with (Options)option but it should be automatically casted.

foreach (Options option in myClass)
{
   // I want it to be casted automatically when using 'var'
}

CodePudding user response:

Quoting from the C# language reference, 12.9.5 The foreach statement:

The compile-time processing of a foreach statement first determines the collection type, enumerator type and iteration type of the expression. This determination proceeds as follows:

  • If the type X of expression is an array type then there is an implicit reference conversion from X to the IEnumerable interface (since System.Array implements this interface). The collection type is the IEnumerable interface, the enumerator type is the IEnumerator interface and the iteration type is the element type of the array type X.
  • If the type X of expression is dynamic then there is an implicit conversion from expression to the IEnumerable interface (§10.2.10). The collection type is the IEnumerable interface and the enumerator type is the IEnumerator interface. If the var identifier is given as the local_variable_type then the iteration type is dynamic, otherwise it is object.
  • Otherwise, determine whether the type X has an appropriate GetEnumerator method:
    • Perform member lookup on the type X with identifier GetEnumerator and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.
    • Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods.
    • If the return type E of the GetEnumerator method is not a class, struct or interface type, an error is produced and no further steps are taken.
    • Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.
    • Member lookup is performed on E with the identifier MoveNext and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken.
    • Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not bool, an error is produced and no further steps are taken.
    • The collection type is X, the enumerator type is E, and the iteration type is the type of the Current property.
  • Otherwise, check for an enumerable interface:
    • If among all the types Tᵢ for which there is an implicit conversion from X to IEnumerable<Tᵢ>, there is a unique type T such that T is not dynamic and for all the other Tᵢ there is an implicit conversion from IEnumerable<T> to IEnumerable<Tᵢ>, then the collection type is the interface IEnumerable<T>, the enumerator type is the interface IEnumerator<T>, and the iteration type is T.
    • Otherwise, if there is more than one such type T, then an error is produced and no further steps are taken.
    • Otherwise, if there is an implicit conversion from X to the System.Collections.IEnumerable interface, then the collection type is this interface, the enumerator type is the interface System.Collections.IEnumerator, and the iteration type is object.
    • Otherwise, an error is produced and no further steps are taken.

So in your case the compiler found a public GetEnumerator method in the MyClass, which happened to be a method that returns an IEnumerator, and was happy to use this method for the foreach enumeration because the criteria were satisfied. There was no reason to go a step down and check for an enumerable interface, so the explicitly implemented IEnumerable<Options>.GetEnumerator method was ignored.

CodePudding user response:

In the foreach the compiler does not see the generic version of the GetEnumerator method, because you are implementing it explicitly, and hence without a cast to IEnumerable<Options> the IEnumerable implementation is used, yielding objects.

With your current implementation you'd have to do:

foreach (var option in (IEnumerable<Options>) myClass)

then option is of type Options.

You should implement the non-generic version explicitly, and the generic version implicitly. Then it works as you expect:

IEnumerator<Options> IEnumerable<Options>.GetEnumerator()
{
    return options.GetEnumerator();
}

public IEnumerator GetEnumerator()
{
    return options.GetEnumerator();
}

and the type of var option will be Options.

CodePudding user response:

I would try to implement the interface IEnumerable explicitly, and IEnumerable<T> publicly

public IEnumerator<Options> GetEnumerator()
{
    return options.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

So the default is the generic function, and the non-generic is called only explicitly.

  • Related