Как я могу создать открытый делегат из метода экземпляра структуры? - PullRequest
4 голосов
/ 01 декабря 2010

У меня есть структура с закрытым методом, который я хотел бы вызвать. Так как я планирую сделать это в критическом разделе производительности, я бы хотел кэшировать делегата для выполнения действия. Проблема в том, что я не могу связать его метод с Delegate.CreateDelegate. Рассматриваемая структура не является моим созданием и используется во взаимодействии со сторонней библиотекой. Рассматриваемая структура выглядит следующим образом: *

public struct A
{
     private int SomeMethod()
     {
        //body go here
     }
}

И следующий код завершится с ошибкой «Ошибка привязки к целевому методу».

Delegate.CreateDelegate(typeof(Func<A,int>),typeof(A).GetMethod("SomeMethod",BindingFlags.Instance | BindingFlags.NonPublic));

Я знаю, что могу написать дерево выражений для выполнения действия, но кажется странным, что я не могу использовать свое обычное goto для этих вещей - метод Delegate.CreateDelegate.

Приведенный выше код прекрасно работает, если A был классом. Проблема возникает только потому, что A является структурой. Документация MSDN неверна для этой перегрузки CreateDelegate, поскольку она работает на нестатических методах.

Ответы [ 3 ]

8 голосов
/ 01 декабря 2010

Интересная проблема.Из этого отчета об ошибках, похоже, что это может быть ошибка, которая будет исправлена ​​в будущей версии .NET: http://connect.microsoft.com/VisualStudio/feedback/details/574959/cannot-create-open-instance-delegate-for-value-types-methods-which-implement-an-interface#details

РЕДАКТИРОВАТЬ: на самом деле, я думаю, что этот отчет об ошибке касается другой проблемы, поэтомуповедение, которое вы видите, может на самом деле не быть ошибкой.

Из этого отчета об ошибке я обнаружил, что существует обходной путь, если вы укажете первый аргумент вашего делегата как передаваемый по ссылке.Ниже приведен полный рабочий пример:

public struct A
{
    private int _Value;

    public int Value
    {
        get { return _Value; }
        set { _Value = value; }
    }

    private int SomeMethod()
    {
        return _Value;
    }
}

delegate int SomeMethodHandler(ref A instance);

class Program
{
    static void Main(string[] args)
    {
        var method = typeof(A).GetMethod("SomeMethod", BindingFlags.Instance | BindingFlags.NonPublic);

        SomeMethodHandler d = (SomeMethodHandler)Delegate.CreateDelegate(typeof(SomeMethodHandler), method);

        A instance = new A();

        instance.Value = 5;

        Console.WriteLine(d(ref instance));
    }
}

РЕДАКТИРОВАТЬ: Ответ Джона Скита здесь также обсуждает эту проблему.

1 голос
/ 01 декабря 2010

Первый параметр несвязанного делегата метода экземпляра не может быть типом значения. Это связано с тем, что типы значений должны обрабатываться при использовании в качестве параметров this. Вы не можете просто передать их по значению (как это произошло бы, если бы вы передавали тип значения в качестве первого параметра статического метода), поскольку тогда метод действует на копию, и любая мутация копии не влияет на оригинал .


ОБНОВЛЕНИЕ: Как отмечалось в другом ответе, типы значений при использовании в качестве параметров «this» эффективно передаются по ссылке.

0 голосов
/ 01 декабря 2010

Вы используете эту перегрузку CreateDelegate :

Создает делегат указанного типа для представления указанного статического метода.

SomeMethod не является статическим методом.

Используйте перегрузку , которая позволяет указать целевой объект :

A target = new A();

Func<int> f = (Func<int>)Delegate.CreateDelegate(
    typeof(Func<int>),
    target,
    typeof(A).GetMethod(
        "SomeMethod",
        BindingFlags.Instance | BindingFlags.NonPublic));

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

Лучшим решением, вероятно, является создание лямбда-выражения с использованием деревьев выражений LINQ:

var p = Expression.Parameter(typeof(A), "arg");
var lambda = Expression.Lambda<Func<A, int>>(
    Expression.Call(
        p,
        typeof(A).GetMethod(
            "SomeMethod",
            BindingFlags.Instance | BindingFlags.NonPublic)),
    p);

Func<A, int> f = lambda.Compile();
...