1

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

我已经让它几乎按照我想要的方式工作,但我想知道我是否可以将它改进到可以将传递的参数转换为 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));
    }
}
4

1 回答 1

2

我以前做过类似的事情,但我选择使用新的 EventArgs/EventHandler 包装器。它使用隐式转换和泛型来自动处理与事件参数的转换。

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

(请注意,我将它放在与 the 相同的命名空间中,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);
}

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

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

于 2012-09-29T02:06:25.757 回答