C#无法从泛型方法调用重载的非泛型方法

16

我有一些遗留代码,其中有一个名为foo的方法,它有700多个重载:

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

我希望使用泛型将这些重载方法通过单个方法公开:

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

不幸的是,这会返回错误:" foo(int, ref StructA)的最佳重载方法匹配有一些无效参数" 和 " 无法将'ref T'转换为'ref StructA'"。

有没有一种优雅的方法来实现这个?


类型classAclassB是否属于类继承体系?如果是,能否解释一下结构? - Oded
700 个重载?对于一个类来说相当大了。 - TalentTuner
哎呀,你确定要在这个上面再加一层复杂度吗?已经有700多种重载了吧? - Makach
2
@Makach - 有时候,让事情变得更简单的第一步是让它变得稍微复杂一些(例如添加一个间接层)。 - Oded
2
@Makach - 如果他有时间限制,现在无法重构呢?或者他正在尽力在重构之前对系统进行测试?你可以对事情做出空泛的陈述,但是在不知道其他限制的情况下,不要假设你的解决方案是最适合他的情况的。 - Oded
显示剩余3条评论
4个回答

8

我本希望使用 dynamic 来解决这个问题,但它不支持 ref。不过,可以使用反射来完成:

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

这是一个优化过的版本,只进行一次反射操作;速度应该会更快:
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;
    }
}

这对于实例方法很有效,但不幸的是对于静态方法(例如:protected static void foo(int len, ref classA obj){}),我会收到System.TypeInitializationException错误。(BindingFlags已从.Instance更改为.Static) - Iain Sproat
@sprocketonline - 要处理静态内容,您需要去掉arg0;所有内容都是静态的吗?还是两者都有混合? - Marc Gravell
所有这些都是静态的 - 它们都是对外部 C 库的 P/Invoke 调用。 - Iain Sproat
1
@sprocketonine - 我不在电脑旁边,但是:更改委托类型定义以删除arg0参数,将绑定更改为静态,并且不要将"this"传递到调用中。那应该就行了。 - Marc Gravell

5

首先 - 由于你有where T : new()
你可以直接声明T obj = new T();而不是T obj = Activator.CreateInstance<T>();
现在,对于另一个问题,在一个类中拥有很多这样的函数是混乱的。
我会定义一个接口

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

让所有的类都实现它。 然后:
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);
}

5

您可以自己处理编组而不是交给P/Invoke编组程序来完成。请将foo重新声明如下:

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

现在您可以定义一个通用方法:

现在允许您定义一个通用方法:

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

如果性能很重要,那么您可以通过保留由AllocCoTaskMem分配的内存来加速它,仅在需要时才增加它的大小。从您的问题中并不清楚C函数是否会更新传递的结构体,如果不是,则可以省略PtrToStructure调用。

是的,C函数确实会更新传递的结构体。这个解决方案看起来很优雅,但不幸的是会导致以下错误:"System.AccessViolationException: 尝试读取或写入受保护的内存。这通常表明其他内存已损坏。" - Iain Sproat
嗯,应该可以。你传递的是结构体还是类?如果你传递的是类对象,那么你需要一个指向指针的引用IntPtr。 - Hans Passant
好的,你说得对 - 我传递的是结构体。我已经更新了问题以反映这一点。 - Iain Sproat
1
糟糕,我颠倒了StructureToPtr/PtrToStructure的调用。代码片段已更新。 - Hans Passant

2
很抱歉,您无法在此处以所需的方式使用泛型。原因是通用方法需要编译为IL并在编译时解析重载。在那一点上,它真的不知道选择哪个重载,因为这是运行时信息。
如果您有像您所说的那样多的重载,那么我真的会考虑使用更好的抽象。例如,将您的foo方法实现为某个接口的成员,该接口由所有类实现。如果您提供更多细节,我相信这里的人们可以提供更好的设计建议。
如果您确实需要以这种方式进行操作,则可以使用类似于Dictionary<Type, SomeDelegate<int, obj>的东西,并将所有foo方法存储在字典中。 callFoo方法只需执行查找即可。
public void callFoo<T>(int len)  where T : new()
{ 
   T obj = Activator.CreateInstance<T>();
   fooDictionary[typeof(T)](len, obj);
   // ...
} 

那么唯一的问题就是如何将它们全部添加到字典中。你可以通过手动方式,在每个类的静态构造函数中完成,或者使用反射进行动态添加。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接