简体   繁体   中英

C# subclassing with generics: I need an extra generic parameter for ctor, but how?

I have a class

public class LDBList<T> : List<T> where T : LDBRootClass {
    // typical constructor
    public LDBList(LDBList<T> x) : base(x) { }
    ...
}

but I want to have an extra constructor that takes a list of a different generic type (say A), and a function that converts an A to a T, and build the T list from that, something like

public LDBList(
        Func<A, T> converter, 
        IList<A> aList)
{
    foreach (var x in aList) {
        this.Append(converter(x));
    }
}

so converter is of type A->T so I take an A list and make a T list from it. My class is parameterised by T so that's fine.

But it's complaining "The type or namespace name 'A' could not be found".

OK, so it needs the an A generic parameter on the class I suppose (it really doesn't like it on the constructor). But where do I put it, in fact is this even possible?

In theory you could add the extra type to the generic types for the class itself:

public class LDBList<A, T> : List<T> where T : LDBRootClass
{
    // typical constructor
    public LDBList(LDBList<A, T> x) : base(x) { }

    public LDBList(
        Func<A, T> converter,
        IList<A>   aList)
    {
        foreach (var x in aList)
        {
            this.Append(converter(x));
        }
    }
}

But of course that means that you need to declare the instances of that type with an extra type parameter that you won't even need unless you're using that specific constructor. So that's no good.

You could declare a helper class like so:

public static class LDBListCreator
{
    public static LDBList<T> CreateFrom<T, A>(Func<A, T> converter, IList<A> aList) where T: LDBRootClass
    {
        var result = new LDBList<T>();
        result.AddRange(aList.Select(converter));
        return result;
    }
}

This assumes that LDBList<T> has a default constructor.

But on inspection, you should be able to see that it's pointless creating such a simple helper class. If you add a constructor to your list class that accepts an IEnumerable<T> (like the List<T> class has) like so:

public LDBList(IEnumerable<T> items) : base(items)
{
}

Then you can construct an instance of LDBList<T> just by using IEnumerable.Select() .

For example, given:

public class LDBRootClass
{
}

public class DerivedLDBRootClass : LDBRootClass
{
    public DerivedLDBRootClass(string value)
    {
        // .. whatever
    }
}

Then you could convert from a List<string> to a LDBList<DerivedLDBRootClass> very simply without any additional scaffolding, like so:

var strings = new List<string> { "One", "Two", "Three", "Four", "Five" };
var result  = new LDBList<DerivedLDBRootClass>(strings.Select(item => new DerivedLDBRootClass(item)));

I don't believe you can add additional generic types to a constructor like that that.

I would refactor the converter to do the creation and return the instance of LDBList, that way the convert acts as a factory for creating LDBLists from instances of A.

public class Converter<T,A>
{
    public LDbList<T> CreateLdbList(IList<A>) {
       var list = new LdbList<T>();
       // do the conversion here
       return list;
    }
}

then, change the usage to be

var Converter<X,Y> = new Converter();
var result = Converter.Convert(originalData);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM