Создайте статический делегат из нестатического метода - PullRequest
4 голосов
/ 03 ноября 2010

Мне нужно создать делегата для нестатического метода класса.Сложности в том, что во время создания у меня нет интанса для класса, только его определение класса.Во время разговора у меня есть экземпляр под рукой.Таким образом, мне нужен способ:

  1. Создание «неполного» делегата для метода-члена, без экземпляра.
  2. Вызов делегата из 1, явно передавая экземпляр класса.1006 *

Возможны ли оба варианта?Как?Примечание: я готов заплатить высокую цену за номер один, но в идеале 2 не должен быть намного дороже, чем вызов делегата.

Ответы [ 5 ]

4 голосов
/ 03 ноября 2010

Что плохого в том, чтобы просто передать экземпляр таким образом?

// Creation.
Action<Foo> bar = foo =>
{
    foo.Baz();
};

// Invocation.
bar(new Foo());

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

Редактировать: Если вы ограничены использованием делегата определенной подписи (не позволяя экземпляру быть явно переданным в качестве параметра), то вы можете использовать некоторую форму «поставщика экземпляра», которая указана во время создание делегата, но может быть изменено позже, чтобы обеспечить соответствующий экземпляр, когда он станет доступным, например:

class Provider<T>
{
    public T Instance { get; set; }
}

static Action Create(Provider<Foo> provider)
{
    return () =>
    {
        provider.Instance.Baz();
    };
}

// ...

// Creation.
var provider = new Provider<Foo>();
var bar = Create(provider);

// Invocation.
provider.Instance = new Foo();
bar();

Конечно, это немного запутанно и требует, чтобы дополнительный объект был обойден, так что, возможно, это не идеально!

4 голосов
/ 03 ноября 2010

Вы можете использовать Delegate.CreateDelegate для динамического создания делегата для конкретного целевого экземпляра с учетом MethodInfo. Вы можете найти MethodInfo с помощью Type.GetMethod (Reflection) и кэшировать его для последующего использования при создании делегата.

Например, это будет захватывать метод «GetHashCode» и связывать его с экземпляром «this»:

        var method = typeof(Object).GetMethod("GetHashCode");
        var del = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), this, method);

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

3 голосов
/ 25 октября 2012

Несмотря на то, что это старый пост, для этого стоит третье решение.

Я был обеспокоен общей проблемой создания таблиц отправки, например, статические таблицы поиска нестатических методов. Типичное использование может быть в обработке событий ASP.NET.

Мы бы хотели, чтобы синтаксис и инициализация были максимально простыми. Если объявление и инициализация таблицы диспетчеризации слишком сложны, было бы проще / безопаснее просто написать явный оператор If-then-else-if или switch, который выполняет диспетчеризацию.

Мы можем очень просто объявить собрание делегатов. Предполагая некоторые методы Method1, Method2 и тип делегата SomeDelegate для них, тогда мы можем написать:

Dictionary<string, SomeDelegate> dispatchTable = new Dictionary<string, SomeDelegate>
{
    { "Key1", Method1 }
    ,{ "Key2", Method2 }
   ....
}

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

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

Как указано в выбранном решении в этой теме, вы можете создавать открытые делегаты через CreateDelegate, но это синтаксически неудобно и также полагается на передачу имени метода в виде строки для создания делегата, поэтому вы теряете проверку времени компиляции. Объявление таблицы диспетчеризации с использованием этого синтаксиса было бы очень грязным.

Техника метода расширения менее многословна и сохраняет проверку времени компиляции, но она все еще неудобна по сравнению с синтаксисом выше.

Другой (третий) вариант - заключить закрытые делегаты в (связывающую) функцию, которая с учетом экземпляра класса вернет желаемый (закрытый) делегат. Например, вы можете использовать Func.

Тогда таблица отправки в основном:

public class DispatchTable<Class, Key, Delegate> : Dictionary<Key, Func<Class, Delegate>> 

Предполагается, что некоторые методы называются EventHandler1, EventHandler2 и тип делегата для них, например,

delegate int EventHandler(string param1, int param2);

затем объявление и инициализация статической таблицы отправки нестатических элементов так же просто, как:

class MyDispatchTable : DispatchTable<MyClass, string, EventHandler>
static MyDispatchTable dispatchTable = new MyDispatchTable
{
    { "Event1", c => c.EventHandler1 }
    ,{ "Event2", c => c.EventHandler2 }
}; 

Методы теперь могут вызываться через таблицу диспетчеризации с учетом экземпляра класса, ключа для обработчика и параметров метода.

