Warum verbietet C # generische Attributtypen?

510

Dies führt zu einer Ausnahme bei der Kompilierung:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Mir ist klar, dass C # keine generischen Attribute unterstützt. Nach langem Googeln kann ich den Grund jedoch nicht finden.

Weiß jemand, warum generische Typen nicht abgeleitet werden können? Attribute ? Irgendwelche Theorien?

Bryan Watts
quelle
17
Sie könnten [Validates (typeof (string)] - Ich stimme zu, Generika wären schöner ...
ConsultUtah
20
Obwohl dies eine sehr späte Ergänzung dieser Frage ist, ist es traurig, dass nicht nur Attribute selbst, sondern auch abstrakte Attributklassen (die offensichtlich sowieso nicht als Attribute instanziiert werden können) nicht wie folgt zugelassen werden: Dies abstract class Base<T>: Attribute {}könnte verwendet werden, um Nicht- Attribute zu erstellen. generische abgeleitete Klassen wie diese:class Concrete: Base<MyType> {}
Lucero
88
Ich sehne mich nach generischen Attributen und Attributen, die Lambdas akzeptieren. Stellen Sie sich Dinge vor wie [DependsOnProperty<Foo>(f => f.Bar)]oder [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń
3
Dies wäre in einer Situation, der ich gerade begegnet bin, äußerst nützlich. Es wäre schön, ein LinkedValueAttribute zu erstellen, das einen generischen Typ akzeptiert und diesen Typ für den angegebenen tatsächlichen Wert erzwingt. Ich könnte es für Aufzählungen verwenden, um den "Standard" -Wert einer anderen Aufzählung anzugeben, der verwendet werden soll, wenn dieser Aufzählungswert ausgewählt wird. Mehrere dieser Attribute können für verschiedene Typen angegeben werden, und ich kann den benötigten Wert basierend auf dem benötigten Typ erhalten. Ich kann es so einrichten, dass Typ und Objekt verwendet werden, aber eine starke Eingabe wäre ein großes Plus.
KeithS
10
Wenn Ihnen ein wenig IL nichts ausmacht, sieht das vielversprechend aus .
Jarrod Dixon

Antworten:

358

Nun, ich kann nicht antworten, warum es nicht verfügbar ist, aber ich kann bestätigen, dass es sich nicht um ein CLI-Problem handelt. Die CLI-Spezifikation erwähnt es nicht (soweit ich sehen kann) und wenn Sie IL direkt verwenden, können Sie ein generisches Attribut erstellen. Der Teil der C # 3-Spezifikation, der dies verbietet - Abschnitt 10.1.4 "Klassenbasisspezifikation" gibt keine Rechtfertigung.

Die mit Anmerkungen versehene ECMA C # 2-Spezifikation enthält ebenfalls keine hilfreichen Informationen, obwohl sie ein Beispiel dafür enthält, was nicht zulässig ist.

Meine Kopie der kommentierten C # 3-Spezifikation sollte morgen eintreffen ... Ich werde sehen, ob das weitere Informationen liefert. Auf jeden Fall ist es definitiv eher eine Sprachentscheidung als eine Laufzeitentscheidung.

EDIT: Antwort von Eric Lippert (umschrieben): Kein besonderer Grund, außer um Komplexität sowohl in der Sprache als auch im Compiler für einen Anwendungsfall zu vermeiden, der nicht viel Wert hinzufügt.

Jon Skeet
quelle
139
"außer um Komplexität sowohl in der Sprache als auch im Compiler zu vermeiden" ... und das von den Leuten, die uns Co und Contravarianz geben ...
flq
254
"Anwendungsfall, der nicht viel Wert bringt"? Das ist eine subjektive Meinung, sie könnte mir viel Wert geben!
Jon Kruger
34
Was mich am meisten daran stört, dass ich diese Funktion nicht habe, ist, dass ich nicht in der Lage bin, Dinge wie [PropertyReference (x => x.SomeProperty)] zu tun. Stattdessen brauchst du magische Strings und typeof (), was meiner Meinung nach irgendwie scheiße ist.
Asbjørn Ulsberg
13
@John: Ich denke, Sie unterschätzen die Kosten für das Entwerfen, Spezifizieren, Implementieren und Testen einer neuen Sprachfunktion erheblich.
Jon Skeet
14
Ich möchte zur Verteidigung von @ Timwi nur hinzufügen, dass dies nicht der einzige Ort ist, an dem sie diskutiert werden , und die 13.000 Ansichten zu dieser Frage implizieren ein gesundes Interesse. Außerdem: Danke, dass Sie eine maßgebliche Antwort erhalten haben, Jon.
Jordan Gray
84

Ein Attribut schmückt eine Klasse zur Kompilierungszeit, aber eine generische Klasse erhält ihre endgültigen Typinformationen erst zur Laufzeit. Da das Attribut die Kompilierung beeinflussen kann, muss es zur Kompilierungszeit "vollständig" sein.

Weitere Informationen finden Sie in diesem MSDN-Artikel .

GalacticCowboy
quelle
3
Der Artikel wiederholt, dass sie nicht möglich sind, aber ohne Grund. Ich verstehe Ihre Antwort konzeptionell. Kennen Sie weitere offizielle Unterlagen zu diesem Thema?
Bryan Watts
2
Der Artikel behandelt die Tatsache, dass die IL weiterhin generische Platzhalter enthält, die zur Laufzeit durch den tatsächlichen Typ ersetzt werden. Der Rest wurde von mir abgeleitet ... :)
GalacticCowboy
1
Für das, was es wert ist, erzwingt VB dieselbe Einschränkung: "Klassen, die generisch sind oder in einem generischen Typ enthalten sind, können nicht von einer Attributklasse erben."
GalacticCowboy
1
In ECMA-334, Abschnitt 14.16 heißt es: "Konstante Ausdrücke sind in den unten aufgeführten Kontexten erforderlich, und dies wird in der Grammatik durch Verwendung eines konstanten Ausdrucks angegeben. In diesen Kontexten tritt ein Fehler bei der Kompilierung auf, wenn ein Ausdruck beim Kompilieren nicht vollständig ausgewertet werden kann. Zeit." Attribute sind in der Liste.
GalacticCowboy
4
Dies scheint durch eine andere Antwort widerlegt zu werden, die besagt, dass IL dies zulassen wird. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
22

Ich weiß nicht, warum es nicht erlaubt ist, aber dies ist eine mögliche Problemumgehung

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}
GeekyMonkey
quelle
3
Leider verlieren Sie die Eingabe während der Kompilierung, wenn Sie das Attribut verwenden. Stellen Sie sich vor, das Attribut erstellt etwas vom generischen Typ. Sie können es umgehen, aber es wäre schön; Es ist eines dieser intuitiven Dinge, von denen Sie überrascht sind, dass Sie sie nicht tun können, wie z. B. Varianz (derzeit).
Bryan Watts
14
Leider habe ich versucht, dies nicht zu tun, weshalb ich diese SO-Frage gefunden habe. Ich denke, ich muss mich nur mit Typof beschäftigen. Es fühlt sich wirklich wie ein schmutziges Schlüsselwort an, nachdem es Generika schon so lange gibt.
Chris Marisic
13

