Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

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?

like image 367
Iain Sproat Avatar asked Oct 11 '10 10:10

Iain Sproat


2 Answers

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;
    }
}
like image 132
Marc Gravell Avatar answered Oct 05 '22 08:10

Marc Gravell


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);
}
like image 33
Itay Karo Avatar answered Oct 05 '22 08:10

Itay Karo