C # не может вызвать перегруженный неуниверсальный метод из универсального метода - PullRequest
16 голосов
/ 11 октября 2010

У меня есть какой-то устаревший код с методом 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 'to' ref StructA ' ".

Есть ли элегантный способ добиться этого?

Ответы [ 4 ]

8 голосов
/ 11 октября 2010

Я надеялся, что 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;
    }
}
5 голосов
/ 11 октября 2010

Вы можете сделать это, позаботившись о маршалинге самостоятельно, вместо того, чтобы оставить его маршаллеру 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);
        }
    }

Если perf важен, вы можете ускорить его, сохранив память, выделенную AllocCoTaskMem.вокруг, выращивая его только при необходимости.Из вашего вопроса не ясно, обновляет ли функция C переданную структуру, вы можете пропустить вызов PtrToStructure, если это не так.

5 голосов
/ 11 октября 2010

Первый - так как у вас есть 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 голосов
/ 11 октября 2010

Боюсь, что вы не можете использовать дженерики так, как вы хотите. Причина в том, что универсальный метод должен быть скомпилирован в 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);
   // ...
} 

Тогда единственная проблема состоит в том, как добавить их все в словарь. Вероятно, вы можете сделать это просто вручную, в статическом конструкторе каждого класса или динамически используя отражение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...