List<T> inheritance

F

Fabio Z

Hi all, I have a classic problem: List<T> and List<X> where X is a class X :
T.
Ok, I know the problem: I cannot cast List<T> on List<X> because also if X
is a T, List<X> is not a List<T>.
What I don't know is the solution :)

Let's see this scenario (that obviously doesn't compile):

// --------------------------
class BaseClass
{ }

class DerivedClass : BaseClass
{ }

class BaseClassCollection : List<BaseClass>
{ }

class DerivedClassCollection : List<DerivedClass>
{ }

class Collector
{
BaseClassCollection _classes = new BaseClassCollection();
public virtual BaseClassCollection Classes
{
get { return _classes; }
}
}

class DerivedCollector : Collector
{
public override DerivedClassCollection Classes
{
get { return _classes; }
}
}
// --------------------------

This is what I would have: more specific classes that work on base classes.
What is the more similar solution?

Thanks!
 
M

Marc Gravell

Typically this is solved with a generic method, e.g.

void SomeMethod(List<T> data) where T : SomeBase {

}

Now you can pass either a List<SomeBase> or a List<SomethingElse> as long as
SomethingElse : SomeBase. Note that in most cases you don't need to specify
the T when calling - i.e.

List<SomethingElse> data = //
SomeMethod(data); // or could use longer SomeMethod<SomethingElse>(data);

Marc
 
C

Christof Nordiek

Shouldn't it be:

void SomeMethod<T>(List<T> data) where T: SomeBase {
}

?

Christof
 
M

Marc Gravell

D'oh! yup ta - I missed a <T>, but in my head I typed it perfectly ;-p
The dangers of coding in notepad and hitting "Send"...

"what he said"

Marc
 
F

Fabio Z

Marc Gravell said:
Typically this is solved with a generic method, e.g.

void SomeMethod(List<T> data) where T : SomeBase {

}

Now you can pass either a List<SomeBase> or a List<SomethingElse> as long
as SomethingElse : SomeBase. Note that in most cases you don't need to
specify the T when calling - i.e.

List<SomethingElse> data = //
SomeMethod(data); // or could use longer SomeMethod<SomethingElse>(data);

mmm... it's interesting, I didn't know this possibility, but I don't
understand how this can be applied to my scenario ;)
where I need to cast a CollectionOfItems to a CollectionOfDerivedItems.

I think the only solution is to use the List<T>.ConvertAll<X>() or
something...
 
M

Marc Gravell

Well, the subject and bit at the top of you post (List<T> and List<X>, with
X : T) is unrelated to the bit at the bottom of your post (BaseCollection
and DerivedCollection, with DerivedCollection : BaseCollection).

The difference is very significant; the first has the contained type in an
inheritance chain, the second has the container in an inheritance chain. I
answered the top 2. Re the latter, you can't change the type in a virtual.
You could, however, have a generic base class which you then inherit from
(which gets us back closer to List<T>), and add methods. You will, however,
run into sever problems trying to work with situations in which the
container type *and* the contained type change. The answer here, then, is
usually to use an interface; for instance

public interface ICollector<T> where T : BaseClass {// or something
T Classes {get;}
}

then you can use generic methods typed to ICollector<T>, and it doesn't
matter what the inheritance is between collectors (or not at all) as long as
they all meet the interface.

Marc
 
M

Marc Gravell

Addditional note: a derived collector can redeclare ("new") methods, or it
can override them; I don't think it can do both at once, and if the original
method is tied to an interface, then anybody using that interface will still
get the old method, not the redeclared one. Likewise anybody using a
variable typed as the base-collection.

Personally I would try to avoid this scenario; maybe have unrelated
collections as necessary - inheritance here is going to make for huge
complexity. Of course, it may be warranted, but I couldn't possibly say
without more info.

Marc
 
C

Christof Nordiek

Marc Gravell said:
Addditional note: a derived collector can redeclare ("new") methods, or it
can override them; I don't think it can do both at once, and if the
original method is tied to an interface, then anybody using that interface
will still get the old method, not the redeclared one.

But you can reimplement that method of the interface. Then calling the
method on the interface will call the new implementation if the value is of
the derived type. But you can't call the baseimplementation of that method
from the new implementation, like one often does in overriding methods.
 
F

Fabio Z

Marc Gravell said:
Well, the subject and bit at the top of you post (List<T> and List<X>,
with X : T) is unrelated to the bit at the bottom of your post
(BaseCollection and DerivedCollection, with DerivedCollection :
BaseCollection).

I never said that DerivedCollection : BaseCollection.
Just the items collected are respectively X and T, where X : T.
 
F

Fabio Z

