1. 程式人生 > >比反射更快:委託 第3部分

比反射更快:委託 第3部分

目錄

泛型方法

靜態泛型方法

例項泛型方法

事件

 事件新增訪問器

事件刪除訪問者

總結


現在是時候介紹以下成員了。

  1. 泛型方法
  2. 事件

泛型方法

以前我們沒有介紹C#關於方法的非常重要的特性。如果您閱讀本節的標題,您已經知道我在說什麼。在C#中,可以編寫泛型方法,這些方法沒有剛性引數型別或剛性返回型別,因為它們可能會有所不同並且取決於型別引數。大多數DelegateFactory類方法都是泛型的。我們如何通過委託呼叫這些方法?主要問題是我們不能,或者至少不那麼容易。實際上,具有不同型別引數集的泛型方法的每個版本都是不同的方法,並且我們需要針對該組引數分配不同的委託。

 

靜態泛型方法

考慮TestClass中的以下方法。

public static T StaticGenericMethod<T>() where T : new ()
{
    return new T();
}

public static T StaticGenericMethod<T>(T param)
{
    return param;
}

第一個是非常簡單的沒有引數的泛型方法,返回型別是來自型別引數的。C#世界中有很多型別都有預設的無引數建構函式,所以我們可以使用它並建立非常基本的泛型方法,實際上有意義的方法。第二個甚至更簡單(它只返回型別引數給出的相同型別的物件),但重要的是顯示採用不同的引數集的泛型方法的過載。

兩者都是靜態方法,所以我們可以使用StaticMethod方法,對嗎?是的,我們可以,而它將無法工作,因為我們無法為第一個StaticGenericMethod示例(它將丟擲錯誤,因為傳遞的委託型別不相容)建立正確的委託或者如果至少有一個引數是不同的型別時將返回null(如果我們不知道方法過載的引數,就不能通過它的引數來搜尋,因為它們不同於型別引數)。這些問題的解決方案是新的過載,它將型別引數(或帶有型別的新引數)應用於泛型方法。

使用委託的傳遞型別引數與索引器和方法(靜態和例項)成員型別的問題相同。引數型別的數量確實沒有限制(在ActionFunc類中,除了理由和16個限制),因此我們必須為每個型別引數編寫不同的

StaticMethodInstanceMethod過載。這沒什麼意義,根據經驗,我可以說大多數方法都有一個或兩個型別引數。我可能在某些方法中用了3引數,但我不記得了。這就是為什麼(和DelegateFactory其他方法保持一致性)我認為,支援最多 3個型別引數就足夠了。對於每種其他情況,型別引數的型別陣列就足夠了。

但首先我們需要獲得泛型方法的方法資訊,並考慮可能的過載。過載可以是引數和型別引數數量的任意組合。例如,如果它們與型別引數的數量不同,則可以有兩個沒有引數的過載。或者,您可以使用相同型別引數(或無)但具有不同引數型別的過載。考慮以下方法。

public static T StaticGenericMethod<T>() where T : new()
{
    return new T();
}

public static T StaticGenericMethod<T>(T param)
{
    return param;
}

public static T StaticGenericMethod<T>(T param, int i) where T : ITestInterface
{
    return param;
}

public static T StaticGenericMethod<T>(T param, int i, bool p) where T : struct
{
    return param;
}

public static T1 StaticGenericMethod<T1, T2>() where T1 : new()
{
    return new T1();
}

public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new()
{
    return new T1();
}

這些都是完全有效的方法,因為GetStaticMethod方法的早期版本不起作用。為什麼?反射不允許我們輕鬆找到泛型方法過載,如果同一組引數(如果一個或多個方法是泛型的,這是完全可能的)有多個過載,則Type.GetMethod將丟擲異常。如何解決這個問題?當存在多個過載時,我們可以捕獲AmbiguousMatchException異常,並檢查我們正在尋找的泛型方法的所有方法。為此,我們需要比較兩對集合:我們正在尋找的第一組引數,並設定具有正確約束的pf型別引數。

private static MethodInfo GetStaticMethodInfo(Type source, string name, Type[] parametersTypes, Type[] typeParameters = null)
{
    MethodInfo methodInfo = null;
    try
    {
        methodInfo = (source.GetMethod(name, BindingFlags.Static, null, parametersTypes, null) ??
                        source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic, null, parametersTypes, null)) ??
                        source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, parametersTypes, null);
    }
    catch (AmbiguousMatchException)
    {
        //swallow and test generics
    }
    //check for generic methods
    if (typeParameters != null)
    {
        var ms = source.GetMethods(BindingFlags.Static)
                .Concat(source.GetMethods(BindingFlags.NonPublic | BindingFlags.Static))
                .Concat(source.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static));
        foreach (var m in ms)
        {
            if (m.Name == name && m.IsGenericMethod)
            {
                var parameters = m.GetParameters();
                var genericArguments = m.GetGenericArguments();
                var parametersTypesValid = parameters.Length == parametersTypes.Length;
                parametersTypesValid &= genericArguments.Length == typeParameters.Length;
                if (!parametersTypesValid)
                {
                    continue;
                }
                for (var index = 0; index < parameters.Length; index++)
                {
                    var parameterInfo = parameters[index];
                    var parameterType = parametersTypes[index];
                    if (parameterInfo.ParameterType != parameterType
                        && parameterInfo.ParameterType.IsGenericParameter
                        && !parameterInfo.ParameterType.CanBeAssignedFrom(parameterType))
                    {
                        parametersTypesValid = false;
                        break;
                    }
                }
                for (var index = 0; index < genericArguments.Length; index++)
                {
                    var genericArgument = genericArguments[index];
                    var typeParameter = typeParameters[index];
                    if (!genericArgument.CanBeAssignedFrom(typeParameter))
                    {
                        parametersTypesValid = false;
                        break;
                    }
                }
                if (parametersTypesValid)
                {
                    methodInfo = m.MakeGenericMethod(typeParameters);
                    break;
                }
            }
        }
    }
    return methodInfo;
}

