Heim  >  Artikel  >  Backend-Entwicklung  >  Überblick und spezifische Verwendungsmöglichkeiten von Generika

Überblick und spezifische Verwendungsmöglichkeiten von Generika

零下一度
零下一度Original
2017-06-23 16:32:421494Durchsuche

1. Übersicht über Generika

Generische Klassen und generische Methoden weisen sowohl Wiederverwendbarkeit, Typsicherheit als auch hohe Effizienz auf ist alles in einem, was außerhalb der Reichweite der entsprechenden nicht generischen Klassen und Methoden liegt. Generika werden häufig in Containern (Sammlungen) und Methoden verwendet, die mit Containern arbeiten. Die .NET Framework 2.0-Klassenbibliothek stellt einen neuen Namespace System.Collections.Generic bereit, der einige neue generische Containerklassen enthält.

  1. Generische Variablentypparameter: normalerweise T, aber es können auch alle Nicht-Schlüsselwörter und reservierten Wörter verwendet werden;

  2. Alle Variablentypen T liegen während der Kompilierung in Form von Platzhaltern vor und alle Punkthalter werden durch den tatsächlichen Typ ersetzt, der zur Laufzeit übergeben wird

2. Vorteile von Generika

Für frühe Versionen der Common Language Runtime und Einschränkungen der C#-Sprache bieten Generika eine Lösung. Die Verallgemeinerung früherer Typen wurde durch die Konvertierung des Typs in die globale Basisklasse System.Object erreicht. Die ArrayList-Containerklasse der .NET Framework-Basisklassenbibliothek ist ein Beispiel für diese Einschränkung. ArrayList ist eine sehr praktische Containerklasse, die jeden Referenztyp oder Werttyp speichern kann, ohne ihn während der Verwendung zu ändern.

ArrayList list = new ArrayList();
list.Add(1);
list.Add(175.50);
list.Add("hello kitty");

double sum = 0;
foreach(int value in list)
{
     sum += value;
}

ArrayList list = new ArrayList();
list.Add(1);
list.Add(175.50);
list.Add("hello kitty");

double sum = 0;

foreach(int value in list)
{
sum += value;
}

Nachteile: Bequemlichkeit hat ihren Preis, weshalb jeder zu ArrayList hinzugefügte Referenztyp oder Werttyp implizit in System.Object hochkonvertiert werden muss. Wenn es sich bei den Elementen um Werttypen handelt, müssen sie beim Hinzufügen zur Liste eingerahmt und beim Abruf entpackt werden. Typkonvertierungen sowie Boxing- und Unboxing-Vorgänge verringern alle die Leistung; die Auswirkungen des Boxings und Unboxings können erheblich sein, wenn große Container iteriert werden müssen. Eine weitere Einschränkung ist die fehlende Typprüfung zur Kompilierungszeit. Wenn eine ArrayList einen beliebigen Typ in ein Objekt konvertiert, können Fehler wie „sum+=vlaue“ im Kundencode zur Kompilierungszeit nicht verhindert werden List-Container im .Collections.Generic-Namespace wird derselbe Vorgang ausgeführt, um Elemente zum Container hinzuzufügen, ähnlich wie dieser:

    List listInt = new List();
     listInt.Add(100);
     listInt.Add(200);
     listInt.Add(123.112); //编译报错
     listInt.Add("heel");  //编译报错

    double sum = 0;
     foreach (int value in list)
     {
         sum += value;
     }

List listInt = new List();
listInt.Add(100);
listInt.Add(200);
listInt.Add(123.112); //Kompilierungsfehler
listInt.Add("heel"); //Kompilierungsfehler
double sum = 0;
foreach (int value in list)
                                         Summe += Wert;                                               , die einzige Ergänzung zur List-Syntax im Clientcode sind die Typparameter in der Deklaration und Instanziierung. Der Vorteil für etwas komplexeren Code besteht darin, dass die von Ihnen erstellte Tabelle nicht nur sicherer als eine ArrayList, sondern auch deutlich schneller ist, insbesondere wenn die Elemente in der Tabelle Werttypen sind.

3. Generische Typparameter

Bei der Definition eines generischen Typs oder einer generischen Methode ist der Typparameter ein Platzhalter, normalerweise ein Großbuchstabe (Sie können auch verwenden). alle Nicht-Schlüsselwörter und reservierten Wortnamen), wie z. B. T. Wenn der Clientcode eine Variable dieses Typs deklariert und instanziiert, ersetzen Sie T durch den vom Clientcode angegebenen Datentyp. Eine generische Klasse wie die in Generics angegebene List-Klasse kann nicht unverändert verwendet werden, da es sich nicht um einen echten Typ, sondern eher um eine Blaupause eines Typs handelt. Um MyList zu verwenden, muss der Clientcode einen konstruierten Typ deklarieren und instanziieren, indem er einen Typparameter in spitzen Klammern angibt. Die Typparameter dieser bestimmten Klasse können von jedem Typ sein, der vom Compiler erkannt wird. Es können beliebig viele Instanzen eines konstruierten Typs erstellt werden, die jeweils unterschiedliche Typparameter verwenden, wie folgt:

List listInt = new List();
List listFloat = new List();
List listString = new List();

List listInt = new List();
List listFloat = new List();
List list ) ;

4. Einschränkungen für generische Typparameter

Generika bieten die folgenden fünf Einschränkungen:

约束 描述
where T : struct 参数类型必须为值类型
where T : class 参数类型必须为引用类型
where T : new() 参数类型必须有一个公有的无参构造函数。当与其它约束联合使用时,new()约束必须放在最后。
where T : 参数类型必须为指定的基类型或派生自指定基类型的子类
where T : 参数类型必须为指定的接口或指定接口的实现。可指定多个接口的约束。接口约束也可以是泛型的。

Uneingeschränkte Typparameter:

  1. Kann != und == nicht zum Vergleichen von Instanzen veränderlicher Typen verwenden, da es keine Garantie für die Spezifität gibt. Typparameter unterstützen diese Operatoren ;

  2. Sie können in und aus System.Object oder explizit in einen beliebigen Schnittstellentyp konvertiert werden;

    kann mit null verglichen werden. Wenn ein unqualifizierter Typparameter mit null verglichen wird, ist das Ergebnis des Vergleichs immer falsch, wenn der Typparameter ein Werttyp ist.
  3. Keine Typeinschränkungen:
  4. Wenn die Einschränkung ein generischer Typparameter ist, wird sie als Nackttypeinschränkungen bezeichnet.

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:

    IDictionary

    IDictionary

    //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:
    Swap(a, b);

    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)

    tbody>

    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
    }

    void DoSomething() { }
    void DoSomething() { }
    void DoSomething() { }

    Generische Methoden sind mit mehreren Typparametern überladen. Beispielsweise können die folgenden Methoden in derselben Klasse platziert werden:

    Tabelle>

    8. Das Standardschlüsselwort in Generika

    1. 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:

    2. T wird ein Werttyp oder ein Referenztyp sein

    Wenn T ein Werttyp ist, dann ist T ein numerischer Wert oder eine Struktur

    void DoSomething() { }
    void DoSomething() { }
    void DoSomething() { }

    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.

Das obige ist der detaillierte Inhalt vonÜberblick und spezifische Verwendungsmöglichkeiten von Generika. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn