Need help with co/contravariance and generic lists :s
-
Hi guys, I need to find a way around a problem so that I can have an array containing lists of different types, where those types are all derived from the same class. Unfortunately I'm at the end of my knowledge of covariance etc so any help would be greatly appreciated. To explain better: I have a class ('class X') which has several lists in it. The lists contain different types, but these types all derive from the same abstract class. I need to be able to iterate through all items (i.e. across and within the lists) as though all the lists were one. For various reasons, the lists must remain as separate objects. Furthermore, 'Class X' is inherited, and derived classes have additional lists... My solution was to create an array containing these lists (which makes iterating with an unknown number of lists very easy), but I hit problems very quickly (e.g. see code below). Can anyone show me how to do something like this, or find a more elegant way to do it? Here's a much smaller, simple example of the kind of thing I'm trying to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace ConsoleApplication1
{
abstract class A
{ }class B : A { } class C : A { } class ClassX { static void DoStuff() { List<B> listB = new List<B>(); List<C> listC = new List<C>(); List<A>\[\] arrayOfLists = new List<A>\[2\]; arrayOfLists\[0\] = listB;//Error 1 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.B>' arrayOfLists\[1\] = listC;//Error 2 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.C>' arrayOfLists\[0\] = listB.Cast<A>().ToList();//Subsequent changes in list B are not seen an arrayoflists because a new object is created List<List<A>> listOfLists = new List<List<A>>(); listOfLists.Add(listB);//Error 4 Argument 1: cannot convert from 'System.Collections.Generic.List<ConsoleApplication1.B>' to 'System.Collections.Generic.List<ConsoleApplication1.A>' } }
}
Thanks! Lee
-
Hi guys, I need to find a way around a problem so that I can have an array containing lists of different types, where those types are all derived from the same class. Unfortunately I'm at the end of my knowledge of covariance etc so any help would be greatly appreciated. To explain better: I have a class ('class X') which has several lists in it. The lists contain different types, but these types all derive from the same abstract class. I need to be able to iterate through all items (i.e. across and within the lists) as though all the lists were one. For various reasons, the lists must remain as separate objects. Furthermore, 'Class X' is inherited, and derived classes have additional lists... My solution was to create an array containing these lists (which makes iterating with an unknown number of lists very easy), but I hit problems very quickly (e.g. see code below). Can anyone show me how to do something like this, or find a more elegant way to do it? Here's a much smaller, simple example of the kind of thing I'm trying to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace ConsoleApplication1
{
abstract class A
{ }class B : A { } class C : A { } class ClassX { static void DoStuff() { List<B> listB = new List<B>(); List<C> listC = new List<C>(); List<A>\[\] arrayOfLists = new List<A>\[2\]; arrayOfLists\[0\] = listB;//Error 1 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.B>' arrayOfLists\[1\] = listC;//Error 2 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.C>' arrayOfLists\[0\] = listB.Cast<A>().ToList();//Subsequent changes in list B are not seen an arrayoflists because a new object is created List<List<A>> listOfLists = new List<List<A>>(); listOfLists.Add(listB);//Error 4 Argument 1: cannot convert from 'System.Collections.Generic.List<ConsoleApplication1.B>' to 'System.Collections.Generic.List<ConsoleApplication1.A>' } }
}
Thanks! Lee
The List(T) class is neither covariant nor contravariant. Here is why: If you have a list of class A, you can't cast it to a list of class B (derived from A), because not all instances of class A are also instances of class B. This is obvious. Now the other way: why is it impossible to cast a list of clas B (derived from A) to a list of class A? Because then you could add an item of class C (derived from A) to the list, although it is actually a list of B. If you only need to iterate throught all the items (thus the full list can be read-only), then you can create a custom iteration logic by implementing the IEnumerable(T) and IEnumerator(T) interfaces, that's probably what I would do... Instead of adding a new List(A) to the class, create a method that returns IEnumerable(A) and implement the iteration logic.
-
The List(T) class is neither covariant nor contravariant. Here is why: If you have a list of class A, you can't cast it to a list of class B (derived from A), because not all instances of class A are also instances of class B. This is obvious. Now the other way: why is it impossible to cast a list of clas B (derived from A) to a list of class A? Because then you could add an item of class C (derived from A) to the list, although it is actually a list of B. If you only need to iterate throught all the items (thus the full list can be read-only), then you can create a custom iteration logic by implementing the IEnumerable(T) and IEnumerator(T) interfaces, that's probably what I would do... Instead of adding a new List(A) to the class, create a method that returns IEnumerable(A) and implement the iteration logic.
Hey Kubajzz, thanks for your reply. I understand what you've said. Problem is that kind of jump-to-the-next-list iteration logic makes the whole thing very messy when the List**, List, etc lists are not in some sort of collection (i.e. where it is obvious as to which list to jump to without needing special programming). It also raises the problem that every class that subsequently derives from 'ClassX' requires a decent amount of overriding/patching if they add new lists (e.g. List, List). My project has several deriving classes, and values being entirely 'readonly' defeats the purpose of the whole class :( Is there not an easy way to collect these lists together in some sort of collection? Thanks heaps for your time
modified on Monday, September 6, 2010 6:52 AM
**
-
Hi guys, I need to find a way around a problem so that I can have an array containing lists of different types, where those types are all derived from the same class. Unfortunately I'm at the end of my knowledge of covariance etc so any help would be greatly appreciated. To explain better: I have a class ('class X') which has several lists in it. The lists contain different types, but these types all derive from the same abstract class. I need to be able to iterate through all items (i.e. across and within the lists) as though all the lists were one. For various reasons, the lists must remain as separate objects. Furthermore, 'Class X' is inherited, and derived classes have additional lists... My solution was to create an array containing these lists (which makes iterating with an unknown number of lists very easy), but I hit problems very quickly (e.g. see code below). Can anyone show me how to do something like this, or find a more elegant way to do it? Here's a much smaller, simple example of the kind of thing I'm trying to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace ConsoleApplication1
{
abstract class A
{ }class B : A { } class C : A { } class ClassX { static void DoStuff() { List<B> listB = new List<B>(); List<C> listC = new List<C>(); List<A>\[\] arrayOfLists = new List<A>\[2\]; arrayOfLists\[0\] = listB;//Error 1 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.B>' arrayOfLists\[1\] = listC;//Error 2 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.C>' arrayOfLists\[0\] = listB.Cast<A>().ToList();//Subsequent changes in list B are not seen an arrayoflists because a new object is created List<List<A>> listOfLists = new List<List<A>>(); listOfLists.Add(listB);//Error 4 Argument 1: cannot convert from 'System.Collections.Generic.List<ConsoleApplication1.B>' to 'System.Collections.Generic.List<ConsoleApplication1.A>' } }
}
Thanks! Lee
As has been explained, it can't be done with an implicit or explicit cast. Here is a little method to do it for you.
public static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList)
where TDerived : TBase
{
return derivedList.ConvertAll<TBase>(
new Converter<TDerived, TBase>(delegate(TDerived derived)
{
return derived;
}));
}To use:
List<Base> listBase = DerivedConverter<Base, Derived>(listDerived);
where
Base
is the base class andDerived
is a class derived fromBase
.Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Hey Kubajzz, thanks for your reply. I understand what you've said. Problem is that kind of jump-to-the-next-list iteration logic makes the whole thing very messy when the List**, List, etc lists are not in some sort of collection (i.e. where it is obvious as to which list to jump to without needing special programming). It also raises the problem that every class that subsequently derives from 'ClassX' requires a decent amount of overriding/patching if they add new lists (e.g. List, List). My project has several deriving classes, and values being entirely 'readonly' defeats the purpose of the whole class :( Is there not an easy way to collect these lists together in some sort of collection? Thanks heaps for your time
modified on Monday, September 6, 2010 6:52 AM
**
Lee Reid wrote:
My project has several deriving classes, and values being entirely 'readonly' defeats the purpose of the whole class
I thought you only needed the complete list for iterating through all lists, thus a read-only iterator (such as IEnumerator) could be enough. IEnumerable(T) can be implemented in a very clean way without the need of any "special programming". See the following example:
class A {}
class B : A {}
class C : A {}
class MyClass {
private List<B> listB;
private List<C> listC;public MyClass() {
// Initialize lists...
}public IEnumerable<A> GetAllItems() {
// Get each list in this class as IEnumerable<A>
IEnumerable<A> b = listB;
IEnumerable<A> c = listC;return b.Concat(c);
}
}Yes, it is this simple. Note that the GetAllItems method could easily be 1-line long. You can implement some logic to get all the lists in the current class as an array of IEnumerable<A> for easy overriding, it's not more than 1 or 2 very short methods... I don't think this is messy, nor does it require any "special programming" and I highly doubt there is easier and simpler solution. The one and only question is: does IEnumerable provide enough functionality for your application?
-
As has been explained, it can't be done with an implicit or explicit cast. Here is a little method to do it for you.
public static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList)
where TDerived : TBase
{
return derivedList.ConvertAll<TBase>(
new Converter<TDerived, TBase>(delegate(TDerived derived)
{
return derived;
}));
}To use:
List<Base> listBase = DerivedConverter<Base, Derived>(listDerived);
where
Base
is the base class andDerived
is a class derived fromBase
.Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)Cheers Dave, From what I understand unfortunately this seems to make a copy of the original - meaning that a list<B> is not actually contained within a List, but rather its contents are copied across. (Or am I mistaken??)
List<B> listB = new List<B>();
List<C> listC = new List<C>();List<A>[] arrayOfLists = new List<A>[2];
arrayOfLists[0] = DerivedConverter<A, B>(listB);//listB count = 0, arrayOfLists[0].count = 0
listB.Add(new B());
//listB count = 1, but arrayOfLists[0].count = 0. I would want them BOTH to have counts of 1
If i create a list<B> and a list<A>, changes made to listB need to be seen in listA. Sorry for the lack of clarity :(
-
Cheers Dave, From what I understand unfortunately this seems to make a copy of the original - meaning that a list<B> is not actually contained within a List, but rather its contents are copied across. (Or am I mistaken??)
List<B> listB = new List<B>();
List<C> listC = new List<C>();List<A>[] arrayOfLists = new List<A>[2];
arrayOfLists[0] = DerivedConverter<A, B>(listB);//listB count = 0, arrayOfLists[0].count = 0
listB.Add(new B());
//listB count = 1, but arrayOfLists[0].count = 0. I would want them BOTH to have counts of 1
If i create a list<B> and a list<A>, changes made to listB need to be seen in listA. Sorry for the lack of clarity :(
Sort of. The item references are copied so any changes to an element in
listB
will be reflected inlistA
. Test Code:using System;
using System.Collections.Generic;class Program
{
static void Main(string[] args)
{
Derived derived1 = new Derived(1);
Derived derived2 = new Derived(2);
List<Derived> listDerived = new List<Derived>(new Derived[] { derived1, derived2 });
// Create Base list
List<Base> listBase = DerivedConverter<Base, Derived>(listDerived);
// Make change to derived list
listDerived[0].ID = 0;
for (int i = 0; i < listBase.Count; i++)
{
Console.WriteLine("{0} {1}",
object.ReferenceEquals(listBase[i], listDerived[i]),
listBase[i].ID);
}
Console.ReadKey();
}
public static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList)
where TDerived : TBase
{
return derivedList.ConvertAll<TBase>(
new Converter<TDerived, TBase>(delegate(TDerived derived)
{
return derived;
}));
}
}public class Base
{
public Base(int id)
{
ID = id;
}
public int ID { get; set; }
}public class Derived : Base
{
public Derived(int id)
: base(id)
{ }
}If you add or remove items to
listB
then that won't be reflected inlistA
unless you rebuild it dynamically.Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Cheers Dave, From what I understand unfortunately this seems to make a copy of the original - meaning that a list<B> is not actually contained within a List, but rather its contents are copied across. (Or am I mistaken??)
List<B> listB = new List<B>();
List<C> listC = new List<C>();List<A>[] arrayOfLists = new List<A>[2];
arrayOfLists[0] = DerivedConverter<A, B>(listB);//listB count = 0, arrayOfLists[0].count = 0
listB.Add(new B());
//listB count = 1, but arrayOfLists[0].count = 0. I would want them BOTH to have counts of 1
If i create a list<B> and a list<A>, changes made to listB need to be seen in listA. Sorry for the lack of clarity :(
Perhaps something like this will help. This implementation doesn't allow you to add items to the base list, I don't know if that is a requirement or not. This won't be particularly efficient but should work (untested!). You would need to add checks to make sure the lists aren't null too.
public class BaseList<TBase, TDerived1, TDerived2>
where TDerived1 : TBase
where TDerived2 : TBase
{
private List<TDerived1> derived1List;
private List<TDerived2> derived2List;public List<TDerived1> Derived1List { get { return derived1List; } set { derived1List = value; } } public List<TDerived2> Derived2List { get { return derived2List; } set { derived2List = value; } } public ReadOnlyCollection<TBase> BaseCollection { get { List<TBase> baseList = DerivedConverter<TBase, TDerived1>(derived1List); baseList.AddRange(DerivedConverter<TBase, TDerived2>(derived2List)); return baseList.AsReadOnly(); } } private static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList) where TDerived : TBase { return derivedList.ConvertAll<TBase>( new Converter<TDerived, TBase>(delegate(TDerived derived) { return derived; })); }
}
Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Lee Reid wrote:
My project has several deriving classes, and values being entirely 'readonly' defeats the purpose of the whole class
I thought you only needed the complete list for iterating through all lists, thus a read-only iterator (such as IEnumerator) could be enough. IEnumerable(T) can be implemented in a very clean way without the need of any "special programming". See the following example:
class A {}
class B : A {}
class C : A {}
class MyClass {
private List<B> listB;
private List<C> listC;public MyClass() {
// Initialize lists...
}public IEnumerable<A> GetAllItems() {
// Get each list in this class as IEnumerable<A>
IEnumerable<A> b = listB;
IEnumerable<A> c = listC;return b.Concat(c);
}
}Yes, it is this simple. Note that the GetAllItems method could easily be 1-line long. You can implement some logic to get all the lists in the current class as an array of IEnumerable<A> for easy overriding, it's not more than 1 or 2 very short methods... I don't think this is messy, nor does it require any "special programming" and I highly doubt there is easier and simpler solution. The one and only question is: does IEnumerable provide enough functionality for your application?
-
Perhaps something like this will help. This implementation doesn't allow you to add items to the base list, I don't know if that is a requirement or not. This won't be particularly efficient but should work (untested!). You would need to add checks to make sure the lists aren't null too.
public class BaseList<TBase, TDerived1, TDerived2>
where TDerived1 : TBase
where TDerived2 : TBase
{
private List<TDerived1> derived1List;
private List<TDerived2> derived2List;public List<TDerived1> Derived1List { get { return derived1List; } set { derived1List = value; } } public List<TDerived2> Derived2List { get { return derived2List; } set { derived2List = value; } } public ReadOnlyCollection<TBase> BaseCollection { get { List<TBase> baseList = DerivedConverter<TBase, TDerived1>(derived1List); baseList.AddRange(DerivedConverter<TBase, TDerived2>(derived2List)); return baseList.AsReadOnly(); } } private static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList) where TDerived : TBase { return derivedList.ConvertAll<TBase>( new Converter<TDerived, TBase>(delegate(TDerived derived) { return derived; })); }
}
Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Absolutely perfect! Thank you so much; I've not used ienumerable and related interfaces before, and didn't realise quite how elegant/easy/functional they could be. You've relieved quite a headache of mine! Thanks again
I'm glad it helped... Here are 2 more notes to make my answer a bit more complete. I assume you aready know all this, but it coud be useful to someone: - The System.Linq namespace is your best friend when working with IEnumerable(T). - The
yield return
statement is an awesome language feature... http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx -
Thanks Dave! while not being the 'perfect' solution, you seem to have inadvertently solved another problem of mine, which I hadn't put to the forums yet! I appreciate the help :-)
You're welcome :)
Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Perhaps something like this will help. This implementation doesn't allow you to add items to the base list, I don't know if that is a requirement or not. This won't be particularly efficient but should work (untested!). You would need to add checks to make sure the lists aren't null too.
public class BaseList<TBase, TDerived1, TDerived2>
where TDerived1 : TBase
where TDerived2 : TBase
{
private List<TDerived1> derived1List;
private List<TDerived2> derived2List;public List<TDerived1> Derived1List { get { return derived1List; } set { derived1List = value; } } public List<TDerived2> Derived2List { get { return derived2List; } set { derived2List = value; } } public ReadOnlyCollection<TBase> BaseCollection { get { List<TBase> baseList = DerivedConverter<TBase, TDerived1>(derived1List); baseList.AddRange(DerivedConverter<TBase, TDerived2>(derived2List)); return baseList.AsReadOnly(); } } private static List<TBase> DerivedConverter<TBase, TDerived>(List<TDerived> derivedList) where TDerived : TBase { return derivedList.ConvertAll<TBase>( new Converter<TDerived, TBase>(delegate(TDerived derived) { return derived; })); }
}
Dave
If this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum.(Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)Nice one - have a 5 sir.
I have CDO, it's OCD with the letters in the right order; just as they ruddy well should be
Forgive your enemies - it messes with their heads
-
Hi guys, I need to find a way around a problem so that I can have an array containing lists of different types, where those types are all derived from the same class. Unfortunately I'm at the end of my knowledge of covariance etc so any help would be greatly appreciated. To explain better: I have a class ('class X') which has several lists in it. The lists contain different types, but these types all derive from the same abstract class. I need to be able to iterate through all items (i.e. across and within the lists) as though all the lists were one. For various reasons, the lists must remain as separate objects. Furthermore, 'Class X' is inherited, and derived classes have additional lists... My solution was to create an array containing these lists (which makes iterating with an unknown number of lists very easy), but I hit problems very quickly (e.g. see code below). Can anyone show me how to do something like this, or find a more elegant way to do it? Here's a much smaller, simple example of the kind of thing I'm trying to do:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace ConsoleApplication1
{
abstract class A
{ }class B : A { } class C : A { } class ClassX { static void DoStuff() { List<B> listB = new List<B>(); List<C> listC = new List<C>(); List<A>\[\] arrayOfLists = new List<A>\[2\]; arrayOfLists\[0\] = listB;//Error 1 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.B>' arrayOfLists\[1\] = listC;//Error 2 Cannot implicitly convert type 'System.Collections.Generic.List<ConsoleApplication1.C>' arrayOfLists\[0\] = listB.Cast<A>().ToList();//Subsequent changes in list B are not seen an arrayoflists because a new object is created List<List<A>> listOfLists = new List<List<A>>(); listOfLists.Add(listB);//Error 4 Argument 1: cannot convert from 'System.Collections.Generic.List<ConsoleApplication1.B>' to 'System.Collections.Generic.List<ConsoleApplication1.A>' } }
}
Thanks! Lee
Would this help?
public static System.Collections.Generic.IEnumerable<T>
Enumerate<T>
(
params System.Collections.Generic.IEnumerable<T>[] Lists
)
{
foreach
(
System.Collections.Generic.IEnumerable<T> list
in
Lists
)
{
foreach
(
T t
in
list
)
{
yield return ( t ) ;
}
}yield break ;
}