這比以前複雜得多!但別擔心,我會解釋一切。

以前的程式碼在try語句中。第一個更改是帶有一組型別引數的額外引數,應該針對方法泛型引數編號和所有約束的每個過載進行檢查。如果我們確實尋找泛型方法(typeParameter不是空的),我們正在逐步轉向尋找方法。在檢索具有所有可見性的所有型別的方法之後,我們必須按名稱過濾它們。然後我們檢查方法是否泛型。如果是,我們必須檢查引數數量和型別引數。如果這些是正確的,我們必須將它們與委託引數進行比較並在同一位置鍵入引數。檢查引數並不像僅比較型別那麼容易,因為引數可以具有型別引數的型別,型別引數可以具有約束。因此,我們必須檢查引數的型別,如果它是不同的(泛型引數具有特殊的泛型型別),我們必須檢查引數是否是泛型的,以及它的泛型型別是否可以表示我們正在尋找的引數型別。在示例in t型別可以由帶有struct約束的泛型型別T的引數來表示。如果不瞭解IsAssignableFrom方法的細節,這可能有點難以理解。當然有Type.IsAssignableFrom方法,但奇怪的是它沒有正常工作,因為沒有約束的簡單引數無法與任何類匹配。兩者的程式碼是相同的,除了下面的檢查程式碼,這導致錯誤的結果。

RuntimeType toType = this.UnderlyingSystemType as RuntimeType;
if (toType != null)
    return toType.IsAssignableFrom(c);

我不知道為什麼,但在刪除它並建立新方法而不使用它之後,它會正常工作,也就是說,不使用型別引數約束的方法中的T型別引數:

public static T StaticGenericMethod<T>(T param)

當我們檢查IsAssignableFrom新實現時,可以從TestClass型別分配。

public static bool CanBeAssignedFrom(this Type destination, Type source)
{
    if (source == null || destination == null)
    {
        return false;
    }

    if (destination == source || source.IsSubclassOf(destination))
    {
        return true;
    }

    if (destination.IsInterface)
    {
        return source.ImplementsInterface(destination);
    }

    if (!destination.IsGenericParameter)
    {
        return false;
    }

    var constraints = destination.GetGenericParameterConstraints();
    return constraints.All(t1 => t1.CanBeAssignedFrom(source));
}

private static bool ImplementsInterface(this Type source, Type interfaceType)
{
    while (source != null)
    {
        var interfaces = source.GetInterfaces();
        if (interfaces.Any(i => i == interfaceType
            || i.ImplementsInterface(interfaceType)))
        {
            return true;
        }

        source = source.BaseType;
    }
    return false;
}

以上方法必須在兩種情況下使用。第一種,我們必須將委託的給定引數集與方法的引數進行比較。這是通過在Type.GetMethod方法中搜索方法自動完成的。但是現在,既然我們知道,有兩個泛型方法可以使用相同的引數集,我們必須針對所需型別檢查每個泛型方法是否過載。如果方法沒有泛型引數比較很容易:只檢查兩個Type型別例項是否相等。但是如果方法具有泛型引數,我們必須比較此引數的型別引數約束。這是通過CanBeAssignedFrom方法完成的。

如果我們的引數集是正確的,我們必須以類似的方式比較型別引數。每個提供的具體型別都必須在同一索引及其約束條件下檢查方法的型別引數。它也是由CanBeAssignedFrom方法完成的。

如果兩個集合都是正確的,則意味著我們有所需的泛型方法過載,它可以用於通過Type.MakeGenericMethod生成具有具體型別的方法

好。讓我們來介紹靜態方法。第一種情況,當我們知道所有委託型別,然後知道未知型別(通過物件),之後我們將對例項方法做同樣的事情。

泛型的第一個新的過載非常容易實現。由於我們已經有了GetStaticMethodInfo方法,它不僅搜尋方法資訊,而且還使用指定的型別引數建立非泛型方法,我們可以使用返回的物件來建立委託,就像非泛型委託一樣。唯一的區別是委託方法的額外型別引數...型別引數。它在程式碼中看起來會更清晰。

public static TDelegate StaticMethod<TSource, TDelegate, TParam1>(string name)
    where TDelegate : class
{
    return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1));
}

public static TDelegate StaticMethod<TSource, TDelegate, TParam1, TParam2>(string name)
    where TDelegate : class
{
    return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2));
}

public static TDelegate StaticMethod<TSource, TDelegate, TParam1, TParam2, TParam3>(string name)
    where TDelegate : class
{
    return typeof(TSource).StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3));
}

public static TDelegate StaticMethod<TSource, TDelegate>(string name, params Type[] typeParameters)
    where TDelegate : class
{
    return typeof(TSource).StaticMethod<TDelegate>(name, typeParameters);
}

public static TDelegate StaticMethod<TDelegate>(this Type source, string name, params Type[] typeParameters)
    where TDelegate : class
{
    var paramsTypes = GetFuncDelegateArguments<TDelegate>();
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes, typeParameters);
    return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate;
}

對於具有相同數量型別引數的泛型方法,前三個只是使用1,23個型別引數的過載。下一個方法允許建立具有更多型別引數的委託。最重要的是執行建立委託的實際工作的最後一個方法。

要將上述方法用於泛型方法委託,我們必須按以下方式呼叫它們(第一個示例呼叫,然後是通過委託執行的目標泛型方法)。

var g1 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, TestClass>, TestClass>("StaticGenericMethod");
public static T StaticGenericMethod<T>(T param)

