Проблема дженериков C# - newing универсальный тип с параметрами в конструкторе

Я нашел эта страница , которая показывает, какой различие между Proc.new и lambda. Согласно странице, единственная разница - то, что лямбда строга о количестве аргументов, которые это принимает, тогда как Proc.new преобразовывает недостающие аргументы nil. Вот является пример сессией IRB, иллюстрирующей различие:

irb(main):001:0> l = lambda { |x, y| x + y }
=> #
irb(main):002:0> p = Proc.new { |x, y| x + y }
=> #
irb(main):003:0> l.call "hello", "world"
=> "helloworld"
irb(main):004:0> p.call "hello", "world"
=> "helloworld"
irb(main):005:0> l.call "hello"
ArgumentError: wrong number of arguments (1 for 2)
    from (irb):1
    from (irb):5:in `call'
    from (irb):5
    from :0
irb(main):006:0> p.call "hello"
TypeError: can't convert nil into String
    from (irb):2:in `+'
    from (irb):2
    from (irb):6:in `call'
    from (irb):6
    from :0

страница также рекомендует использовать лямбду, если Вы конкретно не хотите ошибки терпимое поведение. Я соглашаюсь с этим чувством. Используя лямбду кажется немного более кратким, и с таким незначительным различием, это кажется лучшим выбором в средней ситуации.

Что касается Ruby 1.9, извините, я еще не изучил 1.9, но я не предполагаю, что они изменились бы, все это так очень (не делайте честное слово, хотя, кажется, что Вы услышали о некоторых изменениях, таким образом, я, вероятно, неправ там).

20
задан Community 23 May 2017 в 11:52
поделиться

7 ответов

Ответ Джареда по-прежнему хорош - вам просто нужно заставить конструктор взять Func и спрятать его на потом:

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 .

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

18
ответ дан 29 November 2019 в 23:37
поделиться

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

То, что вы здесь делаете, в основном называется проекцией . У вас есть 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 ).

[Edit] Подумайте о следующем:

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

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  jewellerHomepages)
    {
     // не сохраняем ссылку на jewellerHomepages
     this.AddRange (GetInitialCarouselData (jewellerHomepages));
    }
    

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

     HomepageCarousel  list =
     новый HomepageCarousel  (listOfPageData);
    

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

Вывод: не используйте в конструкторе определенный тип если в этом нет необходимости. Создайте фактические элементы списка (конкретные экземпляры) где-нибудь еще.

5
ответ дан 29 November 2019 в 23:37
поделиться

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

Если вы работаете с C ++, это НЕ БЛОКИРУЕТСЯ и ТРИВИАЛЬНО. ПЛЮС вам даже не требуется интерфейс / ограничение. Повсюду ломаются, и без этого функционального взлома Factory 3.0 вы вынуждены выполнять двухпроходную инициализацию. Управляемое богохульство!

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

1
ответ дан 29 November 2019 в 23:37
поделиться

Возможно другое решение, довольно грязное.

Сделайте 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;
0
ответ дан 29 November 2019 в 23:37
поделиться

Я бы, наверное, последовал совету Тони «Джона», пони Скита, но есть другой способ сделать это. Так что в основном для развлечения, вот другое решение (у которого есть обратная сторона - сбой во время выполнения, если вы забыли реализовать необходимый метод, но положительная сторона отсутствия необходимости предоставлять фабричный метод, компилятор волшебным образом подключит вас.

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

, но вы избегаете особого конструктора, принимающего аргумент делегата.

0
ответ дан 29 November 2019 в 23:37
поделиться

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

0
ответ дан 29 November 2019 в 23:37
поделиться

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

T homepageMgmtCarouselItem = Activator.CreateInstance(typeof(T), pageData) as T;
20
ответ дан 29 November 2019 в 23:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: