c# generic - Übergibt explizit generische Typparameter an jede Schnittstelle




method where (3)

In Generics FAQ: Best Practices sagt:

Mit dem Compiler können Sie generische Typparameter explizit auf jede Schnittstelle, aber nicht auf eine Klasse umwandeln:

interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T> 
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;//Compiles
      SomeClass      obj2 = (SomeClass)t;     //Does not compile
   }
}

Ich sehe Einschränkungen sowohl für Klassen als auch für Schnittstellen, sofern die Klasse / Schnittstelle nicht als Einschränkungstyp angegeben ist.

Warum also solches Verhalten, warum ist es für Schnittstellen erlaubt?


Answers

Im Vererbungs-Prinzip von C # könnten Interfaces mehrfach vererbt werden, aber nur einmal. Da die Vererbung von Interfaces eine komplexe Hierarchie hat, muss das .NET-Framework den generischen Typ T zum Zeitpunkt der Kompilierung nicht auf eine bestimmte Schnittstelle absichern. (EDIT) Im Gegenteil, eine Klasse könnte mit einer Typ-Constraint deklariert werden bei der Kompilierung als der folgende Code.

class MyClass<T> where T : SomeClass
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;
      SomeClass      obj2 = (SomeClass)t;     
   }
}

Ich glaube, das liegt daran, dass die SomeClass in SomeClass abhängig von den verfügbaren Konvertierungen eine beliebige Anzahl von Dingen bedeuten kann, während die Umwandlung in ISomeInterface nur eine Referenzkonvertierung oder eine ISomeInterface kann.

Optionen:

  • Zuerst zum Objekt werfen:

    SomeClass obj2 = (SomeClass) (object) t;
    
  • Verwenden Sie stattdessen:

    SomeClass obj2 = t as SomeClass;
    

Offensichtlich müssen Sie im zweiten Fall auch einen Nichtigkeitscheck durchführen, wenn t kein SomeClass .

EDIT: Die Begründung hierfür ist in Abschnitt 6.2.7 der C # 4-Spezifikation gegeben:

Die obigen Regeln erlauben keine direkte explizite Konvertierung von einem unbeschränkten Typparameter in einen Nicht-Interface-Typ, was überraschend sein könnte. Der Grund für diese Regel besteht darin, Verwirrung zu vermeiden und die Semantik solcher Konvertierungen deutlich zu machen. Betrachten Sie zum Beispiel die folgende Deklaration:

class X<T>
{
    public static long F(T t) {
        return (long)t; // Error 
    }
} 

Wenn die direkte explizite Umwandlung von t in int zulässig wäre, könnte man leicht erwarten, dass X<int>.F(7) 7L zurückgeben würde. Dies würde jedoch nicht der Fall sein, da die numerischen Standardumrechnungen nur dann berücksichtigt werden, wenn bekannt ist, dass die Typen zur Bindungszeit numerisch sind. Um die Semantik zu verdeutlichen, muss stattdessen das obige Beispiel geschrieben werden:

class X<T>
{
    public static long F(T t) {
        return (long)(object)t; // Ok, but will only work when T is long
    }
}

Dieser Code kompiliert nun, führt aber X<int>.F(7) würde dann zur Laufzeit eine Ausnahme X<int>.F(7) , da ein boxed int nicht direkt in long konvertiert werden kann.


Sie können den Typ von "T" von jedem Auflistungstyp abrufen, der IEnumerable <T> mit Folgendem implementiert:

public static Type GetCollectionItemType(Type collectionType)
{
    var types = collectionType.GetInterfaces()
        .Where(x => x.IsGenericType 
            && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .ToArray();
    // Only support collections that implement IEnumerable<T> once.
    return types.Length == 1 ? types[0].GetGenericArguments()[0] : null;
}

Beachten Sie, dass es keine Sammlungsarten unterstützt, die IEnumerable <T> zweimal implementieren, z

public class WierdCustomType : IEnumerable<int>, IEnumerable<string> { ... }

Ich nehme an, Sie könnten eine Reihe von Typen zurückgeben, wenn Sie dies unterstützen müssten ...

Vielleicht möchten Sie auch das Ergebnis pro Sammlungsart zwischenspeichern, wenn Sie dies häufig tun (z. B. in einer Schleife).





c# .net generics clr