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