开发者

C# cannot call overloaded non-generic method from generic method

开发者 https://www.devze.com 2023-01-19 06:50 出处:网络
I have some legacy code with a method foo which has 700+ overloads: [DllImport(\"3rdparty.dll\")] protected static extern void foo(int len, ref structA o开发者_如何学Cbj);

I have some legacy code with a method foo which has 700+ overloads:

[DllImport("3rdparty.dll")]
protected static extern void foo(int len, ref structA o开发者_如何学Cbj);
[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...

I'd like to expose these overloaded methods through a single method using generics:

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

Unfortunately this returns the errors: "The best overloaded method match for 'foo(int, ref StructA)' has some invalid arguments" and "cannot convert from 'ref T' to 'ref StructA'".

Is there an elegant way to achieve this?


I was hoping that dynamic would help here, but it doesn't like the ref. Anyway, reflection should work:

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

Here's an optimized version that only does the reflection once; should be much faster:

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


First - since you have where T : new()
you can just state T obj = new T(); instead of T obj = Activator.CreateInstance<T>();
Now, for the other issue, have a lot of functions like this in one class is chaos.
I would define an interface

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

and make all the classes implement it. And then:

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


You can do this by taking care of the marshaling yourself instead of leaving it to the P/Invoke marshaller. Redeclare foo like this:

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

Which now allows you to define a generic method:

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

If perf is critical then you can speed it up by keeping the memory allocated by AllocCoTaskMem around, growing it only if needed. It isn't clear from your question whether the C functions updates the passed structure, you can omit the PtrToStructure call if it doesn't.


I'm afraid that you cannot use generics in a way you want to here. The reason is that the generic method needs to be compiled to IL and it needs to resolve the overload at compile time. At that point, it doesn't really know which overload to choose, because this is runtime information.

If you have as many overloads as you say, then I would really consider using some better abstraction. For example, implement your foo method as a member of some interface that is implemented by all the classes. If you provide more details, I'm sure people here can give advice on better design.

If you really need to do it this way, then you could probably use something like Dictionary<Type, SomeDelegate<int, obj> and store all foo methods in a dictionary. The callFoo method would simply perform a lookup:

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

Then the only problem would be, how to add all of them to the dictionary. You can probably do that simply by hand, in static constructor of each class or dynamically using reflection.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号