Проблема с генераторами С# - создание типичного типа с параметрами в конструкторе

Я пытаюсь создать универсальный класс, который обновляет экземпляр родового типа. Как показано ниже:

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

Но я получаю следующую ошибку:

не может предоставлять аргументы при создании экземпляра переменной тип

Я нашел следующий связанный с ним вопрос, который очень близок к тому, что мне нужно: Передача аргументов в С# generic new() шаблонного типа

Однако я не могу использовать Джареда, который предложил ответить, поскольку я вызов метода в классе Generic, а не за пределами это, поэтому я не могу указать конкретный класс.

Есть ли способ обойти это?

Я пробовал следующее по другому вопросу, но это не работает, поскольку я не знаю конкретного типа T указывать. Поскольку он вызывается изнутри общего класса, а не снаружи:

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

******** ИЗМЕНИТЬ: ДОБАВЛЕННЫЕ ВОЗМОЖНЫЕ РЕШЕНИЯ **

Итак, я протестировал 2 возможных решения:

В первую очередь, как описано ниже Jon Skeet. Эта определенно работает, но означает наличие неясной лямбды в конструктор. Мне это не очень удобно, так как это означает пользователи должны знать правильную лямбду, которая ожидается. В конце концов, они могут пройти лямбду, которая не тип, но делает что-то совершенно неожиданное

Во-вторых, я пошел по маршруту метода Factory; Я добавил метод Create к общему интерфейсу:

IJewellerHomepageCarouselItem Create(PageData pageData);

Затем была реализована реализация в каждом классе Concrete:

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

И использовал двухэтапный синтаксис инициализации:

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

Хотелось бы услышать некоторые отзывы о достоинствах каждого из этих подходов.

Ответы

Ответ 1

Ответ 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;
    }

Затем вы просто передаете функцию в конструктор, где вы создаете новый экземпляр HomepageCarousel<T>.

(Я бы рекомендовал композицию вместо наследования, btw... получение из List<T> почти всегда неправильным способом.)

Ответ 2

Рассматривали ли вы использование Activator (это еще один вариант).

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

Ответ 3

Просто добавьте к другим ответам:

То, что вы здесь делаете, в основном называется проекцией. У вас есть List одного типа и вы хотите проецировать каждый элемент (с помощью делегата) на другой тип элемента.

Итак, общая последовательность операций на самом деле (с использованием LINQ):

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

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

Или, если вы используете .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);
    }
}

а затем используйте его как:

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

Я не уверен, как выглядит остальная часть кода, но я считаю, что GetInitialCarouselData не является подходящим местом для обработки инициализации, тем более что он в основном дублирует функциональность проецирования (что довольно общее и может быть извлеченный в отдельный класс, например Project).

[Изменить] Подумайте о следующем:

Я считаю, что теперь у вашего класса есть такой конструктор:

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

    // ...
}

Я предполагаю, что это так, потому что вы обращаетесь к полю jewellerHomepages в своем методе (поэтому, я думаю, вы храните его в ctor).

С этим подходом существует несколько проблем.

  • У вас есть ссылка на jewellerHomepages, которая не имеет значения. Ваш список - это список IHomepageCarouselItems, так что пользователи могут просто вызвать метод Clear() и заполнить его тем, что им нравится. Затем вы получите ссылку на то, что вы не используете.

  • Вы можете исправить это, просто удалив поле:

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

    Но что произойдет, если вы поймете, что можете захотеть инициализировать его с помощью другого класса, отличного от PageData? Прямо сейчас вы создаете такой список:

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

    Вы оставляете себе возможность каким-либо образом создать экземпляр? Даже если вы добавите новый constuctor, ваш метод GetInitialCarouselData все еще слишком специфичен, чтобы использовать только PageData в качестве источника.

Заключение: не используйте конкретный тип в своем конструкторе, если это не нужно. Создайте фактические элементы списка (конкретные экземпляры) в другом месте.

Ответ 4

Это С# и CLR гандикап, вы не можете передать аргумент новому T(), простому.

Если вы исходите из фона С++, это используется НЕ-сломанно и TRIVIAL. PLUS вам даже не требуется интерфейс/ограничение. Ломать повсюду, и без этого функционального взлома factory 3.0 вы вынуждены выполнять двухпроходную инициализацию. Управляемое богохульство!

Сначала создайте новый T(), а затем установите свойство или передайте синтаксис экзотического инициализатора или, как и все предлагаемые, используйте функциональное обходное решение Pony. Все yucky, но что компилятор и представление времени для "generics" для вас.

Ответ 5

Существует другое решение, довольно грязное.

Сделать IHomepageCarouselItem методом "Construct", который принимает параметр pageData как параметр и возвращает IHomepageCarouselItem.

Затем сделайте следующее:

   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;

Ответ 6

Вероятно, я поеду за предложением Тони "jon" пони Skeet, но есть другой способ сделать это. Поэтому в основном для развлечения здесь существует другое решение (у которого есть недостаток во время выполнения во время выполнения, если вы забудете реализовать необходимый метод, но не в том, чтобы не использовать метод factory, то компилятор волшебным образом подключит вас.

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

просто для того, чтобы повторить мое "отказ от ответственности", это очень важно, чтобы служить напоминанием о том, что могут быть довольно разные подходы к решению одной и той же проблемы, которые у всех у них есть, и сильные стороны. Один из обратных путей этого подхода состоит в том, что он частично черная магия;)

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

но вы избегаете конструктора perculiar, принимающего аргумент делегата. (но я обычно придерживаюсь такого подхода, если только я не использовал механизм инъекции зависимостей для предоставления класса factory для меня. Наверное, это тот вид среды, в которой я работаю в свое свободное время; p)

Ответ 7

Почему бы вам просто не поставить статический метод "конструктора" на интерфейс? Немного хаки, я знаю, но ты должен делать то, что должен делать...