Tar
Tar

Reputation: 9015

How to convert nested generic to another nested generic that inherits from the first one?

This code:

public interface IInter { }
public class Concrete : IInter { /*... body ...*/ }
var t = (List<IInter>)new List<Concrete>();

Yields this error:

Cannot convert type 'System.Collections.Generic.List<Concrete>' to 'System.Collections.Generic.List<IInter>'

Why is it ? how do I overcome it ? my goal is this:

var t = new List<List<IInter>>()
{
    new List<ConcreteA>(){/* ... data ... */},
    new List<ConcreteB>(){/* ... data ... */},
    // ...
    new List<ConcreteX>(){/* ... data ... */},
};

Edit:

Thanks for all your help. Ahh, I kinda abstracted things, to make it easier to read... but my real problem is this:

public class SingletonFactory<T> where T : IToken
{
    private SingletonFactory() { }
    private static SingletonFactory<T> _instance = new SingletonFactory<T>();
    public static SingletonFactory<T> Instance { get { return _instance; } }

    public T Produce(int position) { return (T)Activator.CreateInstance(typeof(T), position); }
    public T Produce(int position, string token) { return (T)Activator.CreateInstance(typeof(T), position, token); }
}

And then:

var keywords = new Dictionary<string, SingletonFactory<IToken>>()
{
    { "abc", SingletonFactory<Abc>.Instance },
    { "xyz", SingletonFactory<Xyz>.Instance },
    { "123", SingletonFactory<Num>.Instance }
};

So I guess it's much more complicated...

I'm using c# 4.0

Upvotes: 1

Views: 269

Answers (6)

jam40jeff
jam40jeff

Reputation: 2596

Make a covrariant interface for the methods you need to access from SingletonFactory<T>. This will ensure that your type T is output type safe.

public interface IToken { }
public class Abc : IToken { }
public class Xyz : IToken { }
public class Num : IToken { }

public interface ISingletonFactory<out T> where T : IToken
{
    T Produce(int position);
    T Produce(int position, string token);
}

public class SingletonFactory<T> : ISingletonFactory<T> where T : IToken
{
    private SingletonFactory() { }
    private static SingletonFactory<T> _instance = new SingletonFactory<T>();
    public static SingletonFactory<T> Instance { get { return _instance; } }

    public T Produce(int position) { return (T)Activator.CreateInstance(typeof(T), position); }
    public T Produce(int position, string token) { return (T)Activator.CreateInstance(typeof(T), position, token); }
}

var keywords = new Dictionary<string, ISingletonFactory<IToken>>()
{
    { "abc", SingletonFactory<Abc>.Instance },
    { "xyz", SingletonFactory<Xyz>.Instance },
    { "123", SingletonFactory<Num>.Instance }
};

Upvotes: 1

Paul Ruane
Paul Ruane

Reputation: 38580

This is called covariance and C# supports covariance (and contravariance) on generic interfaces and not generic classes. This is the following works but your example does not:

IEnumerable<IInter> e = new List<Concrete>();
ICollection<IInter> c = new List<Concrete>();

Covariance is also supported on arrays:

IInter[] a = new Concrete[3];

Upvotes: 2

Johan Larsson
Johan Larsson

Reputation: 17580

One way is:

IEnumerable<IInter> t = new List<Concrete>();

Another is:

List<IInter> t = new List<Concrete>().ConvertAll(x => (IInter) x);

Edit, adding better sample to show that it works (the test passes):

[Test]
public void CovarianceTest()
{
    var concrete = new Concrete();
    IEnumerable<IInter> t = new List<Concrete> { concrete };
    Assert.IsTrue(new[] { concrete }.SequenceEqual(t));

    List<IInter> t2 = new List<Concrete> { concrete }.ConvertAll(x => (IInter)x);
    Assert.IsTrue(new[] { concrete }.SequenceEqual(t2));
}

Upvotes: 1

D Stanley
D Stanley

Reputation: 152501

Because you cannot assign a List<ConcreteA> to a List<IInter>, otherwise you could do this:

concreteAList = newList<ConcreteA>();
List<Inter> interList = concreteAList as List<Inter>;  // seems harmless
interList.Add(new ConcreteB());                        // not allowable

You could do:

var t = new List<List<IInter>>()
{
    new List<IInter>(){/* ... fill with ConcreteAs ... */},
    new List<IInter>(){/* ... fill with ConcreteBs ... */},
    // ...
    new List<IInter>(){/* ... fill with ConcreteXs ... */},
};

But I don't know if that accomplishes what you want.

Upvotes: 2

Tim Schmelter
Tim Schmelter

Reputation: 460038

A List<Concrete> is not a List<IInter> although Concrete implements IInter.

You can use:

List<IInter> t = ConcreteList.Cast<IInter>().ToList();

Covariance and Contravariance FAQ

Upvotes: 2

Mehmet Ataş
Mehmet Ataş

Reputation: 11549

var res = new List<Concrete>().Cast<IInter>().ToList();

Upvotes: 2

Related Questions