Casting generic collections & inheritance

A

Adam Clauss

OK, I have class A defined as follows:

class A
{
A(Queue<B> queue) { ... }
}

Now, I then have a subclass of both classes A and B. The subclass of A
(SubA), more specifically is passed a Queue<SubB>.

class SubA
{
SubA(Queue<SubB> queue)
: base (queue)
{ ... }
}

This, at first glance appears to be correct, because if a queue contains all
elements of type SubB, then those by definition are also of type B. So, the
types should be compatible.

However, upon compiling, I get an error saying it cannot convert the types.
Further, Queue does not have a Convert() method, as the List does. Any
suggestions on how to solve this?

Thanks
 
J

Joanna Carter [TeamB]

"Adam Clauss" <[email protected]> a écrit dans le message de (e-mail address removed)...

| This, at first glance appears to be correct, because if a queue contains
all
| elements of type SubB, then those by definition are also of type B. So,
the
| types should be compatible.
|
| However, upon compiling, I get an error saying it cannot convert the
types.
| Further, Queue does not have a Convert() method, as the List does. Any
| suggestions on how to solve this?

If you inherited from Queue<T>, then you would be able to assign sub-classes
of Queue<T>, but Queue<SubB> does not inherit from Queue<B>, it is regarded
as a separate class.

inheritance of generic classes relies on the generic type, not the parameter
type.

Joanna
 
A

Adam Clauss

Hmm... you would think the compiler would be able to make that distinction -
that a Queue<SubB> is also a Queue<B>.
 
M

Michael Bray

Hmm... you would think the compiler would be able to make that
distinction - that a Queue<SubB> is also a Queue<B>.

I had been wondering about that too... Although it can't cast directly,
you can add each item to a new list..

Can any of the smarter people here explain why this type of generic
inheritance wasn't included in the language/compiler?

Here's some sample code... Note in particular the two comments about
halfway down. (Sorry for the bad wrapping - is there a better way to
do this? Should I just attach the file in the future??)

using System;
using System.Collections.Generic;
using System.Text;

namespace GenericTypeInheritanceTest
{
class Program
{
static void Main(string[] args)
{
List<Person> people = new List<Person>();

Person mike = new Person("mike");
Farmer john = new Farmer("john", 10);
Farmer bob = new Farmer("bob", 20);

people.Add(mike);
people.Add(john);

Console.WriteLine("People:");
foreach (Person p in people) Console.WriteLine
(p.ToString());

List<Farmer> farmers = new List<Farmer>();
farmers.Add(john);
farmers.Add(bob);
Console.WriteLine();
Console.WriteLine("Farmers:");
foreach (Farmer f in farmers) Console.WriteLine
(f.ToString());

// Doesn't compile
// List<Person> otherPeople = new List<Person>
(farmers);
List<Person> otherPeople = new List<Person>();

// But this works just like you would expect!

foreach (Farmer f in farmers) otherPeople.Add(f);
Console.WriteLine();
Console.WriteLine("Other People:");
foreach (Person p in otherPeople) Console.WriteLine
(p.ToString());

Console.ReadLine();
}
}

public class Person
{
public Person(string name)
{
this.name = name;
}

public string name;

public override string ToString()
{
return "Name=" + name;
}
}

public class Farmer : Person
{
public Farmer(string name, int acresOfFarmLand)
: base(name)
{
this.acresOfFarmLand = acresOfFarmLand;
}

int acresOfFarmLand;

public override string ToString()
{
return base.ToString() + "; Acres=" +
acresOfFarmLand.ToString();
}
}
}
 
M

Michael Bray

Can any of the smarter people here explain why this type of generic
inheritance wasn't included in the language/compiler?

Let me say before this gets too far... I understand WHY it doesn't
compile... So please no discussion about how a List<Person> isn't the
same as a List<Farmer> or vice-versa... I want to know why support for
this isn't included in the language/compiler... :)

-mdb
 
J

Jon Skeet [C# MVP]

Adam Clauss said:
Hmm... you would think the compiler would be able to make that distinction -
that a Queue<SubB> is also a Queue<B>.

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.

I guess the reverse question is why covariance *is* allowed for
arrays...
 
J

Jon Skeet [C# MVP]

Michael Bray said:
I had been wondering about that too... Although it can't cast directly,
you can add each item to a new list..

Can any of the smarter people here explain why this type of generic
inheritance wasn't included in the language/compiler?

Well, the C# spec puts it like this:

<quote>
No special conversions exist between constructed reference types other
than those described in §15. In particular, unlike array types,
constructed reference types do not exhibit =3Fco-variant=3F conversions..

This means that a type List<B> has no conversion (either implicit or
explicit) to List<A> even if B is derived from A. Likewise, no
conversion exists from List<B> to List<object>.

[Note: The rationale for this is simple: if a conversion to List<A> is
permitted, then apparently, one can store values of type A into the
list. However, this would break the invariant that every object in a
list of type List<B> is always a value of type B, or else unexpected
failures can occur when assigning into collection classes. end note]
</quote>
 
M

Michael Bray

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>.

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

public class Person { ... }
public class Farmer : Person { ... }
public void ShowNames(List<Person> people)
{
foreach(Person p in people) Console.WriteLine(p.Name);
}
public void ShowFarmerNames(List<Farmer> farmers)
{
// Can't do this:
// ShowNames(farmers);

// Have to do this:
foreach(Farmer f in farmers) Console.WriteLine(f.Name);
}


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! :)

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; }
));

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.

-mdb
 
J

Jon Skeet [C# MVP]

Michael said:
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:

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
 
A

Adam Clauss

Jon Skeet said:
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?

Thanks to everyone for their comments. It now makes sense to me WHY it does
not work. Additionally, I have come up with what I think is a rather good
solution to it.

First, the problem with copying the list and adding the elements to a NEW
queue, is that I needed both parent and subclass to be referencing the same
queue - not a COPY of a queue.

What allowed this to work is - make the base A<> class generic.

class A<T> : where T: B
{
A(Queue<T> queue) { ... }
}

class SubA : A<SubB>
{
SubA(Queue<SubB> queue)
: base (queue)
{ ... }
}

This allows A to make it's casts to B when necessary, but still disallows
adding invalid objects to the queue. Jon, any flaws you see in what I've
done?
 
M

Michael Bray

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?

Yeah I guess that its a matter of semantical grouping in English... in
other words, and keeping with my 'Farmer is a Person' definition,

A "List of (Farmers)" is most definitely a "List of (People)",

BUT

A "(List of Farmers)" is NOT a "(List of People)".

The phrasing just gets in the way. Who's up for speaking in binary?
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.

I think you phrased it as best you can from a technical perspective. I
think the problem is that people who are new to generics (such as
In fact, the cast isn't even needed, is it?

True... I guess I just put it there (unintentionally) to be clear that
I was returning a Person object (self-documenting code is still in
fashion, no?).

FWIW, I guess I could say at this point that it would still be nice to
be able to call ShowNames(List<People>) but there would have to be a
restriction that ShowNames (and any other functions it calls) can't
modify the list, eg something like ShowNames(readonly List<People>).
Wishful thinking I guess.

-mdb
 
M

Michael Bray

Would including source code
have helped.

Definitely. In fact, writing the source code is what let me understand
what the real problem was... Just make sure that the function is one that
modifies the list to show where the problem is.

-mdb
 
J

Jon Skeet [C# MVP]

Adam said:
Thanks to everyone for their comments. It now makes sense to me WHY it does
not work. Additionally, I have come up with what I think is a rather good
solution to it.

First, the problem with copying the list and adding the elements to a NEW
queue, is that I needed both parent and subclass to be referencing the same
queue - not a COPY of a queue.

What allowed this to work is - make the base A<> class generic.

This allows A to make it's casts to B when necessary, but still disallows
adding invalid objects to the queue. Jon, any flaws you see in what I've
done?

Not from the above - but do you definitely need a new class (SubA)? Is
it adding different capabilities to A? Don't forget you can just do:

A<SubB> x = new A(new Queue<SubB>());

in your client code.

I'm not saying you don't need the extra class - just checking that
you've thought of it.

Jon
 
J

Jon Skeet [C# MVP]

Michael said:
Definitely. In fact, writing the source code is what let me understand
what the real problem was... Just make sure that the function is one that
modifies the list to show where the problem is.

Righto. I'll try to get it into the FAQ fairly soon... (at least my web
page, and possibly the MSDN one too.)

Jon
 
J

Jon Skeet [C# MVP]

Michael said:
FWIW, I guess I could say at this point that it would still be nice to
be able to call ShowNames(List<People>) but there would have to be a
restriction that ShowNames (and any other functions it calls) can't
modify the list, eg something like ShowNames(readonly List<People>).
Wishful thinking I guess.

What you *could* do is

void ShowNames<T>(List <T>) where T : Person

(I believe, anyway.)

Jon
 
A

Adam Clauss

Yes - SubA adds additional functionality. There are several overrides (and
completely new methods). I didn't include them here, didn't feel they were
completely relevant to the issue w/ generic and inheritance.

Thanks
 
J

Jon Skeet [C# MVP]

Michael Bray said:
You are absolutely right! That works great!

Goodo :)
BTW I have attached the test source code I used... is this a better way
to provide code samples to the newsgroup? I assume that in the least it
will handle the wrapping problem... Let me know.

It's generally simpler if you just include the code inline, having done
any appropriate wrapping to make the lines less than 80 characters.
It's much easier to see what's going on that way, IMO.
 
M

Marc Gravell

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();

}
}
 
M

Marc Gravell

minor typo - doesn't affect my point... ctor code should be reversed
(currently sets variable to the field)...

also - obviously it would have to use explicit implementation (rather than
public implementation) for the different Ts, but IIRC this isn't a problem
and works currently.

Marc

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:
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).




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.


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).


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

Jon
 
M

Marc Gravell

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:
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).




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.


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).


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

Jon
 

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