В качестве примера, при вызове функции-члена класса iteself, т.е. экземпляра класса = this, для ключа k и параметров p1, p2 синтаксис будет:

var result = dispatchTable[key](this)(p1, p2);

Обратите внимание, что при этом игнорируется соответствующая проверка ошибок, например, несуществующие ключи. Проверка ошибок может быть заключена в метод GetDelegate класса DispatchTable.

Полный пример приведен ниже. Обратите внимание, что он также включает отдельный метод расширения для класса Dictionary, чтобы упростить синтаксис для обработки ошибок.

Расширение словаря:

    static public class DictionaryExtensions
    {
        // extension method to simplify accessing a dictionary 
        static public V GetValueOrDefault<K, V>(this Dictionary<K, V> dict, K key)
        {
            V value;
            dict.TryGetValue(key, out value);
            return value;
        }
    }

Таблица отправки класса:

    // Syntactic sugar for declaring a general dispatch table
    // The dictionary maps from a key to a function that can return 
    // a closed delegate given an instance of a class.
    // Note that if keys are always integers then it is simpler to use an
    // array rather than a dictionary.
    public class DispatchTable<Key, Class, Delegate> : Dictionary<Key, Func<Class, Delegate>> 
    {
        // standardise the method for accessing a delegate
        public Delegate GetDelegate(Class c, Key k)
        {
            var d = GetValueOrDefault(k);
            if (d == null)
            {
                throw new ArgumentException(String.Format("Delegate not found for key [{0}]",k));
            }
            return d(c);
        }                
    };

Пример использования:

    public class Test
    {
        // some member functions to invoke
        public int EventHandler1(string param1, int param2) { return 1; }
        public int EventHandler2(string param1, int param2) { return 2; }

        // Declaration for a (closed) delegate for the member functions
        private delegate int EventHandler(string param1, int param2);

        // Syntactic sugar for declaring the table 
        private class EventDispatchTable : DispatchTable<string, Test, EventHandler> { };

        // Declare dispatch table and initialize 
        static EventDispatchTable dispatchTable = new EventDispatchTable
        {
            { "Event1", c => c.EventHandler1 }
            ,{ "Event2", c => c.EventHandler2 }
        };

        // Invoke via the dispatch table
        public int DoDispatch(string eventName, string param1, int param2)
        {
            return dispatchTable.GetDelegate(this, eventName)(param1, param2);
        }

    }
3 голосов
/ 03 ноября 2010

У вас есть два варианта, вы можете обращаться с ними как с методом расширения.Создайте делегат для получения объекта и любых необязательных аргументов и передачи этих аргументов фактическому вызову функции.Или создайте его, используя Delegate.CreateInstance, как упоминал Дан.

например,

string s = "foobar";

// "extension method" approach
Func<string, int, string> substring1 = (s, startIndex) => s.Substring(startIndex);
substring1(s, 1); // "oobar"

// using Delegate.CreateDelegate
var stype = typeof(string);
var mi = stype.GetMethod("Substring", new[] { typeof(int) });
var substring2 = (Func<string, int, string>)Delegate.CreateDelegate(typeof(Func<string, int, string>), mi);
substring2(s, 2); // "obar"

// it isn't even necessary to obtain the MethodInfo, the overload will determine
// the appropriate method from the delegate type and name (as done in example 2).
var substring3 = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), s, "Substring");
substring3(3); // "bar"

// for a static method
var compare = (Func<string, string, int>)Delegate.CreateDelegate(typeof(Func<string, string, int>), typeof(string), "Compare");
compare(s, "zoobar"); // -1
1 голос
/ 31 января 2016

Я опоздал на вечеринку на пять лет, но я только столкнулся с этой проблемой и решил немного другое решение:

public class DelEx
{
    private delegate void ProcessStuffDelegate(DelEx me);

    private static void ProcessStuffA(DelEx me)
    {
        me.ProcessStuffA();
    }

    private void ProcessStuffA()
    {
        // do tricky A stuff
    }

    private static void ProcessStuffB(DelEx me)
    {
        me.ProcessStuffB();
    }

    private void ProcessStuffB()
    {
        // do tricky B stuff
    }

    private readonly static List<ProcessStuffDelegate> ListOfProcessing = new List<ProcessStuffDelegate>()
    {
        ProcessStuffA,
        ProcessStuffB
        // ProcessStuffC etc
    };

    public DelEx()
    {
        foreach (ProcessStuffDelegate processStuffDelegate in ListOfProcessing)
        {
            processStuffDelegate(this);
        }
    }

}

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

...