var g2 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod");
public static T StaticGenericMethod<T>(T param, int i) where T : ITestInterface

var g3 = DelegateFactory.StaticMethod<TestClass, Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod");
public static T StaticGenericMethod<T>(T param, int i, bool p) where T : struct

var g4 = DelegateFactory.StaticMethod<TestClass, Func<TestClass>, TestClass>("StaticGenericMethod");
public static T StaticGenericMethod<T>() where T : new()

var g5 = DelegateFactory.StaticMethod<TestClass, Func<TestClass>, TestClass, TestStruct>("StaticGenericMethod");
public static T1 StaticGenericMethod<T1, T2>() where T1 : new()

var g6 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod");
public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new()

var g7 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int));
public static T1 StaticGenericMethod<T1, T2, T3>(int i) where T1 : new()

用這種方式解釋時看起來很容易 

我們已經將StaticMethod過載作為Type例項的擴充套件方法。當我們想要建立帶有1,23個型別引數的委託並且我們不知道或不想使用源型別時,我們還可以為這些情況編寫三個新的過載。

public static TDelegate StaticMethod<TDelegate, TParam1>(this Type source, string name)
    where TDelegate : class
{
    return source.StaticMethod<TDelegate>(name, typeof(TParam1));
}

public static TDelegate StaticMethod<TDelegate, TParam1, TParam2>(this Type source, string name)
    where TDelegate : class
{
    return source.StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2));
}

public static TDelegate StaticMethod<TDelegate, TParam1, TParam2, TParam3>(this Type source, string name)
    where TDelegate : class
{
    return source.StaticMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3));
}

它們的使用方式與作為第一個型別引數傳遞的源型別相同。

好。現在,當我們不知道任何型別(或者不想使用那些型別)時,我們可以跳轉到此示例。生成的委託應該使用物件型別作為引數和返回值。它是在靜態方法,例項方法,屬性和索引器中完成的。這個函式的主要問題是我們不能將它作為StaticMethod過載,因為之前的一個過載已經允許使用params引數。具有型別陣列的任何其他過載都將是編譯錯誤。還要記住,對於沒有傳遞委託型別的方法,我們必須編寫兩個方法,對於void  返回型別和非void型別,我們將建立方法StaticGenericMethodStaticGenericMethodVoid。幸運的是,兩者都可以通過與StaticMethodStaticMethodVoid 相同的程式碼來處理,只需進行簡單的更改即可。

public static Func<object[], object> StaticGenericMethod(this Type source,
    string name, Type[] paramsTypes, Type[] typeParams)
{
    return StaticMethod<Func<object[], object>>(source, name, typeParams, paramsTypes);
}

public static Action<object[]> StaticGenericMethodVoid(this Type source,
    string name, Type[] paramsTypes, Type[] typeParams)
{
    return StaticMethod<Action<object[]>>(source, name, typeParams, paramsTypes);
}

以前方法的更改實現將如下所示。

public static Func<object[], object> StaticMethod(this Type source,
    string name, params Type[] paramsTypes)
{
    return StaticMethod<Func<object[], object>>(source, name, null, paramsTypes);
}

public static TDelegate StaticMethod<TDelegate>(this Type source,
    string name, Type[] typeParams, Type[] paramsTypes)
    where TDelegate : class
{
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes, typeParams);
    if (methodInfo == null)
    {
        return null;
    }
    var argsArray = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(methodInfo, paramsExpression);
    if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return Expression.Lambda(returnExpression, argsArray).Compile() as TDelegate;
}

public static Action<object[]> StaticMethodVoid(this Type source,
    string name, params Type[] paramsTypes)
{
    return StaticMethod<Action<object[]>>(source, name, null, paramsTypes);
}

現在靜態泛型方法的一切都準備好了,我們可以開始測試。下面的呼叫將為StaticGenericMethod(每個塊中的行)不同過載建立委託,並使用StaticMethodStaticGenericMethod(三個塊)的不同過載。

var g1 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, TestClass>, TestClass>("StaticGenericMethod");
var g2 = DelegateFactory.StaticMethod<TestClass, Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod");
var g3 = DelegateFactory.StaticMethod<TestClass, Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod");
var g4 = DelegateFactory.StaticMethod<TestClass, Func, TestClass>("StaticGenericMethod");
var g5 = DelegateFactory.StaticMethod<TestClass, Func, TestClass, TestStruct>("StaticGenericMethod");
var g6 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod");
var g7 = DelegateFactory.StaticMethod<TestClass, Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int));

var g8 = Type.StaticMethod<Func<TestClass, TestClass>, TestClass>("StaticGenericMethod");
var g9 = Type.StaticMethod<Func<TestClass, int, TestClass>, TestClass>("StaticGenericMethod");
var g10 = Type.StaticMethod<Func<TestStruct, int, bool, TestStruct>, TestStruct>("StaticGenericMethod");
var g11 = Type.StaticMethod<Func, TestClass>("StaticGenericMethod");
var g12 = Type.StaticMethod<Func, TestClass, TestStruct>("StaticGenericMethod");
var g13 = Type.StaticMethod<Func<int, TestClass>, TestClass, TestStruct, int>("StaticGenericMethod");
var g14 = Type.StaticMethod<Func<int, TestClass>>("StaticGenericMethod", typeof(TestClass), typeof(TestStruct), typeof(int));

