I have a question about foreach behavior in C#.
My custom class implements a custom GetEnumerator. This method returns another object that is implicitly convertible to string.
However if I do foreach(string s in customClass) it fails during run-time ("Unable to cast object of type .. to string").
However if I do string x = new B() it works like a charm.
NOTE: There is nothing in particular I need to achieve here, I just want to understand what is going on. I am particularly interested in this non-generic behavior.
Any ideas? What fundamental knowledge am I missing?
Code to replicate this:
public class A : IEnumerable
{
    #region IEnumerable Members
    public IEnumerator GetEnumerator()
    {
        yield return new B();
    }
    #endregion
}
public class B
{
    public static implicit operator string( B b )
    {
        return "to-stringed implicit";
    }
}
// CODE:
A a = new A();
// Works.
B b = new B();
string xxx = b;
// Doesnt work.
foreach( string str in a )
{
}
                Your implicit conversion can only be used if the compiler sees it can be used at compile-time:
B b = new B();
string str = b;
It can't be used at run-time:
B b = new B();
object obj = b;
string str = obj; // will fail at run-time
Basically, this is because it would be far too expensive to look through all the possible conversions from obj to a string that might work. (See this Eric Lippert blog post).
Your IEnumerator returns objects, so calling foreach (string str in a) is trying to convert an object to a B at runtime.
var e = a.GetEnumerator();
e.MoveNext();
object o = e.Current;
string str = o; // will fail at run-time
If you instead use foreach(B item in a) { string str = item; ... }, the runtime conversion is from object to B (which works, because each object is a B), and the conversion from B to str can be made by the compiler.
var e = a.GetEnumerator();
e.MoveNext();
object o = e.Current;
B item = o;        // will work at run-time because o _is_ a B
string str = item; // conversion made by the compiler
A different way to fix this would be to make A implement IEnumerable<B> instead of just IEnumerable. Then, foreach (string str in a) translates more as
var e = a.GetEnumerator();
e.MoveNext();
B b = e.Current; // not object!
string str = b;  // conversion made by the compiler
so the compiler can make the conversion without you having to change your foreach loop.
The foreach method doesn't require you to implement IEnumerable. All that's required is that your class has a method called GetEnumerator:
public class A
{
    public IEnumerator GetEnumerator()
    {
        yield return new B();
    }
}
and then you could use it in a foreach:
A a = new A();
foreach (B str in a)
{
    Console.WriteLine(str.GetType());
}
But the foreach statement will not call the implicit operator. You will have to do this manually:
foreach (B item in a)
{
    string str = item;
    // use the str variable here
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With