2009-04-08 14 views
162

Jeśli BaseFruit ma konstruktora, który akceptuje int weight, mogę utworzyć wystąpienie kawałek owoców w ogólnej metody, takie jak to?Utwórz wystąpienie typu ogólnego?

public void AddFruit<T>()where T: BaseFruit{ 
    BaseFruit fruit = new T(weight); /*new Apple(150);*/ 
    fruit.Enlist(fruitManager); 
} 

Przykład jest dodawany za komentarzami. Wygląda na to, że mogę to zrobić, jeśli podam BaseFruit konstruktor bez parametrów, a następnie wypełnię wszystko przez zmienne składowe. W moim prawdziwym kodzie (nie o owocach) jest to raczej niepraktyczne.

-Update-
Wydaje się więc, że nie może być rozwiązany przez ograniczenia w jakikolwiek sposób wtedy. Z odpowiedzi są trzy rozwiązania kandydujące:

  • Fabryka Wzór
  • Odbicie
  • Activator

staram się myśleć odbicie jest czyste najmniej jeden, ale nie mogę się zdecydować pomiędzy pozostałe dwie.

+0

Przy okazji: dziś prawdopodobnie rozwiązałbym to z wybraną biblioteką IoC. –

Odpowiedz

238

Dodatkowo prostszy przykład:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight }); 

Należy pamiętać, że przy użyciu nowego() ograniczenia na T jest tylko, aby kompilator sprawdź publiczny konstruktor bez parametrów w czasie kompilacji, rzeczywisty kod użyty do utworzenia typu to klasa Activator.

Musisz się upewnić, że dany konstruktor istnieje, a tego rodzaju wymaganiami może być zapach kodu (lub raczej coś, czego powinieneś unikać w aktualnej wersji na C#).

+0

Ponieważ ten konstruktor jest na baseclass (BaseFruit) wiem, że będzie miał konstruktora. Ale w istocie, jeśli pewnego dnia zdecyduję, że bazfruit potrzebuje więcej parametrów, mogę go spieprzyć. Przyjrzy się jednak klasie ACtivator. Nie słyszałem o tym wcześniej. –

+3

Ten działał dobrze. Istnieje również procedura CreateInstance (), ale nie ma ona przeciążenia dla parametrów dla niektórych rason .. –

+8

Nie ma potrzeby używania 'new object [] {weight}'. "CreateInstance" jest zadeklarowane z parametrami, 'public static object CreateInstance (typ Type, params object [] args)', więc możesz po prostu zrobić 'return (T) Activator.CreateInstance (typeof (T), weight);'. Jeśli istnieje wiele parametrów, przekaż je jako oddzielne argumenty. Tylko jeśli masz już skonstruowane przeliczalne parametry, powinieneś zawracać sobie głowę konwersją do 'object []' i przekazać to do 'CreateInstance'. – ErikE

40

Tak; zmienić gdzie się:

where T:BaseFruit, new() 

Jednak ta działa tylko z bez parametrów konstruktorów. Będziesz musiał mieć inne sposoby na ustawienie swojej nieruchomości (ustawienie samej nieruchomości lub coś podobnego).

+42

Skarbie, dostałem Robinsona ... –

+3

@Jon Skeet: To było bardzo bliskie rozśmieszania mnie (w pracy!). –

+1

@MichaelMyers Nie byłem tym szczęściarzem – ppeterka

74

Nie można użyć żadnego sparametryzowanego konstruktora. Możesz użyć konstruktora bez parametrów, jeśli masz ograniczenie "where T : new()".

Jest to ból, ale takie jest życie :(

Jest to jedna z rzeczy, chciałbym zająć się "static interfaces". Można by następnie móc ograniczyć T zawierać statyczne metody, operatorów i konstruktorów , a następnie połączyć je.

+1

Jesteś cały owocowy! :) –

+10

Naprawdę chciałem tylko powiedzieć Skeeted. –

+1

Przynajmniej MOŻESZ robić takie więzy - Java zawsze mnie rozczarowuje. –

13

Jak Jon zauważył to jest życie dla ograniczający non-bez parametrów konstruktora. jednak inne rozwiązanie polega na zastosowaniu wzoru fabrycznego. to jest łatwo constrainable

interface IFruitFactory<T> where T : BaseFruit { 
    T Create(int weight); 
} 

public void AddFruit<T>(IFruitFactory<T> factory) where T: BaseFruit {  
    BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/  
    fruit.Enlist(fruitManager); 
} 

Jeszcze inna opcja jest użycie podejście funkcjonalne. Przekaż metodę fabryczną.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
    BaseFruit fruit = factoryDel(weight); /* new Apple(150); */ 
    fruit.Enlist(fruitManager); 
} 
+2

