it-swarm.com.de

Allgemeine Einschränkungen, wobei T: struct und T: Klasse

Ich möchte folgende Fälle unterscheiden:

  1. Ein einfacher Werttyp (z. B. int)
  2. Ein nullwertfähiger Werttyp (z. B. int?)
  3. Ein Referenztyp (z. B. string) - optional wäre es mir egal, ob dieser (1) oder (2) oben zugeordnet wurde

Ich habe den folgenden Code entwickelt, der in den Fällen (1) und (2) gut funktioniert:

static void Foo<T>(T a) where T : struct { } // 1

static void Foo<T>(T? a) where T : struct { } // 2

Wenn ich jedoch versuche, case (3) wie folgt zu ermitteln, wird es nicht kompiliert:

static void Foo<T>(T a) where T : class { } // 3

Die Fehlermeldung lautet Typ 'X' definiert bereits einen Member namens 'Foo' mit denselben Parametertypen . Nun, irgendwie kann ich zwischen where T : struct und where T : class nicht unterscheiden.

Wenn ich die dritte Funktion (3) entferne, wird der folgende Code ebenfalls nicht kompiliert:

int x = 1;
int? y = 2;
string z = "a";

Foo (x); // OK, calls (1)
Foo (y); // OK, calls (2)
Foo (z); // error: the type 'string' must be a non-nullable value type ...

Wie kann ich Foo(z) zum Kompilieren bringen und es einer der oben genannten Funktionen zuordnen (oder einer dritten mit einer anderen Einschränkung, an die ich nicht gedacht habe)?

44
Pierre Arnaud

Einschränkungen sind nicht Teil der Signatur, jedoch Parameter. Während der Überlastauflösung werden Einschränkungen in Parametern erzwungen.

Also setzen wir die Einschränkung in einen Parameter. Es ist hässlich, aber es funktioniert.

class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }

static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3

(besser sechs Jahre später als nie?)

46
Alcaro

Sie können den aufzurufenden Methodentyp leider nicht nur aufgrund der Einschränkungen unterscheiden.

Sie müssen also eine Methode in einer anderen Klasse oder mit einem anderen Namen definieren.

Zusätzlich zu Ihrem Kommentar zu Marnixs Antwort können Sie durch ein wenig Nachdenken erreichen, was Sie wollen.

Im folgenden Beispiel verwendet die uneingeschränkte Methode Foo<T> die Aufrufe der entsprechenden eingeschränkten Methode - entweder FooWithStruct<T> oder FooWithClass<T> - durch Reflektion. Aus Leistungsgründen werden wir einen stark typisierten Delegaten erstellen und im Cache speichern, anstatt bei jedem Aufruf der Foo<T>-Methode die normale Reflexion zu verwenden.

int x = 42;
MyClass.Foo(x);    // displays "Non-Nullable Struct"

int? y = 123;
MyClass.Foo(y);    // displays "Nullable Struct"

string z = "Test";
MyClass.Foo(z);    // displays "Class"

// ...

public static class MyClass
{
    public static void Foo<T>(T? a) where T : struct
    {
        Console.WriteLine("Nullable Struct");
    }

    public static void Foo<T>(T a)
    {
        Type t = typeof(T);

        Delegate action;
        if (!FooDelegateCache.TryGetValue(t, out action))
        {
            MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
            action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
            FooDelegateCache.Add(t, action);
        }
        ((Action<T>)action)(a);
    }

    private static void FooWithStruct<T>(T a) where T : struct
    {
        Console.WriteLine("Non-Nullable Struct");
    }

    private static void FooWithClass<T>(T a) where T : class
    {
        Console.WriteLine("Class");
    }

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}

(Beachten Sie, dass dieses Beispiel nicht threadsicher ist. Wenn Sie Threadsicherheit benötigen, müssen Sie entweder alle Zugriffe auf das Cache-Wörterbuch sperren oder - wenn Sie ein Targeting durchführen können. NET4 - stattdessen ConcurrentDictionary<K,V> verwenden.)

10
LukeH

Löschen Sie die Struktureinschränkung für die erste Methode. Wenn Sie zwischen Werttypen und Klassen unterscheiden müssen, können Sie dazu den Typ des Arguments verwenden.

      static void Foo( T? a ) where T : struct
      {
         // nullable stuff here
      }

      static void Foo( T a )
      {
         if( a is ValueType )
         {
            // ValueType stuff here
         }
         else
         {
            // class stuff
         }
      }
5

Um meinen Kommentar zu LukeH zu verstärken, ist ein nützliches Muster, wenn Sie Reflection verwenden müssen, um verschiedene auf einem Typparameter basierende Aktionen aufzurufen (im Unterschied zum Typ einer Objektinstanz), eine private generische statische Klasse wie die folgende (dies Der genaue Code ist nicht getestet, aber ich habe das schon so gemacht):

 statische Klasse FooInvoker <T> 
 {
 public Aktion <Foo> theAction = configureAction; 
 void ActionForOneKindOfThing <TT> (TT-Parameter) Dabei gilt Folgendes: TT: thatKindOfThing, T 
 {
 ...
 } 
 void ActionForAnotherKindOfThing <TT> (TT-Parameter) Dabei gilt Folgendes: TT: thatOtherKindOfThing, T 
 {
 ...
 } 
 void configureAction (T param) 
 {
 ... Bestimmen Sie, was für ein Ding T ist, und setzen Sie "theAction" auf einen der 
 ... über Methoden. Dann ende mit ...
 theAction (param); 
 } 
} 

Beachten Sie, dass Reflection eine Ausnahme auslöst, wenn versucht wird, einen Delegaten für ActionForOneKindOfThing<TT>(TT param) zu erstellen, wenn TT den Einschränkungen dieser Methode nicht entspricht. Da das System den Typ von TT bei der Erstellung des Delegaten validiert hat, können Sie theAction ohne weitere Typprüfung sicher aufrufen. Beachten Sie auch, dass, wenn externer Code gilt:

 FooInvoker <T> .theAction (param); 

nur der erste Anruf erfordert eine Reflexion. Nachfolgende Aufrufe rufen den Delegierten einfach direkt auf.

2
supercat

Wenn Sie keine generischen Parameter benötigen und nur zur Kompilierzeit zwischen diesen 3 Fällen unterscheiden möchten, können Sie folgenden Code verwenden.

static void Foo(object a) { } // reference type
static void Foo<T>(T? a) where T : struct { } // nullable
static void Foo(ValueType a) { } // valuetype
1

Glücklicherweise ist dieses Durcheinander weniger von C # Version 7.3 erforderlich

Siehe Was ist neu in C # 7.3 - Es ist nicht sehr explizit, aber es scheint, dass während der Überlastauflösung die Wo-Argumente verwendet werden. 

Die Überlastauflösung hat jetzt weniger mehrdeutige Fälle

Siehe auch Auswahl der C # -Version in Ihrem Visual Studio-Projekt

Es wird immer noch Zusammenstöße mit Folgendem sehen

Foo(x);
...
static void Foo<T>(T a) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3

Wird aber richtig aufgelöst

Foo(x);
...
static void Foo<T>(T a, bool b = false) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3
0
Sprotty