2010-10-11 28 views
15

tengo algo de código heredado con un método foo que tiene más de 700 sobrecargas:C# No se puede llamar sobrecargado método no genérico del método genérico

[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structA obj); 
[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structB obj); 
[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structC obj); 
//and 700 similar overloads for foo... 

me gustaría exponer estos métodos sobrecargados través de un único método que utiliza los genéricos:

public void callFoo<T>(int len) 
    where T : new() //ensure an empty constructor so it can be activated 
{ 
    T obj = Activator.CreateInstance<T>(); //foo expects obj to be empty, and fills it with data 
    foo(len, ref obj); 

    //...do stuff with obj... 
} 

Desafortunadamente, este devuelve los errores: "El partido mejor método sobrecargado para 'foo (int, ref Structa)' tiene algunos argumentos no válidos" y "no se puede convertir de 'ref T' a 'ref StructA' ".

¿Hay una manera elegante de lograr esto?

+0

¿Los tipos 'classA',' classB' forman parte de una jerarquía de clases? Si es así, ¿puedes explicar la estructura? – Oded

+0

700 sobrecargas? Bastante grande para una clase – TalentTuner

+0

Uhu, ¿700 sobrecargas? ¿Seguro que quieres agregar otra capa de complejidad en eso? – Makach

Respuesta

8

Esperaba que dynamic ayudaría aquí, pero no le gusta el ref. De todos modos, la reflexión debería funcionar:

public T callFoo<T>(int len) 
    where T : new() //ensure an empty constructor so it can be activated 
{ 
    T obj = new T(); 
    GetType().GetMethod("foo", BindingFlags.Instance | BindingFlags.NonPublic, 
     null, new[] { typeof(int), typeof(T).MakeByRefType() }, null) 
     .Invoke(this, new object[] { len, obj }); 
    return obj; 
} 

Aquí es una versión optimizada que sólo hace el reflejo de una vez; debe ser mucho más rápido:

class Test 
{ 

    protected void foo(int len, ref classA obj){} 
    protected void foo(int len, ref classB obj){ } 
    protected void foo(int len, ref classC obj){} 
    static readonly Dictionary<Type, Delegate> functions; 
    delegate void MyDelegate<T>(Test arg0, int len, ref T obj); 
    static Test() 
    { 
     functions = new Dictionary<Type, Delegate>(); 
     foreach (var method in typeof(Test).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) 
     { 
      if (method.Name != "foo") continue; 
      var args = method.GetParameters(); 
      if (args.Length != 2 || args[0].ParameterType != typeof(int)) continue; 
      var type = args[1].ParameterType.GetElementType(); 
      functions[type] = Delegate.CreateDelegate(
       typeof(MyDelegate<>).MakeGenericType(type), method); 
     } 
    } 
    public T callFoo<T>(int len) 
     where T : new() //ensure an empty constructor so it can be activated 
    { 
     T obj = new T(); 
     Delegate function; 
     if (!functions.TryGetValue(typeof(T), out function)) throw new NotSupportedException(
      "foo is not supported for " + typeof(T).Name); 
     ((MyDelegate<T>)function)(this, len, ref obj); 
     return obj; 
    } 
} 
+0

Esto funciona bien, por ejemplo, en métodos, pero desafortunadamente no para métodos estáticos, es decir, protected void foo (int len, ref classA obj) {}, obtengo un error System.TypeInitializationException. (los BindingFlags se han cambiado de .Instance a .Static ya) –

+0

@sprocketonline - para manejar estáticos necesitarás sacar el arg0; son todos estáticos? ¿O hay una mezcla de ambos? –

+0

todos son estáticos; son todos llamadas P/Invoke a la biblioteca C externa. –

5

En primer lugar - ya que tienes where T : new()
sólo se puede afirmar T obj = new T(); en lugar de T obj = Activator.CreateInstance<T>();
Ahora, por el otro tema, tienen una gran cantidad de funciones como esto en una clase es el caos.
Yo definiría una interfaz

public interface IFoo 
{ 
    void foo(int len); 
} 

y hacer todas las clases implementan. Y luego:

public void callFoo<T>(int len) 
    where T : IFoo, new() //ensure an empty constructor so it can be activated 
{ 
    T obj = new T(); 
    obj.foo(len); 
} 
2

Me temo que no se puede utilizar los genéricos en una forma en que desea aquí. La razón es que el método genérico debe compilarse en IL y debe resolver la sobrecarga en tiempo de compilación. En ese momento, realmente no sabe qué sobrecarga elegir, porque esta es información de tiempo de ejecución.

Si tiene tantas sobrecargas como dice, entonces realmente consideraría usar una mejor abstracción. Por ejemplo, implemente su método foo como miembro de alguna interfaz implementada por todas las clases. Si proporciona más detalles, estoy seguro de que la gente de aquí puede dar consejos sobre un mejor diseño.

Si realmente necesita hacerlo de esta manera, entonces probablemente podría usar algo como Dictionary<Type, SomeDelegate<int, obj> y almacenar todos los métodos foo en un diccionario. El método callFoo simplemente realizar una búsqueda:

public void callFoo<T>(int len) where T : new() 
{ 
    T obj = Activator.CreateInstance<T>(); 
    fooDictionary[typeof(T)](len, obj); 
    // ... 
} 

A continuación, el único problema sería, cómo añadir todos ellos al diccionario. Probablemente puedas hacer eso simplemente a mano, en un constructor estático de cada clase o dinámicamente usando la reflexión.

5

Usted puede hacer esto mediante el cuidado del cálculo de referencias a sí mismo en lugar de dejarlo a la P/Invoke marshaller.foo redeclare así:

[DllImport("3rdparty.dll")] 
    private static extern void foo(int len, IntPtr obj); 

que ahora permite definir un método genérico:

protected void foo<T>(ref T obj) { 
     int len = Marshal.SizeOf(obj); 
     IntPtr mem = Marshal.AllocCoTaskMem(len); 
     try { 
      Marshal.StructureToPtr(obj, mem, false); 
      foo(len, mem); 
      // Optional: 
      obj = (T)Marshal.PtrToStructure(mem, typeof(T)); 
     } 
     finally { 
      Marshal.FreeCoTaskMem(mem); 
     } 
    } 

Si Potencia es crítica, entonces puede acelerar manteniendo la memoria asignada por AllocCoTaskMem alrededor, su cultivo solo si es necesario No queda claro, a partir de su pregunta, si las funciones C actualizan la estructura pasada, puede omitir la llamada PtrToStructure si no lo hace.

+0

Sí, la función C actualiza la estructura pasada. Esta solución parece elegante, pero desafortunadamente da como resultado el siguiente error: "System.AccessViolationException: Intentó leer o escribir en la memoria protegida. Esto a menudo indica que otra memoria está dañada". –

+0

Hmm, debería funcionar. ¿Estas estructuras o clases estás pasando? Si pasa objetos de clase, entonces necesita un puntero a un puntero, ref IntPtr. –

+0

Buen punto: sería estructuras que estoy pasando. He actualizado la pregunta para reflejar eso. –

Cuestiones relacionadas