agiro
agiro

Reputation: 2080

Invocation order by a specific attribute

I have subscribed some methods to an event and wish to invoke them by the specific order I give them like so:

foreach (var method in LOAD_DEPENDENCIES.GetInvocationList()
        .OrderBy(x => x.Method.GetCustomAttributes(typeof(InvocationOrderAttribute),false)))
{
    method.DynamicInvoke(localStats, this);
}

where (of course) the event is LOAD_DEPENDENCIES and the attribute is InvocationOrderAttibute. Note that foreach's body works, the parameters of DynamicInvoke are not the issue.

That attribute looks like so:

public class InvocationOrderAttribute : Attribute , IComparable
{
    public int order;
    public InvocationOrderAttribute(int order)
    {
        this.order = order;
    }

    public int CompareTo(object obj)
    {
        return this.order.CompareTo((obj as InvocationOrderAttribute).order);
    }
}

I implemented the IComparable hoping that OrderBy will use that to determine the order.

This doesn't work, by debugging I checked that I never even enter the body of that foreach loop. ALL the subscribed methods have that attribute.

Question is, what am I doing wrong in the foreach loop's LINQ query or in the attribute?

EDIT:

it ain't the best but works:

foreach (var method in LOAD_DEPENDENCIES.GetInvocationList()
       .OrderBy(y => y.Method.GetCustomAttributes(false)
       .OfType<InvocationOrderAttribute>().FirstOrDefault().order))
{
    method.DynamicInvoke(localStats, this);
}

Upvotes: 0

Views: 283

Answers (1)

tinudu
tinudu

Reputation: 1199

.GetCustomAttributes returns an object[], so .OrderBy will use Comparer<object[]>.Default, that will throw exceptions at you as it wants to use some IComparable implementation on the type object[], which does not exist.

Instead you should use .GetCustomAttribute<InvocationOrderAttribute>, which returns the attribute or null if no such attribute is present. How do methods without the attribute compare to those having one?

Just wrote a small example that works with no event handlers as well as with event handlers not carrying the attribute, where the latter precede the former in the ordering. The event is of delegate type Action in the example (yours is something else).

EDIT: C# 4.0 capable version without CustomAttributeExtensions

The attribute:

[AttributeUsage(AttributeTargets.Method)]
public sealed class InvocationOrderAttribute : Attribute
{
    public int Order { get; private set; }

    public InvocationOrderAttribute(int order)
    {
        Order = order;
    }
}

New: useful method for all types of events

/// <summary>
/// Get individual handlers of the invocation list of the specified <paramref name="@event"/>,
/// ordered by the <see cref="InvocationOrderAttribute"/> of the handler's method.
/// </summary>
/// <typeparam name="TDelegate">Delegate type of the <paramref name="@event"/>.</typeparam>
/// <exception cref="ArgumentException"><typeparamref name="TDelegate"/> is not a delegate type.</exception>
/// <remarks>Handlers without the attribute come last.</remarks>
public static IEnumerable<TDelegate> OrderedInvocationList<TDelegate>(TDelegate @event)
{
    if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
        throw new ArgumentException(typeof(TDelegate) + " is not a delegate type.");

    if (@event == null) // empty invocation list
        return Enumerable.Empty<TDelegate>();

    return ((Delegate)(object)@event).GetInvocationList()
        .Select(handler =>
        {
            var attribute = (InvocationOrderAttribute)handler.Method.GetCustomAttributes(typeof(InvocationOrderAttribute), false).FirstOrDefault();
            return new
            {
                Handler = (TDelegate)(object)handler,
                Order = attribute != null ? attribute.Order : int.MaxValue
            };
        })
        .OrderBy(ho => ho.Order)
        .Select(ho => ho.Handler);
}

Usage:

public static class Program
{
    private static event Action Event;

    private static void RaiseEvent()
    {
        foreach (var h in MyAwesomeCode.OrderedInvocationList(Event))
            h();
    }

    [InvocationOrder(1)]
    private static void M1() { Console.WriteLine("M1"); }

    [InvocationOrder(2)]
    private static void M2() { Console.WriteLine("M2"); }

    private static void M3() { Console.WriteLine("M3"); }

    public static void Main()
    {
        RaiseEvent(); // works on empty invocation list

        Event += M3;
        Event += M2;
        Event += M1;
        Event += M3;
        Event += M2;
        Event += M1;

        RaiseEvent(); // works with methods not carrying the attribute
    }
}

Output: M1 M1 M2 M2 M3 M3

The improvements over your code (including 2nd solution):

  • No NullReferenceException if no handlers registered.
  • No NullReferenceException if some handler has no attribute.
  • Not reflecting the attribute during the sort.
  • Applicable for all event delegate types.
  • No .DynamicIvoke (ugly, not refactoring friendly, inefficient)
  • (Not calling a delegate "method", using standard casing of identifiers.)

Upvotes: 1

Related Questions