Casting generic collections & inheritance

M

Marc Gravell

(just realised - this is in fact the same as Jon's solution... d'oh!)

A minor aside (that I didn't expect to work) is that even though the
function is defined as SomeFunc<A>(param), you don't always actually need to
specify A - it derives it from the parameter... very neat.

Marc

Marc Gravell said:
For reference, I found a partial solution to this involving generics (i.e.
making Func<T>(IEnumerable<T>) where T : A); in the following, the main
crux is that A shouldn't need to have any knowledge of B; this then allows
me to pass a Collection<B> or List<B> to a function that expects
IEnumerable<A>

Marc

public class A {
public readonly string Name;
public A(string name) { Name = name; }

public static void TestList<T>(IEnumerable<T> items)
where T : A {

foreach (A item in items) {
Console.WriteLine(item.Name);
}
}
}
public class B : A {
public readonly int ID;
public B(string name, int id) : base(name) { ID = id; }
}
public static class Program {

public static void Main() {
List<B> list = new List<B>();
list.Add(new B("Fred", 1));
list.Add(new B("Barney", 2));
list.Add(new B("Wilma", 3));
list.Add(new B("Betty", 4));
A.TestList<B>(list);
Console.ReadLine();
}
}

Marc Gravell said:
Random aside: the logic I can see here makes perfect sence for anything
that allows addition etc - however, some casts could be sensible defined.
In particular (and most usefully) couldn't a List<X> quite safely
implement IEnumerable<T> for each T in X's inheritance tree?

Unless I am missing something this fails at the moment, but would be very
useful (to me, at least).

Example (which doesn't work) below.

Any thoughts? Am I missing an obvious "this wouldn't work because..."?

public class A {
public readonly string Name; // [sic] for brevity / demo
public A(string name) { name = Name; }
}
public class B : A {
public readonly int ID;
public B(string name, int id) : base(name) { id = ID; }
}
public class Program {

public static void Main() {
List<B> list = new List<B>();
list.Add(new B("Fred", 1));
list.Add(new B("Barney", 2));
list.Add(new B("Wilma", 3));
list.Add(new B("Betty", 4));
// *** following fails (as per spec) at runtime
// (would be nice if a: worked, and b: didn't need cast)
IEnumerable<A> items = (IEnumerable<A>) list;
foreach(A item in items) {
Console.WriteLine(item.Name);
}
Console.ReadLine();

}
}

Jon Skeet said:
Michael Bray wrote:
No it's not. If you could treat a Queue<string> as a Queue<object>
then you could add a new object() to it - and suddenly anything
viewing it
as a Queue<string> would be in trouble.

Jon, this is backwards to what Adam is saying... Its not that we want
to insert objects into the List<string> but we (well, me at least)
would
like to be able to, for example, pass a List<string> to a function that
takes a List<object>.

That's *exactly* what I said - if you could pass a List<string> to
something expecting List<object> that's treating a List<string> as a
List<object>, isn't it?

The method you would pass the list to could do:

theList.Add (new object());

What would you expect to happen at that point? The caller is still
expecting every element to be a string, so either you allow it and they
blow up when they use the element as a string, or you blow up at the
call to Add (which is what array covariance effectively does).

So for example, something like this is not possible, but it sure would
be nice:

<snip>

Although after writing this example, I can see where the problem is...
the ShowNames function in this case doesn't modify the list, but that's
only because of the nature of the function. Some other function that
took a List<Person> might want to modify it and add a Person object
which would violate the definition of the List<Farmer>. So I guess it
makes sense after all! :)

Exactly. That's what I was trying to say. Just out of interest, how
could I have expressed it more clearly? It's a fairly common question -
I'd like to know how to address it better. Would including source code
have helped.

For anyone else following this thread, though, here's two nice ways to
convert (for Lists, at least from one type to another). Although I
will
add that in light of the above, this should only be used for temporary
conversion in the case that you want to pass a List<DerivedClass> to a
function that processes a List<BaseClass> in a read-only fashion.

otherPeople = new List<Person>(farmers.ToArray());

otherPeople = farmers.ConvertAll(new Converter<Farmer, Person>(
delegate(Farmer f) { return (Person)f; }
));

I haven't tried, but I'm sure there's an easy way of writing a generic
converter that can always do an identity conversion (i.e. convert from
S to T where S : T).

The second one actually is more generic and doesn't even require that
the two classes be a base/derived class pair. In the example given,
however, this is the case, which is why the anonymous method code is
just a simple cast from Farmer to Person.

In fact, the cast isn't even needed, is it?

Jon
 
J

Jon Skeet [C# MVP]

Marc Gravell said:
Random aside: the logic I can see here makes perfect sence for anything that
allows addition etc - however, some casts could be sensible defined. In
particular (and most usefully) couldn't a List<X> quite safely implement
IEnumerable<T> for each T in X's inheritance tree?

Yes, quite possibly. However, I suspect that codifying the rules for
this kind of thing would be pretty horrendous. It shouldn't be too hard
to write something which implements IEnumerable<T> given an
IEnumerable<X> where T : X, if you find yourself doing this often.
 
M

Marc Gravell

Yes; after finding this "{base}<X>" ==> "{base}<T> where T : X" solution
(which, to be fair, you posted before I mentioned it), I now 100% agree;
there is a simple, neat solution to this *specific* problem that also
addresses the more complex "Add" scenario, so I agree: stick to one route to
solve the same problems.

Marc
 

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