Reputation: 998
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 )
{
}
Upvotes: 5
Views: 6717
Reputation: 100527
Based on "foreach statement" section in the C# specification (8.8.4 of the C# 5.0 specification) I believe your case falls into section where "enumerated type have GetEnumerable which returns proper object" - there is no implicit conversion to determine type of element (there are some in case there is no unique GetEnumerable on the class)
The collection type is X, the enumerator type is E, and the element type is the type of the Current property.
The foreach
is translated in following code (removed try/finally) in your case:
// foreach( string str in items )
// embedded-statement
IEnumerator enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
string str = (string)(Object)enumerator.Current; // **
embedded-statement
}
}
Note that on line **
the type of object to be converted is Object
(as it determined by result of enumerator.Current
which is Object
in case of non-generic IEnumerator
. As you can see there is no mentioning of your B
type that would allow your implicit conversion.
Note: Specification can be found in normal VS C# installation at following location: "Program Files (x86)\Microsoft Visual Studio XX.0\VC#\Specifications\YYYY" (XX is your version 10 for VS2010, 11 for VS2012 and YYYY is locale 1033 for EN-US).
Upvotes: 3
Reputation: 50114
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.
Upvotes: 8
Reputation: 1038780
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
}
Upvotes: 5