3

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'
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
AidenDean
  • 318
  • 2
  • 14
  • 3
    You have made the generic GetEnumerator method from the generic IEnumerable interface an explicit interface implementation. Try making the non-generic IEnumerable.GetEnumerator an explicit interface implementation instead, and declare the generic GetEnumerator normally (i.e., not as an explicit interface implementation) Not tested/verified myself, but i believe that's the root of your pain... –  Aug 28 '22 at 12:35
  • 1
    Reopened since it seems to be different to [How do I implement IEnumerable](https://stackoverflow.com/questions/11296810/how-do-i-implement-ienumerablet) – Tim Schmelter Aug 28 '22 at 12:44
  • 1
    @TimSchmelter Seems the same to me, it shows how to correctly implement these two interfaces together – Charlieface Aug 28 '22 at 12:59
  • @Charlieface: you're right, if you use that code this will work as well. `public IEnumerator GetEnumerator(){ return options.GetEnumerator(); }` – Tim Schmelter Aug 28 '22 at 13:19
  • Does this answer your question? [How do I implement IEnumerable](https://stackoverflow.com/questions/11296810/how-do-i-implement-ienumerablet) – Charlieface Aug 28 '22 at 18:19

3 Answers3

2

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
0

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:

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

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

and the type of var option will be Options.

Update: For those interested in the full GetEnumerator() resolution algorithm, which is fairly complex, see the C# language specification, plus improvements in C# 8.0 and in C# 9.0.

Ondrej Tucny
  • 27,626
  • 6
  • 70
  • 90
  • *"the compiler does not see the generic version of the `GetEnumerator` method, because you are implementing it explicitly"* -- This doesn't explain why the compiler uses the generic version, when both the generic and the non-generic are implementing explicitly... – Theodor Zoulias Aug 28 '22 at 14:08
  • [SharpLab link](https://sharplab.io/#v2:CYLg1APgAgDABFAjAOgMIHsA2mCmBjAFwEt0A7AZwG4BYAKFgUQBYba6oAmRgdjoG86cIQg4dBwgbWHS4AM3QAnHAEM8ACzgAKAG7KFcAPpwipOKRwB3OAFkAnqkzLy5TQEpXcPnAC+4ob9oA9i47BydyOBBGAGYAHhMCAD5+PzgAejSoaLgASQBRUgBXAFscBWUCRXjSJLgAcRwCApKyisU3OABeRLhgHFllQswCVml8otLyyoVq2qQ4hMTkBqaJ1umO7t7+weHWVIys3ObJtv0Vk/X2jy2+gaGR1PGWqcUY5cbL14VNnrvdx6BOhAA) with both `GetEnumerator` implementing explicitly. – Theodor Zoulias Aug 28 '22 at 14:24
  • @TheodorZoulias Because the compiler gives precedence to `IEnumerable`. The search algorithm for the suitable `GetEnumerator` method is fairly complex and described [in the C# language specification](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#1295-the-foreach-statement). Btw. you don't need to expose any `IEnumerable` at all, just given the compiler a suitable public `GetEnumerator` method satisfying certain criteria. – Ondrej Tucny Aug 28 '22 at 15:08
  • Ondrej sure, but shouldn't you include this detail in the answer? – Theodor Zoulias Aug 28 '22 at 15:14
  • @TheodorZoulias What's more valuable for the OP? A practical answer with a reasonably simple explanation, or a theoretical answer citing a two-page abstract description from the language spec which hardly anyone ever reads in detail? – Ondrej Tucny Aug 28 '22 at 18:21
  • @TheodorZoulias Btw you are welcome to improve my answer. – Ondrej Tucny Aug 28 '22 at 18:23
  • Ondrej I am not sure what the OP would do if they had to choose between a purely practical and a purely theoretical answer. I am just pointing out that the statement *"the compiler does not see the generic version of the `GetEnumerator` method, because you are implementing it explicitly"* is not accurate. If you don't want to be detailed, you could cut this statement out, and your answer will be improved IMHO. – Theodor Zoulias Aug 28 '22 at 18:43
  • 1
    @TheodorZoulias Ah, you are right, fixed. Wrong copy & paste from LINQPad. – Ondrej Tucny Aug 29 '22 at 07:30
  • @TheodorZoulias Regarding your suggestion to _remove_ the sentence _"the compiler does not see the generic version of the GetEnumerator method, because you are implementing it explicitly"_ — no, I don't believe this would anyhow “improve” my question. The reason is, it's a _fact_. In the OP's case the compiler can see _exactly one_ public `GetEnumerator` method, which is `public IEnumerator GetEnumerator()` and the resolution algorithm stops at that point. No interfaces get ever inspected. – Ondrej Tucny Aug 29 '22 at 08:46
  • Ondrej no, it's not a fact, it's part of a fact. Your answer does not present the whole fact, and this could cause people taking your answer at face value to reach wrong conclusions. The wrong conclusion is that "if you implement explicitly the `GetEnumerator`, the compiler will never be able to see it", which is false. – Theodor Zoulias Aug 29 '22 at 09:44
  • @TheodorZoulias No, _“"if you implement explicitly the GetEnumerator, the compiler will never be able to see it"”_, is not a conclusion that could be drawn from my answer. From A ⇒ B you cannot reason about B ⇒ A. My answer covers the OPs problem and I have no intention in copy & pasting a two-page spec to it. Please let's end this discussion. – Ondrej Tucny Aug 29 '22 at 10:07
0

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.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133