2009-11-05 10 views
17

Próbuję utworzyć klasę ogólną, która jest nową instancją typu ogólnego. W następujący sposób:C# problem generyczny - tworzenie nowego typu ogólnego z parametrami w konstruktorze

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

Ale pojawia się następujący błąd:

cannot provide arguments when creating an instance of a variable type

znalazłem następujące powiązane pytanie, który jest bardzo zbliżony do tego, co potrzebne: Passing arguments to C# generic new() of templated type

Jednak mogę” t użyłem sugerowanej odpowiedzi Jareda, ponieważ jestem nazywając metodę wewnątrz klasy Generic, a nie poza , więc nie mogę określić konkretnej klasy.

Czy istnieje sposób obejścia tego?

Próbowałem na podstawie innego pytania, ale to nie działa, ponieważ nie wiem konkretny typ T do określić. Jak nazywa się od wewnątrz klasy rodzajowej, nie zewnątrz:

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: Dodano możliwych rozwiązań **

Więc ja testowałem 2 możliwe rozwiązania:

Pierwsza jest dokładnie taka, jak wyjaśniono poniżej przez Jon Skeet. Ten zdecydowanie działa, ale oznacza posiadanie niejasnego lambda w konstruktorze . Nie jestem z tym bardzo zadowolony, ponieważ oznacza to, że użytkownicy muszą znać poprawną lambdę, która jest oczekiwana. Przecież mogliby przekazać lambdę, która nie jest nowsza niż typ , ale robi coś zupełnie nieoczekiwanego

Po drugie, przeszedłem ścieżką metody Factory; dodałem Tworzenie metodę wspólnego interfejsu:

IJewellerHomepageCarouselItem Create(PageData pageData); 

Następnie pod warunkiem wdrożenia w każdej klasie betonu:

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

i używane dwuetapowego inicjalizacji składni:

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

Would Uwielbiam słyszeć pewne opinie na temat zalet każdego z tych podejść.

+0

możliwy duplikat [przekazywanie argumentów do C# generic new() typu szablonowego] (http://stackoverflow.com/questions/840261/passing -argumenty-do-c-ostry-generic-new-of-templated-type) – nawfal

Odpowiedz

18

odpowiedź Jareda jest nadal dobra droga - wystarczy, aby konstruktor wziąć Func<PageData, T> i schować go na później:

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

Wtedy po prostu przekazać funkcję do konstruktora gdzie utworzyć nowa instancja modelu HomepageCarousel<T>.

(polecam skład zamiast dziedziczenie, btw ... wynikające z List<T> jest prawie zawsze niewłaściwa droga.)

+1

Nigdy nie podobał mi się ten sposób robienia tego, i zwykle domyślnie stosuje się technikę Activator (jak sugerował Quintin), chyba że użyto refleksji mają nieakceptowalny wpływ na wydajność. – philsquared

+0

dzięki Tony. Czy możesz rozwinąć sposób, w jaki używałbyś kompozycji, a nie dziedziczenia? Początkowo miałem właściwość o nazwie CarouselItems, która zawierała dane. Ale potem zmieniono klasę na dziedziczkę z listy i udostępniono dane w ten sposób.
Zgaduję, że mówisz w obie strony, nie są świetne? – ChrisCa

+3

Nie chodzi o wpływ na wydajność, o którym myślę - to aspekt "nie dowiaduję się, że są zepsute do czasu wykonania". –

18

Czy bierzesz za pomocą aktywatora (to jest po prostu innej opcji).

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

tak, rozważałem to. Przeczytałem ten artykuł. http://www.dalun.com/blogs/05.27.2007.htm Ale wolałbym nie iść tą drogą, jeśli można tego uniknąć. Podobają mi się składnia zasugerowana w innym pytaniu Ale dziękuję za sugestię. – ChrisCa

0

Możliwe jest inne rozwiązanie, raczej brudne.

Ustaw IHomepageCarouselItem ma metodę "Construct", która pobiera parametr pageData jako parametr i zwraca IHomepageCarouselItem.

Wtedy to zrobić:

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

Jest to C# i CLR upośledzenie, nie można przekazać argument new T(), proste.

Jeśli pochodzisz z tła w języku C++, to używane NIE jest uszkodzone i TRIVIAL. PLUS nie wymaga nawet interfejsu/ograniczenia. Zniszczenie w każdym miejscu i bez tego funkcjonalnego hackera 3.0, jesteś zmuszony do inicjalizacji 2-etapowej. Zarządzane bluźnierstwo!

Najpierw wykonaj nową T(), a następnie ustaw właściwość lub podaj egzotyczną składnię inicjalizacyjną lub, jak wszystkie dobrze sugerowane, użyj obejścia funkcjonalnego Pony. Całe szczęście, ale jest to dla Ciebie pomysł kompilatora i środowiska wykonawczego dla "generics".

+1

Punkt generyczny ma być ogólny. Wykonanie implementacji z określonymi wymaganiami, aby wpisać implementację taką jak oczekiwanie konstruktora z pewnymi argumentami nie generycznymi, a tym samym niedozwolonymi dla generycznych. Ta reguła zapewnia, że ​​generics są generyczne. –

+1

@Rune FS: Dlaczego więc możesz umieścić na nich inne ograniczenia, takie jak klasy bazowe lub klasa/structness? – RCIX

+1

@ rama-jka toti: w końcu ktoś nazwał łopatą pik. Każdy, kto pochodzi z tła C++ jest po prostu przez to przywrócony. – andriej

0

Prawdopodobnie skorzystam z sugestii Tony'ego "jona" na kucyka Skeeta, ale jest inny sposób na zrobienie tego. Więc głównie dla zabawy tutaj jest inne rozwiązanie (które ma wadę w czasie działania, jeśli zapomnisz zaimplementować potrzebną metodę, ale na plus nie będzie musiał dostarczać metody fabrycznej, kompilator w magiczny sposób Cię podłączy.

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

Aby powtórzyć moje "zrzeczenie się", jest to bardzo ważne, aby przypomnieć, że może istnieć dość różne podejście do rozwiązania tego samego problemu, z którego wszyscy oni mają odwrotne strony i są mocne. część czarnej magii;)

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

