0

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?

4
  • Well, the type needs to come from somewhere. If this was a regular method, you could've just did public void Foo<A, T>(), but since it's a constructor, and generic constructors are not supported, you need to have a way to provide the type. As you suggested, you could add a second generic parameter to the class definition (i.e., public class LDBList<T, A>). Alternatively, you could use a static method that does the conversion and returns an object of type LDBList<T>. Commented Oct 4, 2020 at 12:27
  • Which I did try, but a List<X, Y> is definitely not a List<X>, and this predictably broke the conventional ctor where it now complains quite rightly that 2 generic arguments are now needed. Commented Oct 4, 2020 at 12:31
  • How this question is different from the previous one, which you've asked? Commented Oct 4, 2020 at 20:02
  • @PavelAnikhouski The previous asks why is putting THE type parameter on a ctor doesn't work, this is asking how to add EXTRA type params to a class or a ctor. Related but different. Commented Oct 5, 2020 at 12:40

2 Answers 2

1

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);
Sign up to request clarification or add additional context in comments.

4 Comments

I understand. The problem with pulling the converter out of the class is it just is less encapsulated. But if there's no way round it, that's that. (but as I can have generics on methods, I can create the converter outside and pass it in; ok, not too terrible).
@user3779002 I suggest that you should want to remove the converter from the class, since it is an orthogonal concept, and adding it to the list itself actually violates the Single Responsibility Principle.
@MatthewWatson normally I'd agree, and thanks for pointing that out. In this case it's debatable as the converter just strips minor cruft (essentially parse tree data -> AST) so combining them's ok (in my view). But good point!
@MatthewWatson raises a good point. the LDbList list represents a list of items. the converter is a separate object that converts one thing into another. they are two separate objects which have distinct behaviour.
1

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

1 Comment

I've already accepted the answer before but yours is pretty good. The 'hiding the scaffolding' is what I'm very keen to do; I don't like repeated functionality even if it's a one-liner. I've tried the static method already and that's probably the way I'll go. Cheers!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.