2016-09-12 29 views
10

W przedstawionym poniżej przykładowym kodzie metoda "CompileError" nie będzie kompilować, ponieważ wymaga ograniczenia where T : new(), jak pokazano w metodzie CreateWithNew(). Jednak metoda CreateWithActivator<T>() kompiluje się dobrze bez ograniczeń.Dlaczego funkcja Activator.CreateInstance <T>() jest dozwolona bez ograniczenia typu nowego()?

public class GenericTests 
{ 
    public T CompileError<T>() // compile error CS0304 
    { 
     return new T(); 
    } 

    public T CreateWithNew<T>() where T : new() // builds ok 
    { 
     return new T(); 
    } 

    public T CreateWithActivator<T>() // builds ok 
    { 
     return Activator.CreateInstance<T>(); 
    } 
} 

Dlaczego tak jest?

Według https://stackoverflow.com/a/1649108/531971, która odwołuje MSDN documentation i this question wyrażenie w rodzajowych new T() jest rzeczywiście realizowane użyciu Activator.CreateInstance<T>(). Tak więc nie rozumiem, dlaczego wywołanie new T() wymaga, aby typ ogólny był ograniczony w sposób, który można pominąć podczas korzystania z Activator.CreateInstance<T>().

Lub, aby postawić pytanie na odwrót: jaki jest sens ograniczenia where T : new(), jeśli łatwo utworzyć instancje T w metodzie ogólnej bez ograniczenia, bezpośrednio używając dokładnie tej samej infrastruktury bazowej?

+1

Ponieważ 'CreateInstance()' po prostu używa zwykłego starego odbicia i nie podlega żadnym ograniczeniom. Jeśli typ ma domyślny konstruktor, utworzy go. –

+1

To jest samokontrola. Możesz zrobić prawie wszystko poprzez refleksję (nawet zniszczyć wszystkie zasady OOP), ale wielu tego nie doceni, bo jest brudna. – eocron

+2

Istnieje wiele sposobów pisania niezręcznego kodu, który pozwala wyeliminować błąd czasu kompilacji i wygenerować zamiast niego środowisko wykonawcze. To tylko jedna instancja. Na przykład. 'dynamic' umożliwia pisanie wywołań do funkcji, które nie istnieją, więc zamiast błędu kompilatora pojawia się wyjątek środowiska wykonawczego. Podobnie, tutaj "CreateInstance" pozwala odłożyć, że nie istnieje konstruktor bez parametrów do czasu wykonania. –

Odpowiedz

12

Jest koncepcyjne różnica między Activator i T():

  • Activator.CreateInstance<T> - Chcę, aby utworzyć nową instancję T użyciu domyślnego konstruktora - I rzucić Exception jeśli nie masz (Ponieważ wydarzyło się coś bardzo złego i chcę sobie z tym poradzić/zrób to sam).

    • Notatka: pamiętać, że as MSDN says:

      Generalnie, nie ma zastosowania do CreateInstance<T>() generycznej metody w kodzie aplikacji, ponieważ typ musi być znany w czasie kompilacji. Jeśli typ jest znany w czasie kompilacji, można użyć normalnej składni instancji.

      ponieważ generalnie byłoby chcesz użyć konstruktora gdy Type jest znany w czasie kompilacji (CreateInstance<T>()uses RuntimeTypeHandle.CreateInstance który jest wolniejszy [Również dlatego sama Activator.CreateInstance<T> nie potrzebuje ograniczenie]).

  • T() - Chcę zadzwonić do pustego konstruktora T, podobno jak standardowe wywołanie konstruktora.

Nie chcą „standard” wezwanie konstruktor na niepowodzenie, ponieważ „Nie ma takiego konstruktor został znaleziony” zatem kompilator chce do przymusu, że jest jeden.

Co więcej; Powinieneś preferować błędy czasu kompilacji powyżej Exceptions gdzie to możliwe.

Fakt T() jest realizowane wewnętrznie przy użyciu odbicia znaczenia dla przeciętnego przypadku „Chcę tylko domyślnego wystąpienie T (oczywiście, że wdrożenie wewnętrznego jest ważne, jeśli zależy Ci na wydajności/etc ...).

2

To tylko cukier. Cukier samokontrolujący, jeśli możesz tak powiedzieć. Na przykład można wywołać przez odbicie prawie każdą metodę w dowolnym typie (w niektórych przypadkach nawet bez instancji!), Ale to po prostu nie jest właściwe, nie zgadzasz się? Twój kod stanie się w pewnym momencie nieosiągalny, a wiele błędów pojawi się w czasie wykonywania, jest to bardzo złe. Tak więc, jeśli możesz kontrolować siebie przed wykonaniem - po prostu to zrób.

Ograniczenie pomoże ci to zrozumieć podczas kompilacji.

1

Metoda Activator.CreateInstance<T>() jest narażona na kod użytkownika, aby umożliwić możliwość, że klasa generyczna może być użyteczna na różne sposoby, z których niektóre wymagałyby, aby parametr typu spełniał pewne ograniczenia, a niektóre nie. Na przykład, klasa Foo<T> może obsługiwać jeden z następujących wzorców użycia:

  1. kod Klient dostarcza funkcję, która zwraca nowy T.

  2. Kod klienta odwołuje się do domyślnej funkcji, która tworzy nowy T przy użyciu domyślnego konstruktora.

  3. Kod klienta zapobiega używaniu jakichkolwiek funkcji klasy, które wymagałyby utworzenia nowych wystąpień T.

Wzory nr 1 i nr 3 powinien być używany z dowolnym T, natomiast nr 2 powinien pracować tylko z typami mających konstruktorów bez parametrów. Posiadanie Activator.CreateInstance<T>() kompilacji dla nieograniczonego T i pracy dla typów T, które mają konstruktory bez parametrów, ułatwia kod obsługujący wszystkie trzy wzorce użycia. Jeśli Activator.CreateInstance<T> ma ograniczenie new, byłoby bardzo niewygodne, aby używać go z ogólnymi parametrami typu, które go nie mają.