简体   繁体   English

C#泛型问题 - 使用构造函数中的参数新建泛型类型

[英]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. 我正在尝试创建一个泛型类,其中new是泛型类的实例。 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 我发现了以下与我需要的相关的问题:将参数传递给模板化类型的C#generic new()

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. 但是,我不能使用Jared建议的答案,因为我在Generic类中调用方法,而不是在它之外,所以我不能指定具体的类。

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 . 首先,正如Jon Skeet所述 This definitely works but means having an obscure lambda in the constructor. 这绝对有效,但意味着在构造函数中有一个模糊的lambda。 I am not very comfortable with this as it means users need to know the correct lambda that is expected. 我对此不太满意,因为这意味着用户需要知道预期的正确lambda。 After all, they could pass a lambda which doesn't new up the type, but does something entirely unexpected 毕竟,他们可以通过一个不是新类型的lambda,但做了一些完全出乎意料的事情

Secondly, I went down the Factory method route; 其次,我走了工厂方法路线; I added a Create method to the common interface: 我在公共接口中添加了一个Create方法:

IJewellerHomepageCarouselItem Create(PageData pageData);

Then provided an implementation in each Concrete class: 然后在每个Concrete类中提供了一个实现:

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: Jared的答案仍然是一个很好的方法 - 你只需要让构造函数接受Func<PageData, T>并将其存储起来以供日后使用:

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> . 然后,您只需将该函数传递给构造函数,您可以在其中创建HomepageCarousel<T>的新实例。

(I'd recommend composition instead of inheritance, btw... deriving from List<T> is almost always the wrong way to go.) (我建议使用组合而不是继承,顺便说一句......从List<T>派生出来几乎总是错误的方法。)

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. 您有一个类型的List ,并希望将每个项目(使用委托)投影到不同的项目类型。

So, a general sequence of operations is actually (using LINQ): 因此,一般操作序列实际上是(使用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: 或者,如果您使用.Net 2.0,您可以编写一个帮助程序类,如:

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 ). 我不确定代码的其余部分是什么样的,但我相信GetInitialCarouselData不是处理初始化的正确位置,特别是因为它基本上复制了投影功能(这是非常通用的,可以在单独的类中提取,像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). 我认为是这种情况,因为您正在访问方法中的jewellerHomepages字段(所以我猜你将它存储在ctor中)。

There are several problems with this approach. 这种方法存在一些问题。

  • You have a reference to jewellerHomepages which is unneccesary. 您可以参考jewellerHomepages ,这是不必要的。 Your list is a list of IHomepageCarouselItems, so users can simply call the Clear() method and fill it with anything they like. 您的列表是IHomepageCarouselItems的列表,因此用户可以简单地调用Clear()方法并用他们喜欢的任何东西填充它。 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 ? 但是如果你意识到你可能想要使用与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. 即使添加了新的构造GetInitialCarouselDataGetInitialCarouselData方法仍然太具体,不能仅使用PageData作为源。

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. 这是C#和CLR的障碍,你不能将参数传递给新的T(),简单。

If you're coming from a C++ background this used be NOT-broken and TRIVIAL. 如果你来自C ++背景,那么这个使用是不会破坏和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. 破坏了整个地方,没有那个功能性工厂3.0黑客,你被迫进行2遍初始化。 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. 首先执行新的T(),然后设置属性或传递异国情调的初始化语法,或者所有建议都使用Pony的功能性解决方法。所有这些都令人讨厌,但这就是“泛型”的编译器和运行时概念。

There is another solution possible, rather dirty one. 还有另一种解决方案,相当脏。

Make IHomepageCarouselItem have "Construct" method which takes pageData as parameter and returns IHomepageCarouselItem. 使IHomepageCarouselItem具有“Construct”方法,该方法将pageData作为参数并返回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. 我可能会去寻找Tony“jon”Skeet小马的建议,但还有另一种方法。 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) (但我通常会采用这种方法,除非我使用依赖注入机制为我提供工厂类。我在业余时间正在研究哪种DI框架; 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... 我知道有点hacky,但你必须做你要做的事......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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