Reputation: 2080
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
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):
NullReferenceException
if no handlers registered.NullReferenceException
if some handler has no attribute..DynamicIvoke
(ugly, not refactoring friendly, inefficient)Upvotes: 1