Dies ist nicht wirklich generisch und Sie müssen immer noch eine bestimmte Attributklasse pro Typ schreiben, aber Sie können möglicherweise eine generische Basisschnittstelle verwenden, um ein wenig defensiv zu codieren, weniger Code als sonst erforderlich zu schreiben, Vorteile des Polymorphismus zu erzielen usw.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}
nawfal
quelle
8

Das ist eine sehr gute Frage. Nach meiner Erfahrung mit Attributen, ich denke , die Einschränkung vorhanden ist , weil , wenn auf einem Attribut reflektieren würde es einen Zustand schaffen , in dem Sie für alle mögliche Typen Permutationen müßten überprüfen: typeof(Validates<string>),typeof(Validates<SomeCustomType>) usw ...

Wenn meiner Meinung nach je nach Typ eine benutzerdefinierte Validierung erforderlich ist, ist ein Attribut möglicherweise nicht der beste Ansatz.

Vielleicht wäre eine Validierungsklasse, die einen SomeCustomValidationDelegateoder einen ISomeCustomValidatorals Parameter verwendet, ein besserer Ansatz.

Ichiban
quelle
Ich stimme mit Ihnen ein. Ich habe diese Frage schon lange und baue derzeit ein Validierungssystem auf. Ich habe meine aktuelle Terminologie verwendet, um die Frage zu stellen, habe aber nicht die Absicht, einen auf diesem Mechanismus basierenden Ansatz zu implementieren.
Bryan Watts
Ich bin darauf gestoßen, als ich an einem Entwurf für dasselbe Ziel gearbeitet habe: Validierung. Ich versuche dies auf eine Weise zu tun, die sowohl automatisch automatisch zu analysieren ist (dh Sie könnten einen Bericht erstellen, der die Validierung in der Anwendung zur Bestätigung beschreibt) als auch durch einen Menschen, der den Code visualisiert. Wenn nicht Attribute, bin ich mir nicht sicher, was die beste Lösung wäre ... Ich könnte immer noch das Attributdesign ausprobieren, aber manuell typspezifische Attribute deklarieren. Es ist etwas mehr Arbeit, aber das Ziel ist die Zuverlässigkeit, die Validierungsregeln zu kennen (und sie zur Bestätigung melden zu können).
Bambams
4
Sie könnten mit den generischen Typdefinitionen vergleichen (dh typeof (Validates <>)) ...
Melvyn
5

Dies ist derzeit keine C # -Sprachenfunktion, es gibt jedoch viele Diskussionen über das offizielle C # -Sprach-Repo .

Aus einigen Besprechungsnotizen :

Obwohl dies im Prinzip funktionieren würde, gibt es in den meisten Versionen der Laufzeit Fehler, so dass es nicht richtig funktioniert (es wurde nie ausgeführt).

Wir brauchen einen Mechanismus, um zu verstehen, auf welcher Ziellaufzeit es funktioniert. Wir brauchen das für viele Dinge und schauen uns das derzeit an. Bis dahin können wir es nicht ertragen.

Kandidat für eine wichtige C # -Version, wenn wir eine ausreichende Anzahl von Laufzeitversionen dafür bereitstellen können.

Owen Pauling
quelle
1

Meine Problemumgehung ist ungefähr so:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }
Rasiermesser
quelle