特定属性的调用顺序

Invocation order by a specific attribute

我已经为一个事件订阅了一些方法,并希望按照我给它们的特定顺序调用它们,如下所示:

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

其中(当然)事件是 LOAD_DEPENDENCIES,属性是 InvocationOrderAttibute注意 foreach 的主体有效,DynamicInvoke 的参数不是问题。

该属性看起来像这样:

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

我实现了 IComparable 希望 OrderBy 将使用它来确定顺序。

这是行不通的,通过调试我检查过我什至从未进入 foreach 循环的主体。 ALL 订阅的方法具有该属性。

问题是,我在 foreach 循环的 LINQ 查询或属性中做错了什么?

编辑:

它不是最好的,但很有效:

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

.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.)