var g15 = Type.StaticGenericMethod("StaticGenericMethod", new[] { Type }, new[] { Type });
var g16 = Type.StaticGenericMethod("StaticGenericMethod", new[] { Type, typeof(int) }, new[] { Type });
var g17 = Type.StaticGenericMethod("StaticGenericMethod", new[] { typeof(TestStruct), typeof(int), typeof(bool) }, new[] { typeof(TestStruct) });
var g18 = Type.StaticGenericMethod("StaticGenericMethod", Type.EmptyTypes, new[] { Type });
var g19 = Type.StaticGenericMethod("StaticGenericMethod", Type.EmptyTypes, new[] { Type, typeof(TestStruct) });
var g20 = Type.StaticGenericMethod("StaticGenericMethod", new[] { typeof(int) }, new[] { Type, typeof(TestStruct), typeof(int) });
var g21 = Type.StaticGenericMethodVoid("StaticGenericMethodVoid", new[] { Type }, new[] { Type });

建立的委託按以下方式使用。

var t = g1(TestInstance);
var t2 = g2(TestInstance, 0);
var t3 = g3(new TestStruct(), 0, false);
var t4 = g4();
var t5 = g5();
var t6 = g6(0);
var t7 = g7(0);

var t8 = g8(TestInstance);
var t9 = g9(TestInstance, 0);
var t10 = g10(new TestStruct(), 0, false);
var t11 = g11();
var t12 = g12();
var t13 = g13(0);
var t14 = g14(0);

var t15 = g15(new object[] { TestInstance });
var t16 = g16(new object[] { TestInstance, 0 });
var t17 = g17(new object[] { new TestStruct(), 0, false });
var t18 = g18(new object[] { });
var t19 = g19(new object[] { });
var t20 = g20(new object[] { 0 });
g21(new object[] { TestInstance });
var t21 = TestClass.StaticGenericMethodVoidParameter;

我們還可以針對直接呼叫和反射的泛型方法測試委託的效能。

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = TestClass.StaticGenericMethod(TestInstance);
}
_stopWatch.Stop();
Console.WriteLine("Static generic method directly: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = g1(TestInstance);
}
_stopWatch.Stop();
Console.WriteLine("Static generic method proxy: {0}", _stopWatch.ElapsedMilliseconds);

var methodInfos = Type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == "StaticGenericMethod" && m.IsGenericMethod && m.GetParameters().Length == 1 && m.GetGenericArguments().Length==1);
var methodInfo = methodInfos.Single();
methodInfo = methodInfo.MakeGenericMethod(Type);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = methodInfo.Invoke(null, new object[] { TestInstance });
}
_stopWatch.Stop();
Console.WriteLine("Static generic method via reflection: {0}", _stopWatch.ElapsedMilliseconds);

上面的程式碼將在控制檯中生成輸出類似的行。

Static generic method directly: 1160
Static generic method proxy: 1270
Static generic method via reflection: 21591

正如您所看到的,效能與其他委託對不同的成員型別一致。 

例項泛型方法

我們現在可以對例項方法做同樣的事情。但首先,我們必須以與GetStaticMethodInfo相同的方式更改GetMethodInfo

private static MethodInfo GetMethodInfo(Type source, string name, Type[] parametersTypes, Type[] typeParameters = null)
{
    MethodInfo methodInfo = null;
    try
    {
        methodInfo = (source.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, parametersTypes, null) ??
                        source.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parametersTypes, null)) ??
                        source.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, parametersTypes, null);
    }
    catch (AmbiguousMatchException)
    {
        //swallow and test generics
    }
    //check for generic methods
    if (typeParameters != null)
    {
        var ms = source.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .Concat(source.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance))
                .Concat(source.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
        foreach (var m in ms)
        {
            if (m.Name == name && m.IsGenericMethod)
            {
                var parameters = m.GetParameters();
                var genericArguments = m.GetGenericArguments();
                var parametersTypesValid = parameters.Length == parametersTypes.Length;
                parametersTypesValid &= genericArguments.Length == typeParameters.Length;
                if (!parametersTypesValid)
                {
                    continue;
                }
                for (var index = 0; index < parameters.Length; index++)
                {
                    var parameterInfo = parameters[index];
                    var parameterType = parametersTypes[index];
                    if (parameterInfo.ParameterType != parameterType
                        && parameterInfo.ParameterType.IsGenericParameter
                        && !parameterInfo.ParameterType.CanBeAssignedFrom(parameterType))
                    {
                        parametersTypesValid = false;
                        break;
                    }
                }
                for (var index = 0; index < genericArguments.Length; index++)
                {
                    var genericArgument = genericArguments[index];
                    var typeParameter = typeParameters[index];
                    if (!genericArgument.CanBeAssignedFrom(typeParameter))
                    {
                        parametersTypesValid = false;
                        break;
                    }
                }
                if (parametersTypesValid)
                {
                    methodInfo = m.MakeGenericMethod(typeParameters);
                    break;
                }
            }
        }
    }
    return methodInfo;
}

正如你所看到的,程式碼幾乎是一樣的。區別在於繫結標誌——傳遞例項而不是靜態

現在我們可以新增新的過載。我們應該只以使用型別引數的過載開始,就像使用靜態方法一樣。它們看起來像這樣。

public static TDelegate InstanceMethod<TDelegate, TParam1>(string name)
    where TDelegate : class
{
    return InstanceMethod<TDelegate>(name, typeof(TParam1));
}

public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2>(string name)
    where TDelegate : class
{
    return InstanceMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2));
}

public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2, TParam3>(string name)
    where TDelegate : class
{
    return InstanceMethod<TDelegate>(name, typeof(TParam1), typeof(TParam2), typeof(TParam3));
}

public static TDelegate InstanceMethod<TDelegate>(string name, params Type[] typeParameters)
    where TDelegate : class
{
    var paramsTypes = GetFuncDelegateArguments<TDelegate>();
    var source = paramsTypes.First();
    paramsTypes = paramsTypes.Skip(1).ToArray();
    var methodInfo = GetMethodInfo(source, name, paramsTypes, typeParameters);
    return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate;
}

程式碼非常類似於不支援泛型的程式碼,新的過載也很容易理解。

