简体   繁体   中英

C# generics problem - newing up the generic type with parameters in the constructor

I am trying to create a generic class which new's up an instance of the generic type. As follows:

public class HomepageCarousel<T> : List<T>
    where T: IHomepageCarouselItem, new()
{
    private List<T> GetInitialCarouselData()
    {
        List<T> carouselItems = new List<T>();

        if (jewellerHomepages != null)
        {
            foreach (PageData pageData in jewellerHomepages)
            {
               T item = new T(pageData); // this line wont compile
               carouselItems.Add(item);
            }
        }
        return carouselItems;
    }
}

But I get the following error:

cannot provide arguments when creating an instance of a variable type

I found the following related question which is very close to what I need: Passing arguments to C# generic new() of templated type

However, I can't used Jared's suggested answer as I am calling the method within the Generic class, not outside of it, so I can't specify the concrete class.

Is there a way around this?

I have tried the following based on the other question, but it doesn't work as I don't know the concrete type of T to specify. As it is called from inside the generic class, not outside:

public class HomepageCarousel<T> : List<T>
    where T: IHomepageCarouselItem, new()
{

    private List<T> LoadCarouselItems()
    {
        if (IsCarouselConfigued)
        {
            return GetConfiguredCarouselData();
        }

        // ****** I don't know the concrete class for the following line,
        //        so how can it be instansiated correctly?

        return GetInitialCarouselData(l => new T(l));
    }

    private List<T> GetInitialCarouselData(Func<PageData, T> del)
    {
        List<T> carouselItems = new List<T>();

        if (jewellerHomepages != null)
        {
            foreach (PageData pageData in jewellerHomepages)
            {
                T item = del(pageData);
                carouselItems.Add(item);
            }
        }
        return carouselItems;
    }
}

********EDIT : ADDED POSSIBLE SOLUTIONS**

So I have tested 2 possible solutions:

First is exactly as explained below by Jon Skeet . This definitely works but means having an obscure lambda in the constructor. I am not very comfortable with this as it means users need to know the correct lambda that is expected. After all, they could pass a lambda which doesn't new up the type, but does something entirely unexpected

Secondly, I went down the Factory method route; I added a Create method to the common interface:

IJewellerHomepageCarouselItem Create(PageData pageData);

Then provided an implementation in each Concrete class:

public IJewellerHomepageCarouselItem Create(PageData pageData)
{
     return new JewellerHomepageCarouselItem(pageData, null);
}

And used a two step initialisation syntax:

T carouselItem = new T();
T homepageMgmtCarouselItem = (T) carouselItem.Create(jewellerPage);

Would love to hear some feedback on the merit of each of these approaches.

您是否考虑过使用Activator(这只是另一种选择)。

T homepageMgmtCarouselItem = Activator.CreateInstance(typeof(T), pageData) as T;

Jared's answer is still a good way to go - you just need to make the constructor take the Func<PageData, T> and stash it for later:

