What's the point of IEnumerable and IEnumerable<T>?
-
So, I found out by accident that a class doesn't actually need to implement IEnumerable to be able to foreach over it. This works just fine (VS2005):
class X
{
private List<int> mList = new List<int>() ;
public X() { mList.Add(1) ; mList.Add(2) ; mList.Add(3) ; }
public IEnumerator GetEnumerator() { return mList.GetEnumerator() ; }
}X x = new X() ;
foreach ( int n in x )
System.Console.WriteLine( n ) ;MSDN confirms[^] this behaviour:
Evaluates to a type that implements IEnumerable or a type that declares a GetEnumerator method.
Although this[^] suggests that it's a C# thing only.
The reason for implementing IEnumurable therefore, is (a) to identify the class as enumerable/foreach-able, and (b) to provide a language independant implementation that will work in other languages that don't provide the C# foreach performance "hack".
But even given that, it still begs the question: why did the C# team feel the need for foreach to accept objects that don't implement IEnumerable? But it gets worse when using generics. Deriving X from IEnumerable<T> forces you to implement both the generic (expected) and non-generic (huh?!) version of GetEnumerator():
class X : IEnumerable<int>
{
private List<int> mList = new List<int>() ;
public X() { mList.Add(1) ; mList.Add(2) ; mList.Add(3) ; }
IEnumerator<int> IEnumerable<int>.GetEnumerator() { return mList.GetEnumerator() ; }
public IEnumerator GetEnumerator() { return mList.GetEnumerator() ; }
}X x = new X() ;
foreach ( int n in x )
System.Console.WriteLine( n ) ;Except that when stepping through the debugger, foreach uses the *non-generic* GetEnumerator(). So why even bother having the generic one? I'm pretty new to C# but this seems kinda dumb. Am I missing something?
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [
Taka Muraoka wrote:
I'm pretty new to C# but this seems kinda dumb. Am I missing something?
Not really. The biggest thing you're missing is the fact that when .NET 1.0 was first released there were no real internal guidelines being followed by the team. As the outside world started using the Framework more and the team got smarter about what they were trying to do, guidelines started to form and best practices started to take shape. The fact that C# implements a
foreach
iterator was done mostly as a convenience for the developer more than anything else. There is always an argument over which one provides more performance and there are cases where one wins over the other. The reality is that any type which should support "foreachable" behavior should implement the interface. The rules for implementing the genericIEnumerable<T>
exist that way becauseIEnumerable<T>
implements theIEnumerable
interface.Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[Forum Guidelines] [Articles] [Blog]
-
Taka Muraoka wrote:
I'm pretty new to C# but this seems kinda dumb. Am I missing something?
Not really. The biggest thing you're missing is the fact that when .NET 1.0 was first released there were no real internal guidelines being followed by the team. As the outside world started using the Framework more and the team got smarter about what they were trying to do, guidelines started to form and best practices started to take shape. The fact that C# implements a
foreach
iterator was done mostly as a convenience for the developer more than anything else. There is always an argument over which one provides more performance and there are cases where one wins over the other. The reality is that any type which should support "foreachable" behavior should implement the interface. The rules for implementing the genericIEnumerable<T>
exist that way becauseIEnumerable<T>
implements theIEnumerable
interface.Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[Forum Guidelines] [Articles] [Blog]
Thanks for the info.
Scott Dorman wrote:
The fact that C# implements a foreach iterator was done mostly as a convenience for the developer more than anything else.
Given that this kind of feature is pretty standard in modern languages, I'd say it's perhaps a little more than a developers' convenience. But it wouldn't even occur to me to do it any other way than via the IEnumerable interface. For someone to say "we'll have it accept anything that has a GetEnumerator() method" suggests to me someone who doesn't really know what they're doing :|
Scott Dorman wrote:
The rules for implementing the generic IEnumerable exist that way because IEnumerable implements the IEnumerable interface.
This is the one that really baffles me. The generic GetEnumerator() doesn't appear to get called so I don't quite see the point of having IEnumerable<T> at all. You might as well just derive from IEnumerable :shrug:
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Atom feed reader with support for Code Project.
-
Thanks for the info.
Scott Dorman wrote:
The fact that C# implements a foreach iterator was done mostly as a convenience for the developer more than anything else.
Given that this kind of feature is pretty standard in modern languages, I'd say it's perhaps a little more than a developers' convenience. But it wouldn't even occur to me to do it any other way than via the IEnumerable interface. For someone to say "we'll have it accept anything that has a GetEnumerator() method" suggests to me someone who doesn't really know what they're doing :|
Scott Dorman wrote:
The rules for implementing the generic IEnumerable exist that way because IEnumerable implements the IEnumerable interface.
This is the one that really baffles me. The generic GetEnumerator() doesn't appear to get called so I don't quite see the point of having IEnumerable<T> at all. You might as well just derive from IEnumerable :shrug:
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Atom feed reader with support for Code Project.
Taka Muraoka wrote:
Given that this kind of feature is pretty standard in modern languages, I'd say it's perhaps a little more than a developers' convenience.
Just because it's in most modern languages only means it's a popular feature. I think what you will find behind the scenes in the IL is pretty much the same calls if you used a class that implemented IEnumerable or one that didn't. The code generated is still a call to
GetEnumerator
and then calls toGetCurrent
. The fact thatforeach
works withoutIEnumerable
was probably done in order to provide the most flexibility. I wouldn't necessary say that it suggests "someone who doesn't really know what they are doing".Taka Muraoka wrote:
This is the one that really baffles me. The generic GetEnumerator() doesn't appear to get called so I don't quite see the point of having IEnumerable at all.
What are you using to determine that the generic GetEnumerator isn't being called?
Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[Forum Guidelines] [Articles] [Blog]
-
Taka Muraoka wrote:
Given that this kind of feature is pretty standard in modern languages, I'd say it's perhaps a little more than a developers' convenience.
Just because it's in most modern languages only means it's a popular feature. I think what you will find behind the scenes in the IL is pretty much the same calls if you used a class that implemented IEnumerable or one that didn't. The code generated is still a call to
GetEnumerator
and then calls toGetCurrent
. The fact thatforeach
works withoutIEnumerable
was probably done in order to provide the most flexibility. I wouldn't necessary say that it suggests "someone who doesn't really know what they are doing".Taka Muraoka wrote:
This is the one that really baffles me. The generic GetEnumerator() doesn't appear to get called so I don't quite see the point of having IEnumerable at all.
What are you using to determine that the generic GetEnumerator isn't being called?
Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[Forum Guidelines] [Articles] [Blog]
Scott Dorman wrote:
I think what you will find behind the scenes in the IL is pretty much the same calls if you used a class that implemented IEnumerable or one that didn't. The code generated is still a call to GetEnumerator and then calls to GetCurrent.
I haven't really bothered looking too deeply into IL but as a hard-core C++ guy, it certainly doesn't make any sense.
Scott Dorman wrote:
The fact that foreach works without IEnumerable was probably done in order to provide the most flexibility. I wouldn't necessary say that it suggests "someone who doesn't really know what they are doing".
Well, it doesn't really make any sense from a general OO point of view, either :-)
Scott Dorman wrote:
What are you using to determine that the generic GetEnumerator isn't being called?
Just stepping through in the debugger. Modifying it in the code sample in my OP to return null seems to have no ill effects.
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Atom feed reader with support for Code Project.
-
Scott Dorman wrote:
I think what you will find behind the scenes in the IL is pretty much the same calls if you used a class that implemented IEnumerable or one that didn't. The code generated is still a call to GetEnumerator and then calls to GetCurrent.
I haven't really bothered looking too deeply into IL but as a hard-core C++ guy, it certainly doesn't make any sense.
Scott Dorman wrote:
The fact that foreach works without IEnumerable was probably done in order to provide the most flexibility. I wouldn't necessary say that it suggests "someone who doesn't really know what they are doing".
Well, it doesn't really make any sense from a general OO point of view, either :-)
Scott Dorman wrote:
What are you using to determine that the generic GetEnumerator isn't being called?
Just stepping through in the debugger. Modifying it in the code sample in my OP to return null seems to have no ill effects.
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Atom feed reader with support for Code Project.
Taka Muraoka wrote:
I haven't really bothered looking too deeply into IL but as a hard-core C++ guy, it certainly doesn't make any sense.
The IL is much closer to reading assembly than anything else, but it can sometimes reveal some interesting results.
Taka Muraoka wrote:
Well, it doesn't really make any sense from a general OO point of view, either
I can't disagree with this. I was only saying why I think it might have been done that way.
Taka Muraoka wrote:
Just stepping through in the debugger. Modifying it in the code sample in my OP to return null seems to have no ill effects.
What you are seeing is actually correct behavior for your code. You have explicitly defined the
IEnumerable<T>.GetEnumerator()
and implicitly defining theIEnumerable.GetEnumerator()
. SinceIEnumerable.GetEnumerator()
is public (and implicit) this code gets called. There are a few ways to change this so it calls the generic enumerator: 1: Leave your classX
defined as is, and change the call in the foreach loop to readforeach (int n in (IEnumerable<int>)x)
{
System.Console.WriteLine(n);
}2: Leave the foreach call as is, and change
X
to read:class X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X()
{
mList.Add(1); mList.Add(2); mList.Add(3);
}IEnumerator<int> IEnumerable<int>.GetEnumerator() { return mList.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{
return mList.GetEnumerator();
}
}or
class X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X()
{
mList.Add(1); mList.Add(2); mList.Add(3);
}public IEnumerator<int> GetEnumerator() { return mList.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{
return mList.GetEnumerator();
}
}Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[
-
Taka Muraoka wrote:
I haven't really bothered looking too deeply into IL but as a hard-core C++ guy, it certainly doesn't make any sense.
The IL is much closer to reading assembly than anything else, but it can sometimes reveal some interesting results.
Taka Muraoka wrote:
Well, it doesn't really make any sense from a general OO point of view, either
I can't disagree with this. I was only saying why I think it might have been done that way.
Taka Muraoka wrote:
Just stepping through in the debugger. Modifying it in the code sample in my OP to return null seems to have no ill effects.
What you are seeing is actually correct behavior for your code. You have explicitly defined the
IEnumerable<T>.GetEnumerator()
and implicitly defining theIEnumerable.GetEnumerator()
. SinceIEnumerable.GetEnumerator()
is public (and implicit) this code gets called. There are a few ways to change this so it calls the generic enumerator: 1: Leave your classX
defined as is, and change the call in the foreach loop to readforeach (int n in (IEnumerable<int>)x)
{
System.Console.WriteLine(n);
}2: Leave the foreach call as is, and change
X
to read:class X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X()
{
mList.Add(1); mList.Add(2); mList.Add(3);
}IEnumerator<int> IEnumerable<int>.GetEnumerator() { return mList.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{
return mList.GetEnumerator();
}
}or
class X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X()
{
mList.Add(1); mList.Add(2); mList.Add(3);
}public IEnumerator<int> GetEnumerator() { return mList.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{
return mList.GetEnumerator();
}
}Scott. —In just two days, tomorrow will be yesterday. —Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[
Scott Dorman wrote:
1: Leave your class X defined as is, and change the call in the foreach loop to read
foreach (int n in (IEnumerable<int>x)
{
System.Console.WriteLine(n);
}This is obviously kinda ugly. The intent of deriving from IEnumerable<T> is to enforce type-safety and I was hoping that the compiler would be smart enough to realize that X can only be iterated over using int's and nothing else.
Scott Dorman wrote:
2: Leave the foreach call as is, and change X to read:
Both these work but there are still some oddities. The following code compiles but throws a cast exception at runtime, which is what one (or at least I) would expect:
class X
{
private List<int> mList = new List<int>() ;
public X() { mList.Add(1) ; mList.Add(2) ; mList.Add(3) ; }
public IEnumerator GetEnumerator() { return mList.GetEnumerator() ; }
}foreach ( X n in x ) // nb: iterate using an X, not an int
System.Console.WriteLine( n ) ;But using the modified foreach with either of your #2 code samples doesn't even compile ("Cannot convert int to X"). So it seems that there's now no way to get into the non-generic GetEnumerator() :rolleyes: I don't particularly want to but it annoys me that I have to define this method but it never gets used :-) So, it looks like foreach is ignoring whether the object it's iterating over derives from IEnumerable or IEnumerable<T> and just looks for a public GetEnumerator() method (which is kinda lame). But then how does it know which GetEnumerator() to use in your 2a code? Both of them are public in their respective interfaces, and yes, I tried reversing the order they are defined in :-) I was kinda hoping the compiler could infer which one to use by the type being used in the foreach statement but for some reason, it's only allowing the generic one to be used. Oh well, I guess we can just chalk this one up to C# evolving on the fly. Thanks for your help.
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Ato
-
Scott Dorman wrote:
1: Leave your class X defined as is, and change the call in the foreach loop to read
foreach (int n in (IEnumerable<int>x)
{
System.Console.WriteLine(n);
}This is obviously kinda ugly. The intent of deriving from IEnumerable<T> is to enforce type-safety and I was hoping that the compiler would be smart enough to realize that X can only be iterated over using int's and nothing else.
Scott Dorman wrote:
2: Leave the foreach call as is, and change X to read:
Both these work but there are still some oddities. The following code compiles but throws a cast exception at runtime, which is what one (or at least I) would expect:
class X
{
private List<int> mList = new List<int>() ;
public X() { mList.Add(1) ; mList.Add(2) ; mList.Add(3) ; }
public IEnumerator GetEnumerator() { return mList.GetEnumerator() ; }
}foreach ( X n in x ) // nb: iterate using an X, not an int
System.Console.WriteLine( n ) ;But using the modified foreach with either of your #2 code samples doesn't even compile ("Cannot convert int to X"). So it seems that there's now no way to get into the non-generic GetEnumerator() :rolleyes: I don't particularly want to but it annoys me that I have to define this method but it never gets used :-) So, it looks like foreach is ignoring whether the object it's iterating over derives from IEnumerable or IEnumerable<T> and just looks for a public GetEnumerator() method (which is kinda lame). But then how does it know which GetEnumerator() to use in your 2a code? Both of them are public in their respective interfaces, and yes, I tried reversing the order they are defined in :-) I was kinda hoping the compiler could infer which one to use by the type being used in the foreach statement but for some reason, it's only allowing the generic one to be used. Oh well, I guess we can just chalk this one up to C# evolving on the fly. Thanks for your help.
I enjoy occasionally wandering around randomly, and often find that when I do so, I get to where I wanted to be [^]. Awasu 2.3.2 [^]: A free RSS/Ato
Taka Muraoka wrote:
This is obviously kinda ugly. The intent of deriving from IEnumerable is to enforce type-safety and I was hoping that the compiler would be smart enough to realize that X can only be iterated over using int's and nothing else.
I can't disagree with you here. I pointed this out as an option...I never said it was a good one.
Taka Muraoka wrote:
Both these work but there are still some oddities.
What are the oddities?
Taka Muraoka wrote:
But using the modified foreach with either of your #2 code samples doesn't even compile ("Cannot convert int to X"). So it seems that there's now no way to get into the non-generic GetEnumerator()
That is correct. By deriving X from IEnumerable<int> you are effectively telling the type system that when you enumerate over X (by calling GetEnumerator) that you are returning a type of int not X. Isn't that the point of providing the strongly typed version? You (and the compiler) know at compile time what the data type will be, so there is no need for it to call the non-typed version.
Taka Muraoka wrote:
I don't particularly want to but it annoys me that I have to define this method but it never gets used
True, but that's how interfaces work. If you derive from an interface you have to implement all of the methods defined in the contract.
Taka Muraoka wrote:
So, it looks like foreach is ignoring whether the object it's iterating over derives from IEnumerable or IEnumerable<T> and just looks for a public GetEnumerator() method (which is kinda lame). But then how does it know which GetEnumerator() to use in your 2a code? Both of them are public in their respective interfaces, and yes, I tried reversing the order they are defined in I was kinda hoping the compiler could infer which one to use by the type being used in the foreach statement but for some reason, it's only allowing the generic one to be used.
I'm not sure I would say that it's ignoring the interfaces. It knows to use the generic GetEnumerator in the 2a example based on the type defined in the foreach loop. The key is which method is defined explicitly or implicitly. In my 2a example, they are both explicitly defined (notice there is not a
public
keyword). These m -
Taka Muraoka wrote:
This is obviously kinda ugly. The intent of deriving from IEnumerable is to enforce type-safety and I was hoping that the compiler would be smart enough to realize that X can only be iterated over using int's and nothing else.
I can't disagree with you here. I pointed this out as an option...I never said it was a good one.
Taka Muraoka wrote:
Both these work but there are still some oddities.
What are the oddities?
Taka Muraoka wrote:
But using the modified foreach with either of your #2 code samples doesn't even compile ("Cannot convert int to X"). So it seems that there's now no way to get into the non-generic GetEnumerator()
That is correct. By deriving X from IEnumerable<int> you are effectively telling the type system that when you enumerate over X (by calling GetEnumerator) that you are returning a type of int not X. Isn't that the point of providing the strongly typed version? You (and the compiler) know at compile time what the data type will be, so there is no need for it to call the non-typed version.
Taka Muraoka wrote:
I don't particularly want to but it annoys me that I have to define this method but it never gets used
True, but that's how interfaces work. If you derive from an interface you have to implement all of the methods defined in the contract.
Taka Muraoka wrote:
So, it looks like foreach is ignoring whether the object it's iterating over derives from IEnumerable or IEnumerable<T> and just looks for a public GetEnumerator() method (which is kinda lame). But then how does it know which GetEnumerator() to use in your 2a code? Both of them are public in their respective interfaces, and yes, I tried reversing the order they are defined in I was kinda hoping the compiler could infer which one to use by the type being used in the foreach statement but for some reason, it's only allowing the generic one to be used.
I'm not sure I would say that it's ignoring the interfaces. It knows to use the generic GetEnumerator in the 2a example based on the type defined in the foreach loop. The key is which method is defined explicitly or implicitly. In my 2a example, they are both explicitly defined (notice there is not a
public
keyword). These mScott Dorman wrote:
By deriving X from IEnumerable you are effectively telling the type system that when you enumerate over X (by calling GetEnumerator) that you are returning a type of int not X. Isn't that the point of providing the strongly typed version? You (and the compiler) know at compile time what the data type will be, so there is no need for it to call the non-typed version.
That's right, this is the behaviour I want but I was playing with the code you gave because I wanted to get a better handle on what was really going on. I was wondering if the non-generic GetEnumerator() would get called if I tried to iterate using something other than int's. But see my next comment...
Scott Dorman wrote:
If you derive from an interface you have to implement all of the methods defined in the contract.
Maybe the problem is because I'm still thinking in C++. If I write a C++ class that derives from an interface that in turn derives from another interface, my class has to effectively implement two interfaces. This is reinforced by the fact that I have to implement both versions of GetEnumerator(). So it seems to me that I should be able to foreach over an X using int's (via the generic interface) or using object's (via the non-generic interface). But it seems the compiler is unable to infer which one to call and onlys allow access to the generic one, giving a compile error if you try to "use" the non-generic one. Are there rules governing this i.e. why not the other way around?
Scott Dorman wrote:
The key is which method is defined explicitly or implicitly.
Maybe this is the problem, I don't quite understand what the difference is. No such thing exists in C++ - if a method is pure virtual, you have to implement it, end of story. The fact that your 2b code works really makes me think foreach is ignoring the interface hierarchy and just looks for a public GetEnumerator() method. But on the other hand, the generic GetEnumerator() definition in 2b must be equivalent to the one in 2a (i.e. 2 different ways of defining the same thing, otherwise the compiler would be complaining about an un-implemented interface method) but so then, why does declaring it public make a difference?!
Scott Dorman wrote:
When you declare one of them as public you are telling the compiler that this i
-
Scott Dorman wrote:
By deriving X from IEnumerable you are effectively telling the type system that when you enumerate over X (by calling GetEnumerator) that you are returning a type of int not X. Isn't that the point of providing the strongly typed version? You (and the compiler) know at compile time what the data type will be, so there is no need for it to call the non-typed version.
That's right, this is the behaviour I want but I was playing with the code you gave because I wanted to get a better handle on what was really going on. I was wondering if the non-generic GetEnumerator() would get called if I tried to iterate using something other than int's. But see my next comment...
Scott Dorman wrote:
If you derive from an interface you have to implement all of the methods defined in the contract.
Maybe the problem is because I'm still thinking in C++. If I write a C++ class that derives from an interface that in turn derives from another interface, my class has to effectively implement two interfaces. This is reinforced by the fact that I have to implement both versions of GetEnumerator(). So it seems to me that I should be able to foreach over an X using int's (via the generic interface) or using object's (via the non-generic interface). But it seems the compiler is unable to infer which one to call and onlys allow access to the generic one, giving a compile error if you try to "use" the non-generic one. Are there rules governing this i.e. why not the other way around?
Scott Dorman wrote:
The key is which method is defined explicitly or implicitly.
Maybe this is the problem, I don't quite understand what the difference is. No such thing exists in C++ - if a method is pure virtual, you have to implement it, end of story. The fact that your 2b code works really makes me think foreach is ignoring the interface hierarchy and just looks for a public GetEnumerator() method. But on the other hand, the generic GetEnumerator() definition in 2b must be equivalent to the one in 2a (i.e. 2 different ways of defining the same thing, otherwise the compiler would be complaining about an un-implemented interface method) but so then, why does declaring it public make a difference?!
Scott Dorman wrote:
When you declare one of them as public you are telling the compiler that this i
Taka Muraoka wrote:
That's right, this is the behaviour I want but I was playing with the code you gave because I wanted to get a better handle on what was really going on. I was wondering if the non-generic GetEnumerator() would get called if I tried to iterate using something other than int's.
That's probably the best way to understand it. Hopefully it is clearer for you know.
Taka Muraoka wrote:
If I write a C++ class that derives from an interface that in turn derives from another interface, my class has to effectively implement two interfaces. This is reinforced by the fact that I have to implement both versions of GetEnumerator().
This is the same in C#. Your class
X
derives from theIEnumerable<T>
interface, which derives fromIEnumerable
, so you end up having to implement both interfaces. Since each interface defines a method namedGetEnumerator
you must specifically tell the compiler which method is being implemented. Interface declarations in C# do not allow you to specify an access modifier. If it's declared in an interface it's a public method or property. In order to declare the same-named method from two different interfaces you must "inform" the compiler how to tell them apart. Ordinarily when you implement an interface, you simply define the body for the matching method signature:public IEnumerator GetEnumerator() { ... }
Since (for this part of the example) there is only one method named
GetEnumerator
the compiler is able to determine which method implements the interface. However, when you add theIEnumerator<T>
interface you now have an ambiguous match. In order to resolve that ambiguity, one (or both) of the methods must be defined "in terms of their interface". This is what is meant by an explicit implementation. You are explicitly declaring that you are implementing theIEnumerable.GetEnumerator
method. The key thing here is that when you explicitly implement an interface member you cannot specify an access modifier. It's an implied accessibility, which is something I call "psuedo-public". That is, it's a public member but is not visible unless the object is explicitly cast to that interface. So, in my exampleclass X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X() -
Taka Muraoka wrote:
That's right, this is the behaviour I want but I was playing with the code you gave because I wanted to get a better handle on what was really going on. I was wondering if the non-generic GetEnumerator() would get called if I tried to iterate using something other than int's.
That's probably the best way to understand it. Hopefully it is clearer for you know.
Taka Muraoka wrote:
If I write a C++ class that derives from an interface that in turn derives from another interface, my class has to effectively implement two interfaces. This is reinforced by the fact that I have to implement both versions of GetEnumerator().
This is the same in C#. Your class
X
derives from theIEnumerable<T>
interface, which derives fromIEnumerable
, so you end up having to implement both interfaces. Since each interface defines a method namedGetEnumerator
you must specifically tell the compiler which method is being implemented. Interface declarations in C# do not allow you to specify an access modifier. If it's declared in an interface it's a public method or property. In order to declare the same-named method from two different interfaces you must "inform" the compiler how to tell them apart. Ordinarily when you implement an interface, you simply define the body for the matching method signature:public IEnumerator GetEnumerator() { ... }
Since (for this part of the example) there is only one method named
GetEnumerator
the compiler is able to determine which method implements the interface. However, when you add theIEnumerator<T>
interface you now have an ambiguous match. In order to resolve that ambiguity, one (or both) of the methods must be defined "in terms of their interface". This is what is meant by an explicit implementation. You are explicitly declaring that you are implementing theIEnumerable.GetEnumerator
method. The key thing here is that when you explicitly implement an interface member you cannot specify an access modifier. It's an implied accessibility, which is something I call "psuedo-public". That is, it's a public member but is not visible unless the object is explicitly cast to that interface. So, in my exampleclass X : IEnumerable<int>
{
private List<int> mList = new List<int>();
public X()Scott Dorman wrote:
Defining the member in an interface doesn't imply that it is virtual at all.
Aha! This is the root of my confusion. I was assuming that a C# interface was the same as a C++ interface (i.e. a collection of pure virtual/abstract methods). I remembered reading in the O'Reilly "C# Essentials" book that a C# interface was simply a syntactic shortcut for a bunch of abstract members in an abstract class but their exact wording was "similar to" :| The output from the following code gave me a surprise:
interface I
{
void foo( string msg ) ;
}class A : I
{
public void foo( string msg ) { System.Console.WriteLine( msg+" -> A::foo" ) ; }
}class B : A
{
public void foo( string msg ) { System.Console.WriteLine( msg+" -> B::foo" ) ; }
}static void Main()
{
B b = new B() ;
((I)b).foo( "Static type I" ) ;
((A)b).foo( "Static type A" ) ;
((B)b).foo( "Static type B" ) ;
}--- OUTPUT ---
Static type I -> A::foo
Static type A -> A::foo
Static type B -> B::fooIn C++, the B::foo() method would've been called every time. No wonder I thought foreach appeared to be ignoring the IEnumerable's in the inheritance hierarchy. In every other OO language I've worked with, to get access to GetEnumerator() you would have to go through the IEnumerable but in C#, deriving from an interface seems to be little more than a directive to the compiler telling it that certain methods need to defined i.e. it doesn't really affect the class as such (e.g. by causing the layout of the v-table to change, or whatever C# uses). Playing around a little with the definition of B:
class B : A,I
{
void foo( string msg ) { System.Console.WriteLine( msg+" -> B::foo" ) ; }
}--- OUTPUT ---
Static type I -> A::foo
Static type A -> A::foo
Static type B -> A::fooThat's weird, but I think because B::foo() is not public:
class B : A,I
{
public void foo( string msg ) { System.Console.WriteLine( msg+" -> B::foo" ) ; }
}--- OUTPUT ---
Static type I -> B::foo
Static type A -> A::foo
Static type B -> B::fooAnd how about these two:
class B : A,I
{
void foo( string msg ) { System.Console.WriteLine( msg+" -> B::foo" ) ; }
void I.foo( string msg ) { System.Console.WriteLine( msg+" -> B::foo 2" ) ; }
}--- OUTPUT ---
Static type I -> B::foo 2
Static type A -> A::foo
Static type B -> A::fooc