2011-10-14 14 views
5

Tengo una interfaz con algunos métodos genéricos, y quería implementar un método con sobrecargas para aceptar una instancia de una clase, o su valor PK (que es un int o GUID pero no varía).Contraints genéricos en sobrecargas de métodos

I añaden a métodos similares a estos ejemplos:

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

se destaca El 'HacerAlgo' nombre de método en el segundo de estos, y el error se

Type 'ISomeStuff' ya define un miembro llamado 'DoSomething' con los mismos tipos de parámetros.

Estoy sorprendido por esto ya que los parámetros me han definido claramente como de tipo diferente: uno es una clase y el otro una estructura.

¿Por qué no es esto suficiente para hacer que las firmas sean diferentes?

+1

duplicado posible de [limitaciones genéricas, donde T: struct y donde T: Clase] (http://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where- clase t). Véase también el artículo de Eric Lippert [aquí] (http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx). –

+0

@Frederic: ¡¿Cómo lo extrañé ?! –

+0

Aparentemente, el panel "Relacionado" en la barra lateral tampoco lo recogió, por lo que podría ser más complicado de lo normal;) –

Respuesta

3

Jon Skeet tiene una respuesta para todo: click me

cita:

las declaraciones sólo se diferencian en las limitaciones genéricas, y las limitaciones no son parte de la firma

+1

Enlace actualizado, para lectores futuros (Jon publicó el mismo artículo en su blog personal, aquí): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ –

5

Es es posible hacerlo, necesita crear algo como enable_if de 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); 
} 

Puede ampliarse para usar cualquierdisjunto para distinguir entre sobrecargas. El único inconveniente es que no puede ser utilizado dentro de otro método genérico:

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 
} 

edición Pero no hay posibilidad de uso dynamic en ese caso a evitar esta limitación:

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

El único inconveniente el costo de tiempo de ejecución es similar al llamado al índice Dictionary<,>.

+0

Me encanta esto responder. –

1

Si uno desea invocar un miembro genéricamente independientemente de si se tiene una restricción de clase o una restricción struct, y tienen que invocar un método con una restricción adecuada, se puede definir una interfaz IThingUser<T> para actuar sobre cualquier tipo T, junto con uno una clase que lo implementa para tipos de valores y otro que lo implementa para tipos de clase. Tener una clase estática ThingUsers<T> con un campo estático TheUser del tipo IThingUser<T>, y llenarlo con una instancia de una de las clases anteriores, y luego ThingUsers<T>.theUser podrá actuar sobre cualquier tipo de 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"); 
    } 
} 

Es necesario utilizar la reflexión para crear una instancia de StructUser<T> o ClassUser<T> si el compilador no sabe que T satisface la restricción es necesario, pero no es demasiado difícil.Después de la primera vez que se usa ActOnThing<T>() para un determinado T, ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to ActOnThing(), el rendimiento debería ser muy bueno.

Tenga en cuenta que si se les da un Nullable<T>, el método crea un StructUser<T> y lo convierte en IThingUser<Nullable<T>>, en lugar de tratar de crear un sometype<Nullable<T>>, ya que los tipos anulables mismos no responden a ningún tipo de limitación.

1

Si no necesita parámetros genéricos y solo quiere diferenciar estos casos en tiempo de compilación, puede usar el siguiente código.

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