class List { void Add(List< U> Elemente), wobei U : T
Im obigen Beispiel ist T im Kontext der Add-Methode eine untypisierte Einschränkung, während T im Kontext der List-Klasse ein unbegrenzter Typparameter ist.
Untypisierte Einschränkungen können auch bei der Definition generischer Klassen verwendet werden. Beachten Sie, dass untypisierte Einschränkungen zusammen mit anderen Typparametern auch in spitzen Klammern deklariert werden müssen:
//Naked Type Constraint
public class MyClass< T,U,V> wobei T : V
Da der Compiler nur annimmt, dass untypisierte Einschränkungen von System.Object geerbt werden, haben generische Klassen mit untypisierten Einschränkungen nur sehr begrenzte Verwendungsmöglichkeiten. Verwenden Sie eine nicht typisierte Einschränkung für eine generische Klasse, wenn Sie eine Vererbungsbeziehung zwischen zwei Typparametern erzwingen möchten.
5. Generische Klassen
Generische Klassen kapseln Operationen, die für keinen bestimmten Datentyp spezifisch sind. Generische Klassen werden häufig in Containerklassen verwendet, z. B. verknüpften Listen, Hash-Tabellen, Stapeln, Warteschlangen, Bäumen usw. Vorgänge in diesen Klassen, wie das Hinzufügen und Entfernen von Elementen zum Container, führen unabhängig von der Art der gespeicherten Daten nahezu dieselben Vorgänge aus.
Normalerweise erstellen Sie eine generische Klasse aus einer vorhandenen konkreten Klasse und ändern den Typ nacheinander in Typparameter, bis das beste Gleichgewicht zwischen Allgemeingültigkeit und Benutzerfreundlichkeit erreicht ist. Beim Erstellen eigener generischer Klassen sind folgende wichtige Dinge zu beachten:
-
Welche Typen sollten auf Typparameter verallgemeinert werden? Als allgemeine Regel gilt: Je mehr Typen durch Parameter dargestellt werden, desto größer ist die Flexibilität und Wiederverwendbarkeit des Codes. Zu viel Verallgemeinerung kann dazu führen, dass der Code für andere Entwickler schwer verständlich ist. -
Wenn Einschränkungen vorliegen, welche Einschränkungen für die Typparameter erforderlich sind. Eine gute Praxis besteht darin, die größten Einschränkungen zu verwenden und gleichzeitig sicherzustellen, dass alle Arten verarbeitet werden müssen. Wenn Sie beispielsweise wissen, dass Ihre generische Klasse nur Referenztypen verwenden wird, wenden Sie die Einschränkungen für diese Klasse an. Dies verhindert die versehentliche Verwendung von Werttypen, und Sie können den as-Operator für T verwenden und nach Nullreferenzen suchen.
-
Fügen Sie generisches Verhalten in die Basisklasse ein eine Unterkategorie. Als Basisklassen können generische Klassen verwendet werden. Dies sollte auch bei der Gestaltung nicht-generischer Klassen berücksichtigt werden. Vererbungsregeln für generische Basisklassen; Um beispielsweise eine Klasse zu entwerfen, die Elemente in einem generischen Container erstellt, möchten Sie möglicherweise eine Schnittstelle wie IComparable implementieren, wobei T ein Parameter der Klasse ist.
-
Für eine generische Klasse Node kann der Clientcode entweder einen Typparameter angeben, um einen geschlossenen konstruierten Typ (Node) zu erstellen, oder Retainable Typparameter werden nicht angegeben, z. B. die Angabe einer generischen Basisklasse zum Erstellen eines offenen konstruierten Typs (Node). Generische Klassen können von konkreten Klassen, geschlossenen konstruierten Typen oder offenen konstruierten Typen erben:
// konkreter Typ Klasse Node: BaseNode //geschlossener konstruierter Typ Klasse Node: BaseNode //offener konstruierter Typ Klasse Node : BaseNode |
Nicht-generische konkrete Klassen können von geschlossenen Konstruktor-Basisklassen erben, jedoch nicht geerbt aus der offenen Konstruktor-Basisklasse. Dies liegt daran, dass der Clientcode die für die Basisklasse erforderlichen Typparameter nicht bereitstellen kann:
// concrete type class Node : BaseNode //closed constructed type class Node : BaseNode //open constructed type class Node : BaseNode | // Kein Fehler. Klassenknoten: Bassenode & lt; int & gt; // generiert einen Fehler. table>Generische konkrete Klassen können von offenen konstruierten Typen erben. Mit Ausnahme von Typparametern, die mit Unterklassen geteilt werden, muss der Typ für alle Typparameter angegeben werden:
//No error. class Node : BaseNode //Generates an error. class Node : BaseNode | / /Generiert einen Fehler. class Node }
|
Eine generische Klasse, die von einem offenen Strukturtyp geerbt wurde, Parametertypen und Einschränkungen müssen angegeben werden: //Generates an error. class Node : BaseNode {…} //Okay. class Node : BaseNode {…}
|
class NodeItem where T : IComparable, new() {…} Klasse MYNODEIM & lt; T & Gt; 🎜>Generische Typen können eine Vielzahl von Typparametern und Einschränkungen verwenden:
class NodeItem where T : IComparable, new() {…} class MyNodeItem : NodeItem where T : IComparable, new() {…}
| class KeyType< ;K, V> {…} class SuperKeyType wobei U : IComparable, wobei V : new() {…}
|
Offene und geschlossene konstruierte Typen können als Argumente für Methoden verwendet werden:
void Swap(List list1, List list2) {…} void Swap(List list1, List list2) {…} | void Swap(List list1, List list2) {…} void Swap(List list1, List list2) {…}
| 6. Generische Schnittstelle Wenn eine Schnittstelle als Einschränkung eines Typparameters angegeben wird, nur die Schnittstelle die die Interface-Typen implementiert, können als Typparameter verwendet werden.
Sie können wie folgt mehrere Schnittstellen als Einschränkungen für einen Typ angeben:
class Stack where T : IComparable, IMyStack1{} |
class Stack wobei T : IComparable, IMyStack1{} | Eine Schnittstelle kann wie folgt mehrere Typparameter definieren:
//Okay. IMyInterface: IBaseInterface //Okay. IMyInterface : IBaseInterface //Okay. IMyInterface: IBaseInterface //Error. IMyInterface : IBaseInterface2
| Die Vererbungsregeln von Schnittstellen und Klassen sind die gleichen: //Okay. IMyInterface: IBaseInterface //Okay. IMyInterface : IBaseInterface//Okay. IMyInterface: IBaseInterface //Fehler. IMyInterface : IBaseInterface2 td> |
class MyClass : IBaseInterface |
Konkrete Klassen können geschlossene Konstruktionsschnittstellen wie folgt implementieren:
class MyClass : IBaseInterface//Okay. class MyClass : IBaseInterface //Okay. class MyClass : IBaseInterface |
|
Generische Klassen kann eine generische Schnittstelle oder eine geschlossen konstruierte Schnittstelle implementieren, solange die Parameterliste der Klasse alle für die Schnittstelle erforderlichen Parameter wie folgt bereitstellt:
//Okay. class MyClass , string> |
Generische Klassen, generische Strukturen und generische Schnittstellen haben alle dieselben Methodenüberladungsregeln.
void Swap(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; }
| 7. Generische Methoden Generische Methoden sind Methoden, die Typparameter wie folgt deklarieren:
int a = 1; int b = 2; //… Swap(a, b);
|
void Swap(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; >
| Der folgende Beispielcode zeigt ein Beispiel für den Aufruf einer Methode mit int als Typparameter:
int a = 1; int b = 2; //… Swap(a, b);
Sie können den Typparameter auch ignorieren und der Compiler wird ihn ableiten. Der folgende Code zum Aufrufen von Swap entspricht dem obigen Beispiel:
class List { void Swap(ref T lhs, ref T rhs) { ... } }
|
Statische Methoden und Instanzmethoden haben dieselben Typinferenzregeln. Der Compiler kann Typparameter basierend auf den übergebenen Methodenparametern ableiten; er kann nicht allein anhand von Einschränkungen oder Rückgabewerten bestimmt werden. Daher ist die Typinferenz für Methoden ohne Parameter ungültig. Die Typinferenz erfolgt zur Kompilierungszeit, bevor der Compiler überladene Methodenflags auflöst. Der Compiler wendet die Typinferenzlogik auf alle generischen Methoden mit demselben Namen an. Während der Überladungsauflösungsphase schließt der Compiler nur die generischen Klassen ein, für die die Typinferenz erfolgreich war.
class List { void Swap(ref T lhs, ref T rhs) { } }
warning CS0693: 类型参数“T”与外部类型“List”中的类型参数同名
| In einer generischen Klasse können nicht generische Methoden auf Typparameter in der Klasse zugreifen: class List { void Swap(ref T lhs, ref T rhs) { ... />>
Definieren Sie in einer generischen Klasse eine generische Methode mit denselben Typparametern wie die Klasse, in der sie sich befindet. Versuchen Sie dies , generiert der Compiler die Warnung CS0693. class List { void Swap (ref T lhs, ref T rhs) { } > Warnung CS0693: Typparameter „T“ hat denselben Namen wie ein Typparameter im externen Typ „List“ „ |
Definieren Sie in einer generischen Klasse eine generische Methode, um einen undefinierten Typparameter in der generischen Klasse zu definieren: (nicht häufig verwendet, im Allgemeinen mit Einschränkungen verwendet)
class List { void Swap(ref T lhs, ref T rhs) { } //不常用
void Add(List items) where U : T{} //常用 }
| class List { void Swap(ref T lhs, ref T rhs) { } //Nicht häufig verwendet void Add(List items) where U : T{} //Commonly used }
| tbody>
void DoSomething() { } void DoSomething() { } void DoSomething() { }
| Generische Methoden sind mit mehreren Typparametern überladen. Beispielsweise können die folgenden Methoden in derselben Klasse platziert werden:
void DoSomething() { } void DoSomething() { } void DoSomething() { }
| Tabelle> 8. Das Standardschlüsselwort in Generika
- Ein Problem, das in generischen Klassen und generischen Methoden auftreten wird, ist das Festlegen des Standardwerts bei der Zuweisung zu einer parametrisierten Klasse Typ, die folgenden zwei Punkte können nicht im Voraus bekannt sein:
- T wird ein Werttyp oder ein Referenztyp sein
Wenn T ein Werttyp ist, dann ist T ein numerischer Wert oder eine Struktur
class GenericClass { T GetElement() { return default(T); } } | Für eine Variable t vom parametrisierten Typ T, Die t = null-Anweisung ist nur zulässig, wenn T ein Referenztyp ist; t = 0 ist nur für Werte gültig, nicht für Strukturen. Die Lösung für dieses Problem besteht darin, das Schlüsselwort default zu verwenden, das für Referenztypen null und für Werttypen null zurückgibt. Bei Strukturen wird jedes Mitglied der Struktur zurückgegeben, und zwar null oder null, je nachdem, ob es sich bei dem Mitglied um einen Werttyp oder einen Referenztyp handelt.
|
|
|