2010-06-04 26 views
39

desidero distinguere seguenti casi:vincoli generici, dove T: struct e dove T: classe

  1. Un tipo valore normale (es int)
  2. Un tipo valore nullable (es int?)
  3. Un tipo di riferimento (es string) - opzionalmente, non mi importa se questo mappato (1) o (2) di cui sopra

ho fornito con il seguente c ode, che funziona bene per i casi (1) e (2):

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

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

Tuttavia, se provo a rilevare caso (3) come questo, non viene compilato:

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

L'errore il messaggio è Il tipo 'X' definisce già un membro chiamato 'Foo' con gli stessi tipi di parametro. Beh, in qualche modo non posso fare la differenza tra where T : struct e where T : class.

Se rimuovo la terza funzione (3), il seguente codice non compilato o:

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

Come posso ottenere Foo(z) per compilare, la mappatura ad una delle funzioni di cui sopra (o un terzo con un altro vincolo, a cui non ho pensato)?

+0

Per i tipi di riferimento c'è: new(), ma questo ha un comportamento strano con tipi di valore nullable. –

risposta

36

I vincoli non fanno parte della firma, ma i parametri lo sono. E i vincoli nei parametri vengono applicati durante la risoluzione del sovraccarico.

Quindi poniamo il vincolo in un parametro. È brutto, ma funziona.

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 

(? Migliori sei anni tardi che mai)

+2

Ha, ottima idea! In effetti, non è necessario aggiungere il parametro 'ignore' alla seconda funzione' Foo 'che richiede' T? '. –

+0

Questo mi ha dato l'opportunità di blog sull'argomento su http://code.fitness/post/2016/04/generic-type-resolution.html –

+0

Ho avuto l'idea da [uno dei post del blog di Eric Lippert] (https: //blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/). Mi sono sempre piaciuti gli imbrogli. Per quanto riguarda la T ?, la situazione in cui avevo bisogno di questo aveva solo i casi 1 e 3, e ho dimenticato di testare se fosse necessario. – Alcaro

17

Purtroppo non è possibile differenziare il tipo di metodo da chiamare in base solo ai vincoli.

Quindi è necessario definire un metodo in una classe diversa o con un nome diverso.

+2

+1. Certamente, il primo e il secondo lavoro perché 'T' e' T? 'Sono argomenti differenti. ('T' e' Nullable ') – Powerlord

+0

+1 Vedi: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature .aspx –

+0

Grazie per la rapida risposta; se non riesco a differenziare i tipi, c'è un modo per compilare il mio ultimo esempio, rilasciando qualche vincolo? –

5

Eliminare il contraint struct sul primo metodo. Se è necessario differenziare tra tipi di valore e classi, è possibile utilizzare il tipo di argomento per farlo.

 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 
     } 
     } 
+2

@Maxim: Grazie. Il problema che sto affrontando è che nel metodo non annullabile, devo essere in grado di invocare altre funzioni che prendono e restituiscono 'T?', E questo non è valido senza il vincolo 'where T: struct'. –

10

seguito al tuo commento su Marnix's answer, è possibile ottenere ciò che si vuole, utilizzando un po 'di riflessione.

Nell'esempio che segue, il metodo vincolato Foo<T> utilizza riflessione per coltivare le chiamate al metodo vincolato appropriato - sia FooWithStruct<T> o FooWithClass<T>. Per motivi di prestazioni, creeremo e memorizzeremo in cache un delegato fortemente tipizzato anziché utilizzare una semplice riflessione ogni volta che viene chiamato il metodo Foo<T>.

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>(); 
} 

(Si noti che questo esempio non è threadsafe Se avete bisogno di thread-sicurezza? Allora ti sia bisogno di utilizzare una sorta di blocco intorno a tutti l'accesso al dizionario cache oppure -. Se si' re in grado di target .NET4 - utilizzare invece ConcurrentDictionary<K,V>.

+1

Si potrebbe migliorare le cose utilizzando un approccio simile a "Comparatore . Difetto", ad es. creare una classe generica statica privata 'FooInvoker ' con un campo pubblico 'FooMethod' di tipo' Azione '(dato che' FooInvoker 'non sarebbe accessibile al di fuori di' MyClass' non ci sarebbe il rischio che il codice esterno abusi il campo pubblico)? Se il costruttore di classi per 'FooInvoker ' imposta 'FooMethod' appropriatamente, penso che potrebbe evitare la necessità di una ricerca di dizionario in fase di esecuzione (non so se.la rete dovrebbe eseguire uno internamente ogni volta che è stato chiamato 'Foo '). – supercat

+1

Vedere la mia risposta postata per una descrizione di come si dovrebbe usare una classe statica. Probabilmente ho fatto alcuni errori di sintassi, dato che sto scrivendo dalla memoria (e principalmente programma in vb.net), ma ci dovrebbe essere abbastanza di una struttura per farti andare. – supercat

2

Amplificando il mio commento a LukeH, uno schema utile se si deve utilizzare Reflection per richiamare azioni diverse basate su un parametro di tipo (distinto dal tipo di un'istanza di oggetto) è creare una classe statica generica privata qualcosa come il seguente (questo codice esatto non è testato, ma ho fatto questo genere di cose prima):

 
static class FooInvoker<T> 
{ 
    public Action<Foo> theAction = configureAction; 
    void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T 
    { 
    ... 
    } 
    void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T 
    { 
    ... 
    } 
    void configureAction(T param) 
    { 
    ... Determine which kind of thing T is, and set `theAction` to one of the 
    ... above methods. Then end with ... 
    theAction(param); 
    } 
} 

Nota che la riflessione sarà un'eccezione se si tenta di creare un delegato per ActionForOneKindOfThing<TT>(TT param) quando TT non è conforme con i vincoli di quel metodo. Poiché il sistema ha convalidato il tipo di TT quando il delegato è stato creato, è possibile richiamare in modo sicuro theAction senza ulteriore controllo del tipo. Si noti inoltre che se il codice esterno fa:

 
    FooInvoker<T>.theAction(param); 

solo la prima chiamata richiederà qualsiasi riflessione. Le chiamate successive invocheranno semplicemente il delegato direttamente.

1

Se non sono necessari parametri generici e si desidera solo distinguere tra questi 3 casi in fase di compilazione, è possibile utilizzare il codice seguente.

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