我需要创建一个类的非静态方法的委托。复杂之处在于,在创建时,我没有类的实例,只有类的定义。在通话时,我确实有实例。因此,我需要一种方法:


建立一个“不完整的”委托给成员方法,缺少实例。
从1调用委托,显式传递类的实例。

这两种可能吗?
注意:我愿意为第一名支付高昂的性能价格,但理想情况下,第二名应该不比代表电话贵很多。

#1 楼

您有两个选择,可以像对待扩展方法一样对待它。创建一个委托以接收对象和任何可选参数,并将这些参数传递给实际的函数调用。或使用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,我喜欢扩展方法,因为它更干净,类型更安全。匿名方法可能会有一些开销,但不多。

–丹·布莱恩特
2010年11月3日,0:04

好答案。您应该只进行编辑以包括如何调用打开的委托,并将第一个对象作为第一个参数传递。

–大卫·里斯(David Reis)
2010年11月4日,下午5:13

@David:我在第二个例子中做到了。第三个示例只是为了说明不需要MethodInfo实例。但是为了完整起见,我也会添加它。

–杰夫·梅卡多(Jeff Mercado)
2010年11月4日在5:19

事实证明,第三个例子是错误的。您需要一个实例来按名称绑定到实例方法,否则对于绑定到静态方法很有用。

–杰夫·梅卡多(Jeff Mercado)
2010年11月4日,下午5:36

#2 楼

您可以使用Delegate.CreateDelegate为给定MethodInfo的特定目标实例动态构造一个委托。您可以使用Type.GetMethod(反射)查找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)
2010-11-2 23:46



@David,是的。缓存MethodInfo的优势在于,您节省了与每次查找方法相关的反射成本。如果只需要回调一次(即为该实例保存委托没有任何好处),则可以直接调用MethodInfo。如果您可以保存委托并知道其类型,则使用委托的速度更快(因为它已经被绑定)

–丹·布莱恩特
2010-11-2 23:51

@David,实际上,我刚刚意识到一个Delegate实际上可以表示一个“开放”委托,而没有关联的实例。请参阅Jeff对于此类“开放”委托的回复。

–丹·布莱恩特
2010年11月3日,0:02

#3 楼

只是这样传递实例怎么了?

// 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 楼

即使这是一个老帖子,对于这里值得的是第三个解决方案。
非静态方法的静态查找表。一个典型的用法可能是在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);

br />
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);


请注意,这会忽略适当的错误检查,例如不存在的密钥。错误检查可以包装在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);
        }

    }


#5 楼

我参加聚会已经晚了五年,但是我只是遇到了这个问题,并决定了一个稍微不同的解决方案:

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

}


使用静态方法访问其实例方法可能适合只需要几个委托方法的任何人。