2011-10-14 9 views
5

Ho un'interfaccia con alcuni metodi generici e volevo implementare un metodo con overload per accettare un'istanza di una classe o il suo valore PK (che è o un int o GUID ma varia).Contaminazioni generiche sui sovraccarichi di metodo

ho aggiunto a metodi simili a questi esempi:

void DoSomething<TKey>(TKey key) where TKey: struct; 
    void DoSomething<TModel>(TModel model) where TModel : class; 

Il 'DoSomething' nome metodo sulla seconda di queste è evidenziato, e l'errore è

Tipo 'ISomeStuff' definisce già un membro chiamato "DoSomething" con gli stessi tipi di parametri.

Sono sorpreso da questo come ho chiaramente definito dai parametri di essere di tipo diverso: uno è una classe e l'altro una struttura.

Perché non è questo sufficiente per rendere le firme diverso?

+1

Eventuali duplicati di [vincoli generici, dove T: struct e dove T: classe] (http://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where- t-classe). Vedi anche l'articolo di Eric Lippert [qui] (http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are- no-part-of-the-signature.aspx). –

+0

@Frederic: come mi è mancato quello !!! –

+0

Apparentemente il riquadro "Correlati" nella barra laterale non l'ha selezionato, quindi potrebbe essere più complicato del solito;) –

risposta

3

Jon Skeet ha una risposta a tutto: click me

citazione:

le dichiarazioni si differenziano solo per i vincoli generici, e dei vincoli non fanno parte della firma

+1

Link aggiornato, per futuri lettori (Jon ha postato lo stesso articolo sul suo blog personale, qui): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ –

5

Is possibile farlo, è necessario creare qualcosa di simile enable_if da C++

public class ClassTag<V> where V : class { } 

public class StructTag<V> where V : struct { } 

public void Func<V>(V v, ClassTag<V> dummy = null) where V : class 
{ 
    Console.Writeln("class"); 
} 

public void Func<V>(V v, StructTag<V> dummy = null) where V : struct 
{ 
    Console.Writeln("struct"); 
} 

public void Func<V>(V? v, StructTag<V> dummy = null) where V : struct 
{ 
    Console.Writeln("struct?"); 
} 

static void Main() 
{ 
    Func("A"); 
    Func(5); 
    Func((int?)5); 
} 

Può essere espanso per utilizzare qualsiasi distanziale where per distinguere tra sovraccarichi. unico svantaggio è che Non può essere utilizzato all'interno di un altro metodo generico:

public static void Z1<T>(T t) // where T : class 
{ 
    Func(t); //error there 
} 

public static void Z2<T>(T t) where T : class 
{ 
    Func(t); //ok 
} 

modifica Ma v'è possibilità di utilizzo dynamic in questo caso per aggirare questa limitazione:

public static void Z1<T>(T t) 
{ 
    Func((dynamic)t); //if `T == int` it will call "struct" version 
} 

unico inconveniente il costo del tempo di esecuzione è simile a quello dell'invito all'indice Dictionary<,>.

+0

I love this risposta. –

1

Se si vuole invocare un membro genericamente indipendentemente dal fatto che ha un vincolo di classe o un vincolo struct e falla invocare un metodo con un vincolo adatto, si può definire un'interfaccia IThingUser<T> di agire su qualsiasi tipo T, lungo con una classe che la implementa per i tipi di valore e un'altra che la implementa per i tipi di classe. Avere una classe statica ThingUsers<T> con un campo statico TheUser di tipo IThingUser<T> e farlo compilare con un'istanza di una delle classi precedenti, quindi ThingUsers<T>.theUser sarà in grado di agire su qualsiasi tipo di T.

public static class GenTest93 
{ 
    public interface IThingUser<T> { void ActOnThing(T it); } 
    class StructUser<T> : IThingUser<T>, IThingUser<Nullable<T>> where T : struct 
    { 
     void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Struct {0}", typeof(T)); } 
     void IThingUser<Nullable<T>>.ActOnThing(T? it) { System.Diagnostics.Debug.Print("Struct? {0}", typeof(T)); } 
    } 
    class ClassUser<T> : IThingUser<T> where T : class 
    { 
     void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Class {0}", typeof(T)); } 
    } 
    static class ThingUsers<T> 
    { 
     class DefaultUser : IThingUser<T> 
     { 
      public void ActOnThing(T it) 
      { 
       Type t = typeof(T); 
       if (t.IsClass) 
        t = typeof(ClassUser<>).MakeGenericType(typeof(T)); 
       else 
       { 
        if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) 
         t = t.GetGenericArguments()[0]; 
        t = typeof(StructUser<>).MakeGenericType(t); 
       } 
       TheUser = (IThingUser<T>)Activator.CreateInstance(t); 
       TheUser.ActOnThing(it); 
      } 
     } 
     static IThingUser<T> TheUser = new DefaultUser(); 
     public static void ActOnThing(T it) {TheUser.ActOnThing(it);} 
    } 
    public static void ActOnThing<T>(T it) { ThingUsers<T>.ActOnThing(it); } 
    public static void Test() 
    { 
     int? foo = 3; 
     ActOnThing(foo); 
     ActOnThing(5); 
     ActOnThing("George"); 
    } 
} 

È necessario utilizzare Reflection per creare un'istanza di StructUser<T> o ClassUser<T> se il compilatore non sa che T soddisfi il vincolo necessaria, ma non è troppo duro.Dopo la prima volta, ActOnThing<T>() viene utilizzato per un particolare T, ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to ActOnThing(), quindi le prestazioni dovrebbero essere molto buone.

Nota che se dato un Nullable<T>, il metodo crea un StructUser<T> e l'inserisce in IThingUser<Nullable<T>>, piuttosto che cercare di creare un sometype<Nullable<T>>, dal momento che i tipi nullable stessi non soddisfano alcun vincolo.

1

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

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