隐式将参数转换为委托的扩展方法?

时间:2012-09-29 01:19:31

标签: c# events extension-methods

我正在尝试一种对象可以用来触发自己事件的扩展方法。

我的工作几乎是我想要的,但想知道我是否可以改进它,以便传递的参数可以转换为EventArgs的构造函数参数而不需要使用Activator。

我会提前说我很怀疑这是可能的,但我会试一试,因为有时我真的对其他人的编码技巧感到惊讶......

void Main()
{
    var c = new C();
    c.E += (s, e) => Console.WriteLine (e.Message);
    c.Go();
}

public class C
{
    public event EventHandler<Args> E;
    public void Go()
    {
        Console.WriteLine ("Calling event E...");

        // This version doesn't know the type of EventArgs so it has to use Activator
        this.Fire(E, "hello");

        // This version doesn't know ahead of time if there are any subscribers so it has to use a delegate
        this.Fire(E, () => new Args("world"));

        // Is there some way to get the best of both where it knows the type but can delay the 
        // creation of the event args?
        //this.Fire<Args>("hello");
    }
}

public class Args : EventArgs
{
    public Args(string s)
    {
        Message = s;
    }
    public string Message { get; set; }
}

public static class Ext
{
    public static void Fire<T>(this object source, EventHandler<T> eventHander, Func<T> eventArgs) where T : EventArgs
    {
        if (eventHander != null)
            eventHander(source, eventArgs());
    }

    public static void Fire<T>(this object source, EventHandler<T> eventHander, params object[] args) where T : EventArgs
    {
        if (eventHander != null)
            eventHander(source, (T)Activator.CreateInstance(typeof(T), args));
    }
}

1 个答案:

答案 0 :(得分:2)

之前我做过类似的事情,但我采取了使用新的EventArgs / EventHandler包装器的路径。它使用隐式转换和泛型来自动处理与事件args的转换。

public delegate void DataEventHandler<TSender, TEventArgs>(TSender sender, DataEventArgs<TEventArgs> eventArgs);
public delegate void DataEventHandler<TEventArgs>(DataEventArgs<TEventArgs> eventArgs);

public class DataEventArgs<TEventArgs>
{
    public TEventArgs Args { get; private set; }

    public DataEventArgs(TEventArgs args)
    {
        this.Args = args;
    }

    public static implicit operator TEventArgs(DataEventArgs<TEventArgs> args)
    {
        return args.Args;
    }

    public static implicit operator DataEventArgs<TEventArgs>(TEventArgs args)
    {
        return new DataEventArgs<TEventArgs>(args);
    }
}

我在发送/不发送过载时,可能不是一个好主意,但你至少可以玩它。

然后扩展方法而不是放置在类型object上而不是因为所有对象(我认为)使它在智能感知中显示/可用,即使它不是真的适用,我把它绑定到DataEventHandlers本身:

public static class MyExtensions
{
    public static void Fire<TSender, TEventArgs>(this DataEventHandler<TSender, TEventArgs> eventHandler, TSender sender, TEventArgs args)
    {
        if (eventHandler!= null)
            eventHandler(sender, args);
    }

    public static void Fire<TEventArgs>(this DataEventHandler<TEventArgs> eventHandler, TEventArgs args)
    {
        if (eventHandler != null)
            eventHandler(args);
    }
}

(注意我把它放在与DataEventHandler相同的命名空间中,因此它也可以自动使用/导入它们,假设您使用其命名空间作为using语句的事件)

扩展方法已经知道了参数类型,但它还没有作为args对象传入。相反,它会作为原始类型传递,然后仅在最终调用eventHandler(sender, args)中,如果事件具有注册人,它会隐式转换为事件args

您的C课程可能如下:

public class C
{
    public event DataEventHandler<string> E;
    public event DataEventHandler<C, string> EWithSender;

    public void Go()
    {
        Console.WriteLine ("Calling event E...");

        E.Fire("hello");
        EWithSender.Fire(this, "hello");
    }
}

请注意,C中的事件声明未使用DataEventHandler<DataEventArgs<string>>明确标记自己;这是由委托参数隐式处理的。

您的主叫代码可能如下所示:

C c = new C();
c.E += (args) => PrintOut(args);
c.EWithSender += (sender, args) => Console.WriteLine("Sender Type: " + sender.GetType().Name + " -> Args: " + args.Args);
c.Go();


private void PrintOut(string text)
{
    Console.WriteLine(text);
}

同样,事件args 可以(但你不必)在传递给方法时隐式转换回它们的包装数据类型。

现在,这有一些缺点。主要是,在我看来,它有点违反标准的.NET EventHandler关于打字的做法,很难制作你自己的事件args等等。特别是因为我最终没有创建自己的EventArgs子类而只是传递一些数据对象(基本值类型,或我自己的自定义类或数据模型)。它很好地服务于我,但实际上我发现它越来越没用了。我不是在提倡这种风格/实现,但也许它会给你一些想法。