我有一些带有 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'“。
有没有一种优雅的方法来实现这一目标?
答案 0 :(得分: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;
}
}
答案 1 :(得分: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);
}
答案 2 :(得分:5)
您可以通过自己处理编组而不是将其留给P / Invoke编组来完成此操作。 Redeclare 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);
}
}
如果perf是关键的,那么你可以通过保持AllocCoTaskMem分配的内存来加速它,只在需要时增长它。从您的问题中不清楚C函数是否更新传递的结构,如果没有,则可以省略PtrToStructure调用。
答案 3 :(得分: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);
// ...
}
然后唯一的问题是,如何将所有这些添加到字典中。您可以在每个类的静态构造函数中手动执行此操作,也可以使用反射动态执行此操作。