Warum gibt es eine neue () Einschränkung in C #, aber keine andere ähnliche Einschränkung?

19

In C # -Generika können wir eine Einschränkung für einen Typparameter deklarieren T, um einen Standardkonstruktor zu haben, indem wir sagen where T : new(). Es sind jedoch keine anderen Arten von Einschränkungen wie diese gültig - new(string)zum Beispiel usw.

Was ist der Grund aus Sicht des Sprachdesigns und / oder der Implementierung?

Gibt es etwas in der Art und Weise, wie Konstruktoren arbeiten, oder in der Art und Weise, wie das Typsystem implementiert ist, das dies verbietet (oder zumindest erschwert)? Wenn ja, was ist das? Ich erinnere mich irgendwo zu lesen , die default(T)kompiliert eigentlich new T()für T : struct. Hat es vielleicht damit zu tun?

Oder ist es einfach eine gestalterische Entscheidung, um die Sprache nicht zu kompliziert zu machen?

Theodoros Chatzigiannakis
quelle
new(string)ist keine Standard-Konstruktor-Einschränkung. Ihre Frage lautet gleichbedeutend mit "Warum gibt es keine Einschränkungen, die bestimmte Konstruktorsignaturen erfordern?" Sehr wahrscheinlich, weil eine solche Einschränkung nicht besonders nützlich wäre.
Robert Harvey
3
@RobertHarvey Ja, genau das sage ich. Ich frage mich im Wesentlichen, ob es etwas gibt, das Standardkonstruktoren in Bezug auf die Implementierung besonders macht, oder ob es nur eine willkürliche Wahl war, dies und nicht das andere einzuschließen.
Theodoros Chatzigiannakis
Standardkonstruktoren sind auf bestimmte und wichtige Weise nützlich. Zum Beispiel machen sie einen Typ leicht serialisierbar.
Robert Harvey
6
Bestimmte ctor-Signaturen könnten nützlich sein. Wenn Ihre Typvariable beispielsweise auf Collection <T> mit einem ctor von T (Collection <T>) beschränkt ist, wissen Sie, dass Sie neue Collections mit einer anderen erstellen können. Ob dieser Nutzen jedoch die zusätzliche Komplexität wert ist, ist eine Frage.
Phoshi

Antworten:

5

Als verbindliche Antwort verweise ich auf Eric Lipperts Antwort auf diese Frage zu StackOverflow vor einigen Jahren, von der ein Ausschnitt im Folgenden kurz zitiert wird.

In diesem speziellen Fall kann ich Ihnen jedoch sicherlich einige Gründe nennen, warum ich das Feature zurückschieben würde, wenn es in einem Designtreffen als mögliches Feature für eine zukünftige Version der Sprache auftauchen würde.

...

Ich sage, wir sollten entweder das ganze Feature machen oder gar nicht. Wenn es wichtig ist, Typen auf bestimmte Konstruktoren einzuschränken, lassen Sie uns das gesamte Feature ausführen und die Typen auf der Basis von Mitgliedern im Allgemeinen und nicht nur von Konstruktoren einschränken.

Chris Hannon
quelle
2
Dieses aus dem Zusammenhang gerissene Zitat ist sehr irreführend. Es deutet darauf hin, dass Eric dachte, dass Microsoft "den ganzen Weg gegangen sein sollte", was überhaupt nicht seine Position ist. Er ist in der Tat eindeutig gegen das vorgeschlagene Merkmal.
Robert Harvey
1
Vielen Dank, dies beantwortet meine Frage teilweise: Gegen Ende seiner Antwort erwähnt Eric Lippert, dass es sich um eine Einschränkung der IL handelt und dass für die Aufnahme dieser Funktion eine Ergänzung der IL erforderlich wäre. Es wäre perfekt, wenn Sie eine Quelle für das bereitstellen könnten, was für den implementierten Teil IL generiert wird - das heißt, generisch einen Standardkonstruktor aufrufen.
Theodoros Chatzigiannakis
@TheodorosChatzigiannakis: Warum startest du nicht Teleriks Decompiler und findest es selbst heraus?
Robert Harvey
2
@RobertHarvey Ich denke nicht, dass es eine Position für ihn andeutet, aber ich habe ein zusätzliches Zitat beigefügt, um seine Position zu betonen.
Chris Hannon
1
Hmm, F # bietet erweiterte Möglichkeiten, einen Typ einzuschränken, z. B. als Kompilierungszeit zu prüfen, ob eine Klasse einen Operator hat. Möglicherweise benötigt F # ein leistungsfähigeres Constraint-System als C #, da die Typprüfung sehr streng ist. Nichtsdestotrotz ist diese Sprache in der Lage, erweiterte Methoden zum Beibehalten von Klassen in .NET Framework zu implementieren.
OnesimusUnbound
16

Das Dekompilieren (gemäß dem Vorschlag von Robert Harvey) ergab für alle Interessierten Folgendes. Diese Methode:

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

Anscheinend wird beim Kompilieren Folgendes:

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;
}
  • Wenn Tes sich um einen Werttyp handelt, new()wird default(T).
  • Wenn Tes sich um einen Referenztyp handelt, wird new()die Reflexion verwendet. Activator.CreateInstance()Intern anruft RuntimeType.CreateInstanceDefaultCtor().

Es ist also so - intern sind Standardkonstruktoren in Bezug auf die CLR wirklich etwas Besonderes für C #. Anderen Konstruktoren die gleiche Behandlung zu geben, wäre kostspielig gewesen, selbst wenn es einige gültige Anwendungsfälle für kompliziertere Einschränkungen in Generika gibt.

Theodoros Chatzigiannakis
quelle
4
Interessant. Warum der doppelte Aufruf default(T) für Werttypen?
Avner Shahar-Kashtan
2
@ AvnerShahar-Kashtan Ich weiß es nicht. Es kann ein Artefakt des Kompilierungs- / Dekompilierungsprozesses sein, wie die tVariable (die durch verschachtelte returnAnweisungen ersetzt werden könnte ).
Theodoros Chatzigiannakis