С# не может вызывать перегруженный не общий метод из общего метода
У меня есть код устаревшего кода с методом 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 ".
Есть ли элегантный способ достичь этого?
Ответы
Ответ 1
Я надеялся, что 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;
}
}
Ответ 2
Сначала - так как у вас есть 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);
}
Ответ 3
Вы можете сделать это, заботясь о том, чтобы маршировать самостоятельно, а не оставлять его на маршаллере 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);
}
}
Если первич является критическим, вы можете ускорить его, сохранив память, выделенную AllocCoTaskMem, и увеличивайте ее только при необходимости. Из вашего вопроса неясно, обновляет ли C-структуру переданную структуру, вы можете опустить вызов PtrToStructure, если это не так.
Ответ 4
Я боюсь, что вы не можете использовать дженерики так, как вы хотите здесь. Причина в том, что общий метод должен быть скомпилирован в 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);
// ...
}
Тогда единственной проблемой было бы, как добавить все слова в словарь. Возможно, вы можете сделать это просто вручную, в статическом конструкторе каждого класса или динамически используя отражение.