Aaron Anodide
Aaron Anodide

Reputation: 17186

Extension method that implicitly converts parameter to delegate?

I'm experimenting with an extension method that an object can use to fire its own events.

I've got it working almost how I would like but want to know if I can improve it to the point where the arguments passed could be converted to the constructor arguments of the EventArgs without resorting to Activator.

I'll say upfront that I'm doubtful this is possible but I'm going to give it a shot because sometimes I'm really surprised at the coding tricks others have...

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

Upvotes: 1

Views: 265

Answers (1)

Chris Sinclair
Chris Sinclair

Reputation: 23208

I've done something like this before but I took the path of using a new EventArgs/EventHandler wrapper instead. It uses implicit conversion and generics to automagically handle converting to/from the event 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);
    }
}

I put an overload with/without a sender, probably not a good idea, but you can play around with it at least.

Then the extension methods instead of placing on type object which kinda sucks 'cause then all objects (I think) have it shown/available in their intellisense even if it's not really applicable, I have it tied to the DataEventHandlers themselves:

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

(note I put it in the same namespace as the DataEventHandler so it's also automatically available/imported with them assuming you use the events with their namespace as a using statement)

The extension methods already know the argument type, but it doesn't get passed in as the args object yet. Rather it gets passed as its original type, then only in the final call eventHandler(sender, args) does it get implicitly converted to the event args if the event has registrants.

Your C class might look like:

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

Note that the event declarations in C don't explicitly mark themselves using DataEventHandler<DataEventArgs<string>>; that's handled by the delegate parameters implicitly.

Your calling code might look like:

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

Again, the event args can (but you don't have to) implicitly convert back to their wrapped data type when passed into methods.

Now, there's some drawbacks with this. Mainly, in my opinion, it kinda violates the standard .NET EventHandler practices regarding typing, difficult to make your own event args, etc. Especially since I don't end up creating my own EventArgs subclasses and instead just pass some data object around (either a basic value type, or my own custom class or data model). It's served me fairly well, but in practice I'm finding it more and more useless. I'm not advocating this style/implementation, but maybe it'll give you some ideas.

Upvotes: 2

Related Questions