Introduction
The following article shows one possibility to aggregate return values of multicasters in C#.
Since events, delegates, multicasting, and everything related
has been discussed many times before, I will completely ignore these topics and assume that the reader is familiar with them.
Background
I often have had the need to safely (and synchronously) invoke a (possible unassigned) event handler and aggregate the return values.
A very common pattern there is the before- and afterEdit pattern, where listeners have the possibility to cancel the edit.
MS, for example, does provide a Cancel
property in some of their
EventArgs
based event argument classes, for example the "Selecting" event
of the WinForms TabControl
has TabControlCancelEventArgs
with a boolean
Cancel
property.
Because there may be many listeners which potentially set the
Cancel
property to true
,
the invoker has to check if at least one of them is set and also has to decide, depending on the implementation, if all gets invoked or if the invocation stops as soon as a certain
condition is met.
Although, it's possible to implement every invocation by hand, it could make sense to encapsulate it somewhere.
The only closely related article I could find is the following:
Even if Microsoft suggests to always derive from EventArgs
and sums some good reasons, this point is in no case
the focus of this article.
The article uses Functions for the sake of simplicity.
Source code overview
A generic and aggregating invoker can be built very easy. First, the definition:
public static R InvokeAndAggregateFunc<R>(Func<R> multiCast, bool execAll, R defaultRet,
R initAggregate, Func<R, R, R> aggregate, Func<R, bool> breakCondition)
Func<R> multiCast
is the multicast function to invoke where
R
is the type of the return value
bool execAll
is used to specify if all targets will be invoked or if the invocations are stopped as soon as the
breakCondition
is met
R defaultRet
is the default return value for the case when there isn't anything to invoke (i.e., the invocation list is empty)
R initAggregate
is the initial value for the aggregation function
Func<R,R,R>
is the aggregation function, taking two parameters,
where the first is the current value of the aggregated value and the second is the return value of the current invocation
Func<R,bool>
is the break condition which is only evaluated if the parameter
execAll
is set to false
Because it's so easy and straightforward, I'm not going into the details of the implementation but just add the code as reference:
public static R InvokeAndAggregateFunc<R>(Func<R> multiCast, bool execAll, R defaultRet,
R initAggregate, Func<R, R, R> aggregate, Func<R, bool> breakCondition)
{
if (multiCast != null)
{
R ret = initAggregate;
foreach (Delegate d in multiCast.GetInvocationList())
{
Func<R> f = d as Func<R>;
R r = f.Invoke();
ret = aggregate(ret, r);
if (!execAll && breakCondition(ret))
break;
}
return ret;
}
else { }
return defaultRet;
}
You may have noticed at this point that the functions whose return values will be aggregated cannot have parameters at this time. To add this possibility,
the above static function can be duplicated and one or more generic parameter can be added:
public static R InvokeAndAggregateFunc<P1, P2, P3, P4, P5, R>(Func<P1, P2, P3, P4, P5,
R> multiCast, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, bool execAll, R defaultRet, R initAggregate,
Func<R, R, R> aggregate, Func<R, bool> breakCondition)
{
if (multiCast != null)
{
R ret = initAggregate;
foreach (Delegate d in multiCast.GetInvocationList())
{
Func<P1, P2, P3, P4, P5, R> f = d as Func<P1, P2, P3, P4, P5, R>;
R r = f.Invoke(p1, p2, p3, p4, p5);
ret = aggregate(ret, r);
if (breakCondition(ret) && !execAll)
break;
}
return ret;
}
else { }
return defaultRet;
}
Using the code
Using the code is simple. If you've got multicast, for example:
Func<bool> f1 = new Func<bool>(() => true);
f1 += new Func<bool>(() => false);
and like to know if all targets return true
, you could do the following:
bool bAnd = InvokeAndAggregateFunc(f1, true, false, true, (v1, v2) => v1 && v2, v => !v);
and if you like to know if at least one of them returns true
(and don't invoke them all if not needed to get the result), you could do the following:
bool bOr = InvokeAndAggregateFunc(f1, false, false, false, (v1, v2) => v1 || v2, v => v);
Points of Interest
Exceptions and performance are not covered by the article, as are all different kinds of multicast representations.
History
- 2012/09/05: First version submitted.