Проблема с генераторами С# - создание типичного типа с параметрами в конструкторе
Я пытаюсь создать универсальный класс, который обновляет экземпляр родового типа. Как показано ниже:
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
Почему бы вам просто не поставить статический метод "конструктора" на интерфейс? Немного хаки, я знаю, но ты должен делать то, что должен делать...