同樣的情況是擴充套件方法過載。唯一的區別是傳遞給GetMethodInfo方法的額外引數和型別引數。

public static TDelegate InstanceMethod<TDelegate, TParam1>(this Type source, string name)
    where TDelegate : class
{
    return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1) });
}

public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2>(this Type source, string name)
    where TDelegate : class
{
    return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1), typeof(TParam2) });
}

public static TDelegate InstanceMethod<TDelegate, TParam1, TParam2, TParam3>(this Type source, string name)
    where TDelegate : class
{
    return source.InstanceMethod<TDelegate>(name, new[] { typeof(TParam1), typeof(TParam2), typeof(TParam3) });
}

public static TDelegate InstanceMethod<TDelegate>(this Type source, string name, Type[] typeParams = null)
    where TDelegate : class
{
    var delegateParams = GetFuncDelegateArguments<TDelegate>();
    var instanceParam = delegateParams[0];
    delegateParams = delegateParams.Skip(1).ToArray();
    var methodInfo = GetMethodInfo(source, name, delegateParams, typeParams);
    if (methodInfo == null)
    {
        return null;
    }
    Delegate deleg;
    if (instanceParam == source)
    {
        deleg = methodInfo.CreateDelegate(typeof(TDelegate));
    }
    else
    {
        var sourceParameter = Expression.Parameter(typeof(object));
        var expressions = delegateParams.Select(Expression.Parameter).ToArray();
        Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source),
            methodInfo, expressions.Cast<Expression>());
        if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, typeof(object));
        }
        var lamdaParams = new[] { sourceParameter }.Concat(expressions);
        deleg = Expression.Lambda(returnExpression, lamdaParams).Compile();
    }
    return deleg as TDelegate;
}

最後兩個更通用的方法,只接受物件引數和返回物件,也非常相似,並以與靜態泛型相同的方式更改。

public static Func<object, object[], object> InstanceGenericMethod(this Type source,
    string name, Type[] paramsTypes, Type[] typeParams)
{
    return InstanceGenericMethod<Func<object, object[], object>>(source, name, typeParams, paramsTypes);
}
        
public static Action<object, object[]> InstanceGenericMethodVoid(this Type source,
    string name, Type[] paramsTypes, Type[] typeParams)
{
    return InstanceGenericMethod<Action<object, object[]>>(source, name, typeParams, paramsTypes);
}

public static Func<object, object[], object> InstanceMethod(this Type source,
    string name, params Type[] paramsTypes)
{
    return InstanceGenericMethod<Func<object, object[], object>>(source, name, null, paramsTypes);
}
        
public static Action<object, object[]> InstanceMethodVoid(this Type source,
            string name, params Type[] paramsTypes)
{
    return InstanceGenericMethod<Action<object, object[]>>(source, name, null, paramsTypes);
}

public static TDelegate InstanceGenericMethod<TDelegate>(this Type source,
    string name, Type[] typeParams, Type[] paramsTypes)
    where TDelegate : class
{
    var methodInfo = GetMethodInfo(source, name, paramsTypes, typeParams);
    if (methodInfo == null)
    {
        return null;
    }
    var argsArray = Expression.Parameter(typeof(object[]));
    var sourceParameter = Expression.Parameter(typeof(object));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source),
        methodInfo, paramsExpression);
    if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return Expression.Lambda(returnExpression, sourceParameter, argsArray).Compile() as TDelegate;
}

這就是我們需要支援例項泛型方法的全部內容。現在我們可以編寫一些測試方法。

public T GenericMethod<T>(T s)
{
    return s;
}

public object InstanceGenericMethodVoidParameter;

public void GenericMethodVoid<T>(T s)
{
    InstanceGenericMethodVoidParameter = s;
}

要為上面建立委託,我們以與靜態等價物相同的方式呼叫新建立的過載。唯一的區別是委託的一個額外引數——例項。

var ig1 = DelegateFactory.InstanceMethod<Func<TestClass, TestClass, TestClass>, TestClass>("GenericMethod");
var ig2 = Type.InstanceMethod<Func<TestClass, TestClass, TestClass>, TestClass>("GenericMethod");
var ig3 = Type.InstanceMethod<Func<object, TestClass, TestClass>, TestClass>("GenericMethod");
var ig4 = Type.InstanceGenericMethod("GenericMethod", new[] { Type }, new[] { Type });
var ig5 = Type.InstanceGenericMethodVoid("GenericMethodVoid", new[] { Type }, new[] { Type });

委託的呼叫方式與任何其他例項方法委託的方式相同。

var it1 = ig1(TestInstance, TestInstance);
var it2 = ig2(TestInstance, TestInstance);
var it3 = ig3(TestInstance, TestInstance);
var it4 = ig4(TestInstance, new object[] { TestInstance });
ig5(TestInstance, new object[] { TestInstance });
var it5 = TestInstance.InstanceGenericMethodVoidParameter;

這就是我們完全支援泛型方法所需的全部內容。

正如您所看到的,泛型委託的主要區別和缺點是,如果我們需要完整型別資訊,我們需要為我們需要的每個型別集合建立單獨的委託。或者,可以使用更通用的型別為泛型方法建立委託。例如,如果泛型方法允許某些型別引數僅為IDisposable介面,則可以使用此介面搜尋方法並使用此方法建立委託。下面的程式碼在編譯時和執行時完全有效。

var g22 = Type.StaticGenericMethodVoid("StaticGenericMethodVoid", new[] { typeof(object) }, new[] { typeof(object) });
g22(new object[] { "" });
var t22 = TestClass.StaticGenericMethodVoidParameter;
g22(new object[] { TestInstance });
var t23 = TestClass.StaticGenericMethodVoidParameter;

現在我們討論了很多方法。現在是時候瞭解最後的成員型別了——事件。 