public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem
{
    private readonly Func<PageData, T> factory;

    public HomepageCarousel(Func<PageData, T> factory)
    {
        this.factory = factory;
    }

    private List<T> GetInitialCarouselData()
    {
       List<T> carouselItems = new List<T>();

       if (jewellerHomepages != null)
       {
            foreach (PageData pageData in jewellerHomepages)
            {
                T homepageMgmtCarouselItem = factory(pageData);
                carouselItems.Add(homepageMgmtCarouselItem);
            }
       }
       return carouselItems;
    }

Then you just pass the function into the constructor where you create the new instance of the HomepageCarousel<T> .

(I'd recommend composition instead of inheritance, btw... deriving from List<T> is almost always the wrong way to go.)

Just to add to other answers:

What you are doing here is basically called projection . You have a List of one type and want to project each item (using a delegate) to a different item type.

So, a general sequence of operations is actually (using LINQ):

// get the initial list
List<PageData> pageDataList = GetJewellerHomepages();

// project each item using a delegate
List<IHomepageCarouselItem> carouselList =
       pageDataList.Select(t => new ConcreteCarousel(t));

Or, if you are using .Net 2.0, you might write a helper class like:

public class Project
{
    public static IEnumerable<Tdest> From<Tsource, Tdest>
        (IEnumerable<Tsource> source, Func<Tsource, Tdest> projection)
    {
        foreach (Tsource item in source)
            yield return projection(item);
    }
}

and then use it like:

// get the initial list
List<PageData> pageDataList = GetJewellerHomepages();

// project each item using a delegate
List<IHomepageCarouselItem> carouselList =
       Project.From(pageDataList, 
           delegate (PageData t) { return new ConcreteCarousel(t); });

I'm not sure how the rest of the code looks like, but I believe that GetInitialCarouselData is not the right place to handle the initialization, especially since it's basically duplicating the projection functionality (which is pretty generic and can be extracted in a separate class, like Project ).

[Edit] Think about the following:

I believe right now your class has a constructor like this:

public class HomepageCarousel<T> : List<T>
    where T: IHomepageCarouselItem, new()
{
    private readonly List<PageData> jewellerHomepages;
    public class HomepageCarousel(List<PageData> jewellerHomepages)
    {
        this.jewellerHomepages = jewellerHomepages;
        this.AddRange(GetInitialCarouselData());
    }

    // ...
}

I presume this is the case, because you are accessing a jewellerHomepages field in your method (so I guess you are storing it in ctor).

There are several problems with this approach.

  • You have a reference to jewellerHomepages which is unneccesary. Your list is a list of IHomepageCarouselItems, so users can simply call the Clear() method and fill it with anything they like. Then you end up with a reference to something you are not using.

  • You could fix that by simply removing the field:

     public class HomepageCarousel(List<PageData> jewellerHomepages) { // do not save the reference to jewellerHomepages this.AddRange(GetInitialCarouselData(jewellerHomepages)); } 

    But what happens if you realize that you might want to initialize it using some other class, different from PageData ? Right now, you are creating the list like this:

     HomepageCarousel<ConcreteCarousel> list = new HomepageCarousel<ConcreteCarousel>(listOfPageData); 

    Are you leaving yourself any option to instantiate it with anything else one day? Even if you add a new constuctor, your GetInitialCarouselData method is still too specific to use only PageData as a source.

Conclusion is: Do not use a specific type in your constructor if there is no need for it. Create actual list items (concrete instances) somewhere else.

It's a C# and CLR handicap, you cannot pass an argument to new T(), simple.

If you're coming from a C++ background this used be NOT-broken and TRIVIAL. PLUS you don't even require an interface/constraint. Breakage all over the place, and without that functional factory 3.0 hack you are forced to do 2-pass initialisation. Managed Blasphemy!

Do the new T() first and then set the property or pass an exotic initialiser syntax or as all well suggested use the Pony's functional workaround.. All yucky but that's the compiler and runtime idea of 'generics' for you.

There is another solution possible, rather dirty one.

Make IHomepageCarouselItem have "Construct" method which takes pageData as parameter and returns IHomepageCarouselItem.

Then do this:

   T factoryDummy = new T();
   List<T> carouselItems = new List<T>();

   if (jewellerHomepages != null)
   {
        foreach (PageData pageData in jewellerHomepages)
        {
            T homepageMgmtCarouselItem = (T)factoryDummy.Construct(pageData);
            carouselItems.Add(homepageMgmtCarouselItem);
        }
   }
   return carouselItems;

I'd Probably go for the suggestion from Tony "jon" the Skeet pony but there's another way of doing it. So mostly for the fun here's a different solution (that have the down side of failing at runtime if you forget to implement the needed method but the upside of not having to supply a factory method, the compiler will magically hook you up.

public class HomepageCarousel<T> : List<T> where T: IHomepageCarouselItem
{

    private List<T> GetInitialCarouselData()
    {
       List<T> carouselItems = new List<T>();

       if (jewellerHomepages != null)
       {
            foreach (PageData pageData in jewellerHomepages)
            {
                T homepageMgmtCarouselItem = null;
                homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData);
                carouselItems.Add(homepageMgmtCarouselItem);
            }
       }
       return carouselItems;
    }
}

public static class Factory
{
   someT create(this someT, PageData pageData)
   {
      //implement one for each needed type
   }

   object create(this IHomepageCarouselItem obj, PageData pageData)
   {
      //needed to silence the compiler
      throw new NotImplementedException();
   }
}

just to repeat my "disclaimer" this is very much ment to serve as a reminder that there can be rather different approaches to solving the same problem they all have draw backs and strengths. One draw back of this approach is that it's part black magic ;)

T homepageMgmtCarouselItem = null;
homepageMgmtCarouselItem = homepageMgmtCarouselItem.create(pageData);

but you avoid the perculiar constructor taking a delegate argument. (but I usually go for that approach unless I was using a dependency injection mechanism to supply the factory class for me. Which insidentally is the kind of DI framework I'm working on in my sparetime ;p)

Why don't you just put a static "constructor" method on the interface? A little hacky I know, but you gotta do what you gotta do...

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