ale unikasz konstruktora perculiar biorąc argument delegata. (ale zwykle idę do tego podejścia, chyba że używałem mechanizmu wtrysku zależności do dostarczania klasy fabrycznej dla mnie.To na pewno jest rodzaj struktury DI pracuję w moim sparetime; p)

+0

Jeśli mam to prawo, wymagałoby to napisania ogromnego bloku if/else/else ... wewnątrz metody Create extension, aby poprawnie obsługiwać każdy typ? Po co zatem używać generycznych? – Groo

+0

Próbowałem czegoś podobnego - patrz edit post. Co myślisz? – ChrisCa

+0

@Groo nie potrzebujesz ogromnego, jeśli-else. Potrzebna jest konkretna metoda rozszerzenia dla każdego typu. –

5

Tylko, aby dodać do innych odpowiedzi:

To, co tu robisz, nazywa się w zasadzie projekcją. Masz jeden typ jednego typu i chcesz wyświetlić każdy element (używając elementu delegowanego) w innym typie elementu.

Więc ogólnie kolejność operacji jest faktycznie (przy użyciu LINQ):

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

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

Lub, jeśli używasz .NET 2.0, można napisać klasy pomocnika, takich jak:

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

a następnie użyj go w następujący sposób:

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

Nie jestem pewien, jak wygląda reszta kodu, ale uważam, że GetInitialCarouselData nie jest właściwym miejscem do obsługi inicjowania, zwłaszcza, że ​​w zasadzie duplikuje funkcjonalność projekcji (która jest dość ogólna i może być wyodrębniona w osobnej klasie, np. Project).

[Edytuj] Zastanów się, co następuje:

wierzę teraz twoja klasa ma konstruktora takiego:

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

    // ... 
} 

przypuszczam jest to przypadek, ponieważ masz dostęp do jewellerHomepages pole w twojej metodzie (więc domyślam się, że przechowujesz to w ctor).

Jest kilka problemów z tym podejściem.

  • Masz odniesienie do jewellerHomepages, które jest niepotrzebne. Twoja lista jest listą IHomepageCarouselItems, więc użytkownicy mogą po prostu wywołać metodę Clear() i wypełnić ją dowolnie. Następnie kończy się odniesieniem do czegoś, czego nie używasz.

  • Można ustalić, że po prostu przez usunięcie murawę:

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

    Ale co się stanie, gdy zdajesz sobie sprawę, że może chcesz zainicjować go za jakąś inną klasę, różni się od PageData? Teraz, podczas tworzenia listy tak:

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

    wyjeżdżasz sobie dowolną opcję oznacz ją z niczego innego jeden dzień? Nawet jeśli dodasz nowy constuctor, twoja metoda GetInitialCarouselData jest nadal zbyt specyficzna, aby użyć tylko PageData jako źródła.

Wniosek jest następujący: Nie używaj określonego typu w swoim konstruktorze, jeśli nie jest to konieczne. Twórz rzeczywiste elementy listy (konkretne przypadki) gdzie indziej.

+0

dzięki za sugestię - zobacz edycję postu. Co myślisz? – ChrisCa

+0

Myślę, że klasa "HomepageCarousel " wie zbyt wiele o reszcie świata (naruszając zasadę odpowiedzialności pojedynczej). Jeśli masz jakąś funkcjonalność dołączoną do interfejsu ** IHomepageCarouselItem **, powinieneś obsłużyć ** tylko tę ** funkcjonalność w swojej klasie. Rzeczywista implementacja powinna zostać przekazana do klasy wywołującej (jak pokazano w odpowiedzi Jona), lub możesz po prostu utworzyć odpowiednie instancje w innym miejscu. W końcu jest to po prostu lista pozycji (prawdopodobnie z pewną funkcją związaną z IHomepageCarouselItem). Dlaczego 'List ' kiedykolwiek trzeba utworzyć wystąpienia 'T'? – Groo

+0

Musi utworzyć wystąpienia T w celu zapełnienia listy tj. Aby lista zawierała pewne dane w – ChrisCa

0

Dlaczego po prostu nie umieścisz statycznej metody "konstruktora" na interfejsie? Trochę hacky wiem, ale musisz zrobić to, co musisz zrobić ...

Powiązane problemy