Personally I would try to avoid this scenario; maybe have unrelated
collections as necessary - inheritance here is going to make for huge
complexity.

yes, also because it doesn't compile :)

I try to explain.
Let's say I have these base classes:

class Item {}

class ItemCollection : List<Item> {}

class Document
{
ItemCollection Items;
}


and the derived ones:

class Word : Item {}

class WordCollection : List<Word> {}
// just to have the Word properties righ
// out of the box, without unboxing each accessed item.

class Letter : Document
{
WordCollection Words;
// or an overriden WordCollection Items;
}



Right now the best solution I've found is

class Letter : Document
{
public WordCollection Words
{
get
{
WordCollection words = new WordCollection ();
foreach (Item item in Items)
{
if (item is Word)
{
words.Add((Word)di);
}
}
return words;
}
}
}

but I'm thinking about abolish the WordCollection and return a Word[].

Thanks
 
M

Marc Gravell

I think the mistake you may be making here is dealing in concrete types too
much.

If all the caller needs to know is that it is an enumerable set of Thing
classes, then just return IEnumerable<Thing>. If it needs to know that it is
a list, then return IList<Thing>; that way, the question of List<Thing>
versus ThingCollection versus Thing[] just disappears, as all three
implement IList<Thing>

In your example, sure the answer here is again generics... e.g. Word : Item,
and ItemCollection<T> where T : Item, then Document has
"ItemCollection<Word> Words"...?

Marc
 
F

Fabio

If all the caller needs to know is that it is an enumerable set of Thing
classes, then just return IEnumerable<Thing>. If it needs to know that it
is a list, then return IList<Thing>; that way, the question of List<Thing>
versus ThingCollection versus Thing[] just disappears, as all three
implement IList<Thing>

mmm... not so clear but...
In your example, sure the answer here is again generics... e.g. Word :
Item, and ItemCollection<T> where T : Item, then Document has
"ItemCollection<Word> Words"...?

This can be the right solution :)

class Item {}

class Document
{
List<Item> Items;
}


and the derived ones:

class Word : Item {}

class WordCollection : List<T> where T : Word {}

class Letter : Document
{
WordCollection Words;
}

it will compile this?
I'll try ;)
 
J

Joanna Carter [TeamB]

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

| class WordCollection : List<T> where T : Word {}

This is a redundant declaration, you might as well just do :

class Letter : Document
{
List<Word> words;
}

Joanna
 
F

Fabio

| class WordCollection : List<T> where T : Word {}

This is a redundant declaration, you might as well just do :

class Letter : Document
{
List<Word> words;
}

Yes, but I don't want to expose a List<T>.
 
A

Anders Borum

Hello!

I wouldn't call it redundant, but it could be implemented differently:

class ItemCollection<T> : IList<T> where T : Item
class WordCollection : ItemCollection<Word> {}

Would allow him to have generic methods with the following signature
pattern:

public void ProcessCollection<T>(ItemCollection<T> arg) where T : Item
{
// use methods defined on types ItemCollection and Item
}

From what I can read from the discussion, the original poster (Fabio Z)
wants to provide a set of concrete (potentially sealed) classes that are
exposed from the API to the client application. Exposing types of type T
(i.e. List<T> is flexible and easy, but not necessarily what you want to
do).

I've used the above pattern when I had a generic inheritance chain, and it
worked really well.
 
J

Joanna Carter [TeamB]

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

| Yes, but I don't want to expose a List<T>.

You're not, you would be using a List<Word> as an internal field, then you
can expose the list however you like. My point is that deriving from a
generic list class just to strictly type it really is a waste of effort,
unless you want to create a constrained derivative like others have
suggested.

Joanna
 
M

Marc Gravell

That works just fine for me as long as it meets the interface and Item is
well-defined; to get the error message you report I need to use:

class ItemCollection : IList<T> where T : Item

So check you haven't lost a <T> somewhere

Marc
 
A

Anders Borum

Fabio, let us know if you found the solution (or got the code snippet I
provided to compile).
 
F

Fabio Z

Fabio, let us know if you found the solution (or got the code snippet I
provided to compile).

I'm sorry, now I tell you :)

In my last post I thinked you erroneously used IList<T> instead of List<T>
and this is why I told that it didn't compile: I used List<T>

I tryed your solution but not tested, because implementing IList<T> I would
implement manually a dozen of methods, and avoid this is a reason for what I
used inheritance.

If I should say it all, I should implement an interface, but not IList<T>,
because I need a object that doesn't publish Add, Remove, Clear that are
managed internally (I'm thinking about IEnumerable<T>).

Right now, I'm using the solution I told in my post of 23-nov at 17:17

Thanks
 

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