Dlaczego w języku C # istnieje nowe () ograniczenie, ale nie ma innego podobnego ograniczenia?

19

W generics C # możemy zadeklarować ograniczenie parametru typu Tdo domyślnego konstruktora, mówiąc where T : new(). Jednak żadne inne ograniczenia tego rodzaju nie są ważne - new(string)na przykład itp.

Z jakich powodów projekt i / lub implementacja języka jest tego przyczyną?

Czy jest coś w sposobie działania konstruktorów lub w sposobie implementacji systemu typów, który zabrania tego (a przynajmniej utrudnia)? Jeśli tak, co to jest? Pamiętam czytanie gdzieś, że default(T)faktycznie kompiluje się new T()do T : struct. Może to ma związek z tym?

Czy jest to po prostu decyzja projektowa podjęta w celu uniknięcia nadmiernego skomplikowania języka?

Theodoros Chatzigiannakis
źródło
new(string)nie jest domyślnym ograniczeniem konstruktora. Twoje pytanie jest równoznaczne ze stwierdzeniem „Dlaczego nie istnieją ograniczenia wymagające konkretnych podpisów konstruktorów?” Bardzo prawdopodobne, ponieważ takie ograniczenie nie byłoby szczególnie przydatne.
Robert Harvey
3
@RobertHarvey Tak, dokładnie to mówię. Zasadniczo pytam, czy jest coś, co sprawia, że ​​domyślne konstruktory są specjalne pod względem implementacji, czy to był tylko arbitralny wybór, aby uwzględnić to, a nie inne.
Theodoros Chatzigiannakis
Domyślne konstruktory są użyteczne na określone i ważne sposoby. Na przykład sprawiają, że typ jest łatwy do serializacji.
Robert Harvey
6
Przydatne mogą być określone podpisy ctor. Na przykład, jeśli twoja zmienna typu jest ograniczona do kolekcji <T> z ctor T (Kolekcja <T>), wiesz, że możesz konstruować nowe kolekcje, biorąc pod uwagę inną. Ale czy ta użyteczność jest warta dodatkowej złożoności, jest pytaniem.
Phoshi

Odpowiedzi:

5

Aby uzyskać wiarygodną odpowiedź, odsyłam do odpowiedzi Erica Lipperta na to pytanie na StackOverflow kilka lat temu, której fragment przytoczono poniżej.

Jednak w tym konkretnym przypadku z pewnością mogę podać kilka powodów, dla których odrzuciłbym tę funkcję, jeśli pojawił się na spotkaniu projektowym jako możliwa funkcja dla przyszłej wersji języka.

...

Mówię, że powinniśmy albo wykonać całą funkcję, albo w ogóle jej nie robić. Jeśli ważna jest możliwość ograniczania typów do określonych konstruktorów, wykonajmy całą funkcję i ogranicz typy na podstawie elementów w ogóle, a nie tylko konstruktorów.

Chris Hannon
źródło
2
Ten cytat, wyjęty z kontekstu, jest bardzo mylący. Sugeruje to, że Eric pomyślał, że Microsoft powinien „przejść całą drogę”, co wcale nie jest jego stanowiskiem. W rzeczywistości jest całkowicie przeciwny proponowanej funkcji.
Robert Harvey
1
Dzięki, to częściowo odpowiada na moje pytanie: pod koniec swojej odpowiedzi Eric Lippert wspomina, że ​​jest to ograniczenie IL, a włączenie tej funkcji wymagałoby uzupełnienia IL. Byłoby idealnie, gdyby mógł podać źródło na co IL jest generowany dla tej części, która jest realizowana - czyli ogólnie wywołanie konstruktora domyślnego.
Theodoros Chatzigiannakis
@TheodorosChatzigiannakis: Dlaczego nie można odpalić Telerik za Decompiler i znaleźć dla siebie?
Robert Harvey
2
@RobertHarvey Nie sądzę, że w ogóle sugeruje to jego pozycję, ale zamieściłem dodatkowy cytat, aby podkreślić jego pozycję.
Chris Hannon
1
Hmm, F # ma bardziej zaawansowane sposoby ograniczenia typu , takie jak sprawdzanie jako czas kompilacji, jeśli klasa ma operator. Być może F # potrzebuje silniejszego systemu ograniczeń niż C # ze względu na bardzo ścisłe sprawdzanie typu. Niemniej jednak ten język jest w stanie zaimplementować zaawansowane sposoby ograniczania klasy w .Net Framework.
Onesimus Bez ograniczeń
16

Dekompilacja (zgodnie z sugestią Roberta Harveya) dała następujące, dla wszystkich zainteresowanych. Ta metoda:

static T GenericMake<T>()
    where T : new()
{
    return new T();
}

Najwyraźniej po skompilowaniu staje się to:

private static T GenericMake<T>()
    where T : new()
{
    T t;
    T t1 = default(T);
    if (t1 == null)
    {
        t = Activator.CreateInstance<T>();
    }
    else
    {
        t1 = default(T);
        t = t1;
    }
    return t;
}
  • Jeśli Tjest typem wartości, new()staje się default(T).
  • Jeśli Tjest typem odniesienia, new()działa przy użyciu odbicia. Activator.CreateInstance()połączenia wewnętrzne RuntimeType.CreateInstanceDefaultCtor().

Tak więc jest - wewnętrznie domyślne konstruktory są naprawdę specjalne dla C # w stosunku do CLR. Takie samo traktowanie innych konstruktorów byłoby kosztowne, nawet jeśli istnieją pewne uzasadnione przypadki użycia dla bardziej skomplikowanych ograniczeń w rodzajach.

Theodoros Chatzigiannakis
źródło
4
Ciekawy. Po co zduplikowane wywołanie default(T) dla typów wartości?
Avner Shahar-Kashtan
2
@ AvnerShahar-Kashtan Nie wiem. Może to być artefakt procesu kompilacji / dekompilacji, na przykład tzmienna (którą można zastąpić zagnieżdżonymi returninstrukcjami).
Theodoros Chatzigiannakis