Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does foreach casts objects to specified types?

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 )
{
}
like image 524
NeverStopLearning Avatar asked Dec 08 '22 18:12

NeverStopLearning


2 Answers

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.

like image 144
Rawling Avatar answered Dec 11 '22 07:12

Rawling


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
}
like image 45
Darin Dimitrov Avatar answered Dec 11 '22 09:12

Darin Dimitrov