Создать универсальный обработчик для всех событий и делегатов в C # - PullRequest
7 голосов
/ 14 апреля 2011

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

class Invoker
{
    public object Invoke(object[] arg)
    {
        // generic handling code
    }
}

static void Main()
{
    var p = new Person();
    p.AddHandler("Event1", new Invoker().Invoke);
}

AddHandler - это метод расширения для object, который получает имя события и делегат типа Func<object[], object>. Он должен иметь возможность использовать любую магию, чтобы связать событие (например, Event1 в данном случае) с предоставленным делегатом, чтобы делегат вызывался всякий раз, когда событие вызывается.

Подпись Event1 не должна иметь значения, поскольку AddHandler должна работать со всеми типами событий (и делегатами).

Я подозреваю, что это может потребовать некоторого поколения CIL для создания динамического делегата, соответствующего типу указанного события (например, Event1) и перенаправления вызова указанному делегату (например, new Invoker().Invoke). Мне удалось создать такой динамический делегат, однако он мог только перенаправлять статические методы, а не методы экземпляра, потому что я не мог найти способ вставить связанный экземпляр вызываемого метода в стек CLR (т. Е. Invoker экземпляр в примере). См. Приведенный ниже код, чтобы ясно увидеть эту проблему (см. Строку, помеченную ISSUE).

Если кто-то может указать способ улучшения кода динамической генерации для захвата связанного объекта или еще лучше, предложите более простое решение, не требующее CIL, тогда оно очень ценится.

public static void AddHandler(this object target, string fieldName,
    Func<object[], object> func)
{
    var eventInfo = target.GetType().GetEvent(fieldName);
    if (eventInfo != null)
    {
        Type delegateType = eventInfo.EventHandlerType;
        var dynamicHandler = BuildDynamicHandler(target.GetType(), delegateType, func);
        eventInfo.GetAddMethod().Invoke(target, new Object[] { dynamicHandler });
    }
}

public static Delegate BuildDynamicHandler(this Type delegateOwnerType, Type delegateType, 
    Func<object[], object> func)
{
    MethodInfo invokeMethod = delegateType.GetMethod("Invoke");
    Type returnType = invokeMethod.ReturnType;
    bool hasReturnType = returnType != Constants.VoidType;
    var paramTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
    var dynamicMethod = new DynamicMethod("add_handler",
                                            hasReturnType ? returnType : null, paramTypes, delegateOwnerType);

    var il = new EmitHelper(dynamicMethod.GetILGenerator());
    if (paramTypes.Length == 0)
    {
        il.ldnull.end();
    }
    else
    {
        il.DeclareLocal(typeof(object[]));
        il.ldc_i4(paramTypes.Length);
        il.newarr(typeof(object));
        il.stloc_0.end();
        for (int i = 0; i < paramTypes.Length; i++)
        {
            il.ldloc_0
                .ldc_i4(i)
                .ldarg(i)
                .boxIfValueType(paramTypes[i])
                .stelem_ref.end();
        }
        il.ldloc_0.end();
    }

    /////// ******************  ISSUE: work for static method only
    il.call(func.Method); 
    if (hasReturnType)
    {
        il.unbox_any(returnType).ret();
    }
    else
    {
        il.pop.ret();
    }
    return dynamicMethod.CreateDelegate(delegateType);
}

Ответы [ 3 ]

5 голосов
/ 14 апреля 2011

Вот реализация, использующая деревья выражений:

    public static Delegate BuildDynamicHandle(Type delegateType, Func<object[], object> func)
    {
        var invokeMethod = delegateType.GetMethod("Invoke");
        var parms = invokeMethod.GetParameters().Select(parm => Expression.Parameter(parm.ParameterType, parm.Name)).ToArray();
        var instance = func.Target == null ? null : Expression.Constant(func.Target);
        var converted = parms.Select(parm => Expression.Convert(parm, typeof(object)));
        var call = Expression.Call(instance, func.Method, Expression.NewArrayInit(typeof(object), converted));
        var body =
            invokeMethod.ReturnType == typeof(void) ? (Expression)call : Expression.Convert(call, invokeMethod.ReturnType);
        var expr = Expression.Lambda(delegateType, body, parms);
        return expr.Compile();
    }
2 голосов
/ 14 апреля 2011

Рассматривали ли вы использование деревьев выражений (http://msdn.microsoft.com/en-us/library/bb397951.aspx)? Они значительно упрощают генерацию IL.

0 голосов
/ 15 апреля 2011

Я разработал решение. Я написал об этом с полным кодом здесь , на случай, если кто-то заинтересован в подходе генерации чистого CIL (который не столь же элегантен, как подход kvb).

...