Dobra sugestia - choć jeśli nie jesteś ostrożny, możesz skończyć w piekle API DOM Java, z mnóstwem fabryk :( –

+0

@Jon, nie chciałbym tego :) – JaredPar

+0

Tak, to jest rozwiązanie, które rozważałem siebie. Ale miałem nadzieję na coś w rodzaju ograniczeń. Chyba nie .. –

10

Można to zrobić za pomocą refleksji:

public void AddFruit<T>()where T: BaseFruit 
{ 
    ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) }); 
    if (constructor == null) 
    { 
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor"); 
    } 
    BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit; 
    fruit.Enlist(fruitManager); 
} 

EDIT: Dodany konstruktor == czek wartości null.

EDIT: Szybszy wariant użyciu cache:

public void AddFruit<T>()where T: BaseFruit 
{ 
    var constructor = FruitCompany<T>.constructor; 
    if (constructor == null) 
    { 
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor"); 
    } 
    var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit; 
    fruit.Enlist(fruitManager); 
} 
private static class FruitCompany<T> 
{ 
    public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) }); 
} 
+0

Chociaż nie podoba mi się narzut refleksji, jak wyjaśnili inni, tak po prostu jest tak obecnie. Widząc, jak ten konstruktor nie zostanie nazwany zbyt wiele, mógłbym pójść z tym. Lub w fabryce. Nie wiem jeszcze. –

17

najprostsze rozwiązanie Activator.CreateInstance<T>()

+0

Prosto do rzeczy! masz mój głos –

0

Ostatnio natknąłem się na bardzo podobnym problemem. Chciałem tylko podzielić się z Wami naszym rozwiązaniem. Chciałem I stworzył instancję Car<CarA> z obiektu json za pomocą którego miał ENUM:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>(); 

mapper.Add(1, typeof(CarA)); 
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class 
{  
    public T Detail { get; set; } 
    public Car(T data) 
    { 
     Detail = data; 
    } 
} 
public class CarA 
{ 
    public int PropA { get; set; } 
    public CarA(){} 
} 
public class CarB 
{ 
    public int PropB { get; set; } 
    public CarB(){} 
} 

var jsonObj = {"Type":"1","PropA":"10"} 
MyEnum t = GetTypeOfCar(jsonObj); 
Type objectT = mapper[t] 
Type genericType = typeof(Car<>); 
Type carTypeWithGenerics = genericType.MakeGenericType(objectT); 
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) }); 
-2

Jest jeszcze możliwość, o wysokiej wydajności, wykonując następujące czynności:

// 
    public List<R> GetAllItems<R>() where R : IBaseRO, new() { 
     var list = new List<R>(); 
     using (var wl = new ReaderLock<T>(this)) { 
      foreach (var bo in this.items) { 
       T t = bo.Value.Data as T; 
       R r = new R(); 
       r.Initialize(t); 
       list.Add(r); 
      } 
     } 
     return list; 
    } 

i

// 
///<summary>Base class for read-only objects</summary> 
public partial interface IBaseRO { 
    void Initialize(IDTO dto); 
    void Initialize(object value); 
} 

Właściwe klasy muszą następnie wyprowadzić z tego interfejsu i odpowiednio zainicjować. Należy zauważyć, że w moim przypadku ten kod jest częścią otaczającej klasy, która ma już <T> jako parametr ogólny. R, w moim przypadku, również jest klasą tylko do odczytu. IMO, publiczna dostępność funkcji Initialize() nie ma negatywnego wpływu na niezmienność. Użytkownik tej klasy może umieścić inny obiekt, ale nie zmodyfikuje podstawowej kolekcji.

Powiązane problemy