IEnumerator/IEnumurable

J

Jon Slaughter

I do not understand how to implement these two classes. I created a tree
like structure and I implemented the ability to enumerate over it but I'm
mixing generic and nongeneric classes:

This is the class that works:

public class RTree<T> : IEnumerable
{
public List<RTree<T>> Nodes; // Container for Nodes
public T Value; // Value at this node
public RTree<T> Parent; // Contains the parent node

public RTree() { Nodes = new List<RTree<T>>(); }
public void Add(RTree<T> node)
{
Nodes.Add(node);
node.Parent = this;
}

public virtual IEnumerator GetEnumerator()
{
return new TreeIterator(this);
}

public class TreeIterator : IEnumerator
{
private RTree<T> tree;
private int index = -1;

public TreeIterator(RTree<T> t)
{
tree = t;
}

public bool MoveNext()
{
index++;
if (index < tree.Nodes.Count)
return true;
return false;
}

public object Current
{
get
{
if (index <= -1)
throw new InvalidOperationException();
return tree.Nodes[index];
}
}

public void Reset() { index = -1; }
}
}

But if I do not add
using System.Collections; and keep System.Collections.Generic then I get
errors about Current returning object and if I change that then I get the
error about it not returning object.

Do I need to implement two subclasses, one for the generic version and one
for the non-generic?

If so, is there any benefit to using one over the other? Basically whats the
point of having both a non-generic IEnumerator and a generic one? Also, is
one prefered over the other?

Thanks,
Jon
 
M

Marc Gravell

If so, is there any benefit to using one over the other? Basically whats the
point of having both a non-generic IEnumerator and a generic one? Also, is
one prefered over the other?

Generally the generic form is preferred, since it can offer more
compile time checks - but the untyped form might be used by some UI
bindings (that don't care about the type). However, usually share an
implementation - for example:

class FooSet : IEnumerable<Foo>
{
// implicit generic GetEnumerator
public IEnumerator<Foo> GetEnumerator()
{
// TODO (see below)
}
// explicit untyped GetEnumerator (no new implementation)
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Do I need to implement two subclasses, one for the generic version and one
for the non-generic?

No; for starters, the interfaces extend eachother; IEnumerable<T> :
IEnumerable, and IEnumerator<T> : IEnumerator - so a class that offers
the generic form *must* still offer the untyped form. If you are
simply walking over an inner collection (since you seem to be) then
you can just use the enumerator of that inner list:

public IEnumerator<Foo> GetEnumerator()
{
return tree.Nodes.GetEnumerator();
}

If you are doing something more exciting, you can use "yield" syntax
and let the compiler take the strain:

public IEnumerator<Foo> GetEnumerator()
{ // union of 2 lists
foreach(Foo foo in firstList) {
yield return foo;
}
foreach(Foo foo in secondList) {
yield return foo;
}
}


Apart from anything else, the state-machine that the compiler builds
deals with better cleanup etc than you really want to have to code by
hand - i.e. if I had any "finally" code it would get run correctly
when the enumerator is either walked to the end, or disposed.

Marc
 
M

Marc Gravell

Not an explanation, but a statement...
In order to meet an interface definition, the signature must be an /exact/
match; but yes - in this (and many other) case(s), there is an implicit
conversion that could do the job.

Maybe C# could one day include automatic explicit implementations when there
is a legal implicit conversion (of all args/ret). Using a different example
(below), the explicit IFoo.Bar might have been written for us; then again,
it wasn't much effort to do... and it keeps things simple "as is"... and
there are probably some covariance/contravariance issues to think about, but
I'll be honest: trying to think of such makes my head hurt (especially at
this time of day...)

Marc

interface IFoo {
object Bar(int i);
}
class Foo: IFoo
{
public int Bar(object i) { return 4; }
object IFoo.Bar(int i)
{ // could this theoretically be compiler-generated?
return Bar(i);
}
}
 
M

Marc Gravell

One other thought on B : A - an obvious case where this is an *immediate*
issue is where B is a struct and A is object or an interface. The compiler
is going to want to pass a reference over the boundary (for "object"), which
is very unlikely to even be the same size as the struct, let alone
meaningful. If we exclude the object/struct issue (i.e. we assume B and A
are both reference-types or interfaces) then it is a trickier question...

Marc
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
[...]
No; for starters, the interfaces extend eachother; IEnumerable<T> :
IEnumerable, and IEnumerator<T> : IEnumerator - so a class that offers
the generic form *must* still offer the untyped form.

Speaking of which...I still haven't really figured out why that is.

I'm sure it has something to do with how the type rules work. But I can't
put my finger on it. If IEnumerator<T> implements IEnumerator, then why
is haveing a single method that returns an IEnumerator<T> not sufficient
with respect to also implementing IEnumerable?

Or, put another way, taking the iterator stuff out of the equation: if
class B inherits class A, and an interface I requires a particular method
that returns an A, why is a method that returns a B but is otherwise
correct (i.e. correct name and parameters) not a legal implementation of
that interface I?

The concept you're talking about is called covariance of return type.
The classic example is Clone:

public interface ICloneable
{
object Clone();
}

which has to be implemented *exactly* like that - this isn't allowed:

public class Foo : ICloneable
{
// Not allowed!
public Foo Clone()
{
...
}
}

The reverse, with parameters, is called contravariance. For instance,
given an interface:

public interface IAddable
{
void Add(string value);
}

the follow implementation isn't allowed:

public class Bar : IAddable
{
// Not allowed!
public void Add (object value)
{
...
}
}


To be honest, I'm not sure why it's not allowed. I know that it
certainly *wasn't* in Java until Java 1.5, but it is now.

I don't know whether the CLR itself supports it. I wouldn't be
surprised either way, given that it supports covariance/contravariance
for generics (to a limited extent) and delegates.

I suspect that allowing it could open up some interesting corner cases,
but would generally be a useful thing.
 
B

Barry Kelly

Jon said:
The concept you're talking about is called covariance of return type.
I don't know whether the CLR itself supports it. I wouldn't be
surprised either way, given that it supports covariance/contravariance
for generics (to a limited extent) and delegates.

It doesn't, at least for super/subclass relationships, like C++ does.

-- Barry
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
[...]
I suspect that allowing it could open up some interesting corner cases,
but would generally be a useful thing.

Hmm. I guess I was hoping to see an example of one of those "interesting
corner cases". I assumed they existed and avoiding them was why this
wasn't supported.

But from yours and Marc's answers, I have the impression this may be as
much an arbitrary "let's keep things simple" sort of decision as it was
"here's something that would break if we supported it" decision. Or at
least if it's the latter, it's not quite as obvious a problem as
covariance with generics would be (that one seems pretty easy to
explain...I was hoping this would be something similar).

I don't know about Marc, but certainly my view is "there may be some
interesting issues which are beyond my understanding". It could also be
as straightforward as "it would make the CLR implementation harder,
there wasn't enough time before v1.0, and we've always had more
important things to concentrate on since then"!
Anyway, thanks very much for both of your insights. I guess I didn't get
the "it should be obvious" answer I was hoping for, but on the other hand
I guess I can take comfort in the fact that if there's an obvious answer,
I'm not the only one not seeing it. :)

:)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top