事件

與屬性訪問器一樣,對於事件,我們還有兩個訪問器——新增和刪除。對於兩者,我們將新增方法,幾乎​​沒有過載。我們應該首先建立一些測試事件。

public event EventHandler PublicEvent;

private event EventHandler InternalEventBackend;

internal event EventHandler InternalEvent
{
    add { InternalEventBackend += value; }
    remove { InternalEventBackend -= value; }
}

protected event EventHandler ProtectedEvent;

private event EventHandler PrivateEvent;

public void InvokeInternalEvent()
{
    InternalEventBackend?.Invoke(this, new InternalEventArgs());
}

public void InvokePrivateEvent()
{
    PrivateEvent?.Invoke(this, new PrivateEventArgs());
}

public void InvokeProtectedEvent()
{
    ProtectedEvent?.Invoke(this, new ProtectedEventArgs());
}

public void InvokePublicEvent()
{
    PublicEvent?.Invoke(this, new PublicEventArgs());
}

 四個具有不同可見性的事件。內部事件具有自定義訪問器,僅用於測試此類事件是否也適用於委託。新增用於呼叫事件的公共方法僅用於純測試(我們需要一種從TestClass型別之外的測試程式碼中引發事件的方法)。

由於要引發事件我們需要首先新增處理程式,我們將首先討論新增訪問器。

 事件新增訪問器

DelegateFactory中的新方法將被稱為EventAdd。但首先我們需要從反射中獲取EventInfo

private static EventInfo GetEventInfo(string eventName, Type sourceType)
{
    return (sourceType.GetEvent(eventName)
            ?? sourceType.GetEvent(eventName, BindingFlags.NonPublic))
            ?? sourceType.GetEvent(eventName,
                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
}

這裡沒什麼好看的。它與任何其他成員的程式碼相同。它只是尋找事件而不是屬性或方法。

如果我們知道要訪問的事件的物件型別,知道委託的型別並知道它的事件引數型別,事情就非常簡單。我們只需要傳遞有效的委託型別,以便從訪問器方法建立委託。

public static Action<TSource, EventHandler<TEventArgs>> EventAdd<TSource, TEventArgs>(string eventName)
{
    var sourceType = typeof(TSource);
    var eventInfo = GetEventInfo(eventName, sourceType);
    return (Action<TSource, EventHandler<TEventArgs>>)
            eventInfo?.AddMethod.CreateDelegate(typeof(Action<TSource, EventHandler<TEventArgs>>));
}

它的作用與為get屬性訪問器建立委託相同。例如,對於TestClass.PublicEvent事件,它將建立委託,可以由下面的lambda表示。

Action<TestClass, EventHandler<TestClass.PublicEventArgs>> d = (i, h) => i.PublicEvent += h;

現在考慮具有未知源型別的情況,或者當這種型別無關緊要時。在所有事件處理程式總是具有object型別的第一個引數之後,從使用者的角度來看它實際上是不必要的。在這種情況下唯一的問題是必須將例項引數從物件轉換為具有事件的正確型別。和以前一樣,我們需要表示式。

public static Action<object, EventHandler<TEventArgs>> EventAdd<TEventArgs>(
    this Type source, string eventName)
{
    var eventInfo = GetEventInfo(eventName, source);
    if (eventInfo != null)
    {
        var instanceParameter = Expression.Parameter(typeof(object));
        var delegateTypeParameter = Expression.Parameter(typeof(EventHandler<TEventArgs>));
        var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source),
                                        eventInfo.AddMethod, delegateTypeParameter),
                        instanceParameter, delegateTypeParameter);
        return (Action<object, EventHandler<TEventArgs>>)lambda.Compile();
    }
    return null;
}

建立的lambda表示式只是在轉換為源型別例項引數時使用第二個引數的處理程式呼叫事件資訊AddMethod。通常,這更容易以lambda的形式理解。

Action<object, EventHandler<TestClass.PublicEventArgs>> d = (i, h) => ((TestClass)i).PublicEvent += h;

當我們知道類的型別而我們不知道事件引數的型別時,即當事件是私有的時?一個案例怎麼樣?建立委託的唯一方法是通過表示式(當我們不知道方法的引數時,情況類似)。不幸的是,這有點複雜,因為每個事件處理程式都接受物件和一些派生自EventArgs型別(或者因為它更像是一個約定而不是強制的)。為什麼?考慮以下程式碼將事件繫結到處理程式。

TestInstance.PublicEvent += (sender, args) => { };

什麼都不復雜吧?此程式碼只是將lambda函式新增到事件中。但到底怎麼樣?檢視與上述相同的程式碼。

EventHandler<TestClass.PublicEventArgs> ha = (sender, args) => { };
TestInstance.PublicEvent += ha.Invoke;

使用我們的lambda函式作為目標建立EventHandler<>型別的第一個新物件。之後,Invoke方法中的相同lambda 作為處理程式傳遞給事件。顯然我們需要從Action <objectEventArgs>型別建立EventHandler型別的物件。怎麼做?最明顯的嘗試是使用EventHandler <>型別的建構函式。

EventHandler<TestClass.PublicEventArgs> ha = new EventHandler<TestClass.PublicEventArgs>((sender, args) => { });

這完美無瑕。由於我們已經看到如何使用反射和表示式中的建構函式,因此應該很容易建立呼叫從反射中檢索的建構函式的表示式。嗯......不太好。 

正如您所看到的,此型別只有一個建構函式,它涉及指向某個物件的指標。哪一個?當你建立EventHandler型別的新物件時,真正的發生了什麼?我們可以檢查從C#編譯的CIL程式碼。

