从非静态方法构建一个静态委托

4
我需要创建一个委托来调用类的非静态方法。问题在于,在创建时,我没有类的实例,只有其类定义。但在调用时,我有该实例。因此,我需要一种方法来:
  1. 构建一个“不完整”的成员方法委托,缺少实例。
  2. 从1中显式传递类的实例来调用委托。
这两个都可能吗?如何做到?
注意:我愿意为第一项付出高性能代价,但理想情况下,第二项不应比委托调用贵太多。
5个回答

4
像这样只传递一个实例有什么问题吗?
// 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
您可以使用Delegate.CreateDelegate来动态构造委托,为特定的目标实例提供MethodInfo。您可以使用Type.GetMethod(Reflection)查找MethodInfo并将其缓存以便后续使用创建委托。

例如,以下代码会获取“GetHashCode”方法并将其绑定到'this'实例:
        var method = typeof(Object).GetMethod("GetHashCode");
        var del = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), this, method);

如果方法有多个重载,还有更微妙的问题需要处理,但是你可以使用额外的GetMethod参数来消除歧义(如果必要)。


因此,任务1将包括收集MethodInfos,而不是创建实际的委托,而任务2则是在给定实例和MethodInfo的情况下创建委托。 2也可以只使用反射调用方法,而不是创建委托。 无论步骤2的任何实现方式,其性能特征如何? - David Reis
@David,没错。缓存MethodInfo的优点在于节省了每次查找方法所需的反射成本。如果您只需要回调一次(即没有保存该实例的委托的好处),则可以直接调用MethodInfo。如果您可以保存委托并知道其类型,则使用委托更快(因为它已经被绑定)。 - Dan Bryant
@David,实际上,我刚意识到Delegate可以代表一个“开放”的委托,没有关联的实例。请参见Jeff的响应以获取此类“开放”委托。 - Dan Bryant

3
你有两个选择,你可以像对待扩展方法一样处理它。创建一个委托来接收对象和任何可选参数,并将这些参数传递给实际的函数调用。或者使用 Dan 提到的 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,我喜欢扩展方法的方式,因为它更加干净和类型安全。匿名方法可能会有一些开销,但不多。 - Dan Bryant
好的回答。你只需要编辑并包含如何调用open委托,并将第一个对象作为第一个参数传递。 - David Reis
@David:好的,我在第二个例子中已经这样做了。第三个例子只是为了说明不一定需要MethodInfo的实例。但出于完整性考虑,我也会加上它。 - Jeff Mercado
事实证明,第三个例子是错误的。您需要一个实例来按名称绑定到实例方法,否则它对于绑定静态方法很有用。 - Jeff Mercado

3
尽管这是一篇旧帖子,但是以下的第三种解决方案也许还有参考价值。
我关心的是创建分派表的通用问题,例如非静态方法的静态查找表。典型的应用可能在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的方法,以及用于它们的委托类型,例如:<p>。
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 }
}; 

现在可以通过调度表,在给定类的实例、处理程序键和方法参数的情况下调用方法。

例如,如果要从类自身的成员函数即类实例=this中调用,使用键k和参数p1,p2,则语法应如下:

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

请注意,这忽略了适当的错误检查,例如不存在的键。 错误检查可以包装在DispatchTable类的GetDelegate方法中。
下面给出一个完整的示例。 请注意,它还包括一个用于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);
        }

    }

1

我来晚了五年,但我刚刚遇到这个问题,并决定采用稍微不同的解决方案:

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);
        }
    }

}

对于只需要使用少量委托方法的人来说,使用静态方法来访问它们的实例方法可能是合适的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接