IL_0011:  ldsfld      class Delegates.TestEventHandler/'<>c' Delegates.TestEventHandler/'<>c'::'<>9'
IL_0016:  ldftn       instance void Delegates.TestEventHandler/'<>c'::'<.ctor>b__0_0'(object, class Delegates.TestClass/PublicEventArgs)
IL_001c:  newobj    instance void class [mscorlib]System.EventHandler`1<class Delegates.TestClass/PublicEventArgs>::.ctor(object, native int)

神奇的事情發生在ldftn指令中,這意味著:'將指標推送到堆疊上方法引用的方法。根據這個頁面。這意味著載入了指向方法senderargs=> {}指標,並使用此指標建立新的EventHandler。因此我們無法使用此建構函式,因為我們無法從C#獲取指向託管方法的指標。 

那我們能做什麼呢?最簡單的解決方案是建立為我們執行此操作的新方法。簡單呼叫新的EventHandler()就足夠了。然後我們可以在表示式中使用該方法。

再次出現一個小問題。如您所知,如果您沒有此處理程式例項,則無法刪除事件處理程式。而且您無法建立事件例項,因為您不知道它的型別。我們唯一能做的就是儲存使用者處理程式(即Action<objec, object>)和真正的EventHandler <EventArgs>處理程式之間的某種關係。當然可以編寫EventAdd方法,其建立委託,返回事件處理程式例項。但對於希望以與事件相同的方式(或至少儘可能接近)與委託一起工作的終端使用者來說,這將是某種不直觀的方式。因為最好的想法是在DelegateFactory中將關係儲存在字典中——它對使用者來說是透明的。

private static readonly Dictionary<WeakReference<object>, WeakReference<object>> EventsProxies =
    new Dictionary<WeakReference<object>, WeakReference<object>>();

現在我們可以實現EventHandlerFactory方法。

public static EventHandler<TEventArgs> EventHandlerFactory<TEventArgs, TSource>(
    object handler, bool isRemove)
    where TEventArgs : class
{
    EventHandler<TEventArgs> newEventHandler;
    var haveKey = false;
    var kv = EventsProxies.FirstOrDefault(k =>
    {
        object keyTarget;
        k.Key.TryGetTarget(out keyTarget);
        if (Equals(keyTarget, handler))
        {
            haveKey = true;
            return true;
        }
        return false;
    });
    if (haveKey)
    {
        object fromCache;
        EventsProxies[kv.Key].TryGetTarget(out fromCache);
        newEventHandler = (EventHandler<TEventArgs>)fromCache;
        if (isRemove)
        {
            EventsProxies.Remove(kv.Key);
            return newEventHandler;
        }
    }
    if (!isRemove)
    {
        var action = handler as Action<TSource, object>;
        if (action != null)
        {
            newEventHandler = (s, a) => action((TSource)s, a);
        }
        else
        {
            newEventHandler = new EventHandler<TEventArgs>((Action<object, object>)handler);
        }
        EventsProxies[new WeakReference<object>(handler)] = new WeakReference<object>(newEventHandler);
        return newEventHandler;
    }
    return null;
}

想法很簡單。首先,我們在EventProxies字典中尋找已經生成的委託。如果儲存在WeakReference中的引用鍵等於提供的處理程式,我們儲存鍵值對結構並設定hasKey變數。我們必須使用單獨的bool變數,因為KeyValuePair型別是結構,我們不能對結構執行空檢查。如果找到鍵,我們從WeakReference目標中檢索事件處理程式。isRemove引數用於區分為新增訪問器和刪除訪問器檢索/建立事件處理程式。為了刪除事件處理程式,我們不需要先建立它(因為我們無法以這種方式執行處理程式),如果我們檢索已儲存的處理程式,我們可以同時從快取中刪除它。對於新增訪問器,如果在快取中找不到事件處理程式,我們可以建立新的訪問者。如果提供的動作處理程式具有型別不同於物件的第一個引數,我們需要另一個lambda代理和物件引數。 這是因為.NET中的事件始終具有物件的第一個引數,並且EventHandler不接受不同的簽名。否則,我們可以安全地直接使用動作,將其傳遞給EventHandler建構函式。這就是處理程式引數具有物件型別——我們需要一種方法來傳遞方法的不同簽名,具體取決於EventAdd / EventRemove的過載。儲存新建立的事件處理程式後,我們就完成了。

由於我們正在使用事件處理程式的不相容簽名,並且我們正在編寫這樣複雜的工廠方法以便建立正確的工具方法,我們還需要一個技巧來從表示式中使用它(並且沒有其他方法)。DelegateFactory類中的新欄位是足夠的。

private static readonly MethodInfo EventHandlerFactoryMethodInfo = typeof(DelegateFactory).GetMethod("EventHandlerFactory");

此欄位將以與任何其他MethodInfo物件類似的方式在表示式內使用,但在提供型別引數之後(因為它是泛型方法)。

現在我們可以使用提供的處理程式編寫具有源型別的第一個引數的EventAdd新實現。

public static Action<TSource, Action<TSource, object>> EventAdd<TSource>(string eventName)
{
    var source = typeof(TSource);
    var eventInfo = GetEventInfo(eventName, source);
    if (eventInfo != null)
    {
        var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0];
        var delegateType = typeof(Action<,>).MakeGenericType(typeof(TSource), typeof(object));
        var instanceParameter = Expression.Parameter(source);
        var delegateTypeParameter = Expression.Parameter(delegateType);
        var methodCallExpression =
            Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false));
        var lambda = Expression.Lambda(
                        Expression.Call(instanceParameter, eventInfo.AddMethod, methodCallExpression),
                        instanceParameter, delegateTypeParameter);
        return lambda.Compile() as Action<TSource, Action<TSource, object>>;
    }
    return null;
}

這個過載與前一個過載非常類似,除了直接傳遞委託給新增訪問器而不是傳遞EventHandlerFactory方法的結果。為此,我們需要從eventInfo.EventHandlerType獲取事件引數型別。對於型別,我們都建立非泛型方法,並使用不相容的處理程式作為引數並使用Boolean.False常量來呼叫它。這樣我們將從快取或EventHandler建構函式中獲取正確的處理程式。當然,delegateTypeParameter是物件型別(因為這是EventHandlerFactory方法中第一個引數的型別)而不是EventHandler <>。結果委託將類似於下面的lambda

Action<TestClass, object> d = (i, h) => i.PublicEvent += DelegateFactory.EventHandlerFactory<TestClass.PublicEventArgs,TestClass>(h, false);

現在我們可以實現EventAdd方法的最後一個過載並且更加可用,因為不需要知道任何型別(源型別或事件引數型別)。其實現如下。

public static Action<object, Action<object, object>> EventAdd(this Type source, string eventName)
{
    var eventInfo = GetEventInfo(eventName, source);
    if (eventInfo != null)
    {
        var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0];
        var instanceParameter = Expression.Parameter(typeof(object));
        var delegateTypeParameter = Expression.Parameter(typeof(object));
        var methodCallExpression =
            Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false));
        var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source),
                                        eventInfo.AddMethod, methodCallExpression),
                        instanceParameter, delegateTypeParameter);
        return lambda.Compile() as Action<object, Action<object, object>>;
    }
    return null;
}

與以前的程式碼相同。唯一的區別是產生的委託型別。這是因為我們編寫了EventHandlerFactory來支援兩者。因此,我們可以使用實現建立第三種方法並重寫前兩種方法。

private static TDelegate EventAddImpl<TDelegate>(this Type source, string eventName)
    where TDelegate : class
{
    var eventInfo = GetEventInfo(eventName, source);
    if (eventInfo != null)
    {
        var eventArgsType = eventInfo.EventHandlerType.GetGenericArguments()[0];
        var instanceParameter = Expression.Parameter(typeof(object));
        var delegateTypeParameter = Expression.Parameter(typeof(object));
        var methodCallExpression =
            Expression.Call(EventHandlerFactoryMethodInfo.MakeGenericMethod(eventArgsType, source), delegateTypeParameter, Expression.Constant(false));
        var lambda = Expression.Lambda(Expression.Call(Expression.Convert(instanceParameter, source),
                                        eventInfo.AddMethod, methodCallExpression),
                        instanceParameter, delegateTypeParameter);
        return lambda.Compile() as TDelegate;
    }
    return null;
}

public static Action<TSource, Action<TSource, object>> EventAdd<TSource>(string eventName)
{
    var source = typeof(TSource);
    return source.EventAddImpl<Action<TSource, Action<TSource, object>>>(eventName);
}

public static Action<object, Action<object, object>> EventAdd(this Type source, string eventName)
{
    return source.EventAddImpl<Action<object, Action<object, object>>>(eventName);
}

這是我們在不同情況下為新增訪問器建立委託所需的。現在我們可以測試它們,以確保一切正常。應按以下方式呼叫EventAdd過載。

var ea1 = DelegateFactory.EventAdd<TestClass, TestClass.PublicEventArgs>("PublicEvent");
var ea2 = DelegateFactory.EventAdd<TestClass, TestClass.InternalEventArgs>("InternalEvent");
var ea3 = DelegateFactory.EventAdd<TestClass>("ProtectedEvent");
var ea4 = Type.EventAdd<TestClass.PublicEventArgs>("PublicEvent");
var ea5 = Type.EventAdd("PrivateEvent");

要測試此委託是否有效,我們需要建立一些處理程式。

private static void HandlerWithoutSourceType(object o, TestClass.PublicEventArgs eventArgs)
{
    Console.WriteLine("Public handler without source type works!");
}

private static void HandlerWithSourceType(TestClass sender, object eventArgs)
{
    if (eventArgs.GetType() ==
        Type.GetNestedType("ProtectedEventArgs", BindingFlags.Instance | BindingFlags.NonPublic))
    {
        Console.WriteLine("Protected handler works!");
    }
}

private static void TypelessHandler(object sender, object eventArgs)
{
    if (eventArgs is TestClass.PublicEventArgs)
    {
        Console.WriteLine("Public handler works!");
    }
    else if (eventArgs is TestClass.InternalEventArgs)
    {
        Console.WriteLine("Internal handler works!");
    }
    else if (eventArgs.GetType() ==
        Type.GetNestedType("PrivateEventArgs", BindingFlags.Instance | BindingFlags.NonPublic))
    {
        Console.WriteLine("Private handler works!");
    }
}

現在我們可以使用委託將處理程式繫結到事件。

ea1(TestInstance, TypelessHandler);
ea2(TestInstance, TypelessHandler);
ea3(TestInstance, HandlerWithSourceType);
ea4(TestInstance, HandlerWithoutSourceType);
ea5(TestInstance, TypelessHandler);

這就是處理程式方法在控制檯中寫入不同文字集的原因——並非每個人都處理所有事件。

TestInstance.InvokePublicEvent();
TestInstance.InvokeInternalEvent();
TestInstance.InvokeProtectedEvent();
TestInstance.InvokePrivateEvent();

在通過上面的特殊公共方法呼叫事件之後,我們將在控制檯中看到如下輸出。

Public handler works!
Public handler without source type works!
Internal handler works!
Protected handler works!
Private handler works!

這意味著每個事件處理程式都可以工作,儘管可見性不同,使用者處理程式,自定義和預設訪問器的簽名也不同。 

事件刪除訪問者

刪除訪問器最好的一點是,有與新增訪問器相同的簽名。在EventHandlerFactory方法中處理刪除訪問器的主要問題,這需要完全相同的引用。在EventRemove方法中,我們只需