The code part I: EventTester with expression trees

In my last post I gave a quick overview over the event tester usage and how you could benefit from such a component. In this post I want to show you the source code of my event tester implementation and give you a short dive into the expression tree magic that is happening behind the scenes. This time I show you first not only portions of the code but the whole code in advance annotated with line numbers. My explanations will point to line numbers in the code below.

    public class EventTester<TEventArgs>
        where TEventArgs : EventArgs
    {
        private readonly Delegate eventHandler;

        private readonly EventAttachBehavior eventAttachBehavior;

        private int fireCounter;

        protected enum EventAttachBehavior
        {
            Default,
            AutoAttach,
        }

        public EventTester(object sender, string eventName, Action<object, TEventArgs> eventHandler)
            : this(sender, eventName, eventHandler, (s, e) => true, EventAttachBehavior.Default)
        {
        }

        protected EventTester(object sender, string eventName, Action<object, TEventArgs> eventHandler, EventAttachBehavior autoAttachEvent)
            : this(sender, eventName, eventHandler, (s, e) => true, autoAttachEvent)
        {
        }

        public EventTester(object sender, string eventName, Action<object, TEventArgs> eventHandler, Func<object, TEventArgs, bool> matcher)
            : this(sender, eventName, eventHandler, matcher, EventAttachBehavior.Default)
        {
        }

        protected EventTester(object sender, string eventName, Action<object, TEventArgs> eventHandler, Func<object, TEventArgs, bool> matcher, EventAttachBehavior autoAttachToEvent)
        {
            this.eventAttachBehavior = autoAttachToEvent;
            this.eventHandler = this.Create(sender, eventName, eventHandler, matcher);
        }

        public int FireCounter
        {
            get { return this.fireCounter; }
        }

        private Delegate Create(object sender, string eventName, Action<object, TEventArgs> eventHandler, Func<object, TEventArgs, bool> matcher)
        {
            EventInfo eventInfo = GetSenderEventWithName(sender, eventName);
            Type handlerType = GetEventHandlerType(eventInfo);
            ParameterInfo[] eventParams = GetEventHandlerParameters(handlerType);

            eventHandler = AttachAdditionalActionsWhichAreCalledWithTheHandler(eventHandler, (s, e) => { this.fireCounter++; });
            IEnumerable<ParameterExpression> parameters = GetParameterExpressionsFor(eventParams);
            ParameterExpression[] paramArray = parameters.ToArray();

            ConditionalExpression ifThenElseBody = Expression.Condition(GetIfExpression(matcher, paramArray), GetThenExpression(eventHandler, paramArray), this.GetElseExpression());

            Expression<Action<object, TEventArgs>> lambdaExpression = GetLambdaExpression(paramArray, ifThenElseBody);

            Delegate eventHandlerDelegate = GetDynamicEventHandler(handlerType.IsGenericType ? typeof(EventHandler<TEventArgs>) : handlerType, lambdaExpression);

            if (this.eventAttachBehavior == EventAttachBehavior.AutoAttach)
            {
                AttachEventHandlerToSender(sender, eventInfo, eventHandlerDelegate);
            }

            return eventHandlerDelegate;
        }

        private static void AttachEventHandlerToSender(object sender, EventInfo eventInfo, Delegate eventHandlerDelegate)
        {
            eventInfo.AddEventHandler(sender, eventHandlerDelegate);
        }

        private static Delegate GetDynamicEventHandler(Type handlerType, Expression<Action<object, TEventArgs>> lambdaExpression)
        {
            return Delegate.CreateDelegate(handlerType, lambdaExpression.Compile(), "Invoke", false);
        }

        private static Expression<Action<object, TEventArgs>> GetLambdaExpression(ParameterExpression[] paramArray, ConditionalExpression ifThenElseBody)
        {
            return Expression.Lambda<Action<object, TEventArgs>>(ifThenElseBody, paramArray);
        }

        private static IEnumerable<ParameterExpression> GetParameterExpressionsFor(ParameterInfo[] eventParams)
        {
            return eventParams.Select(p => Expression.Parameter(p.ParameterType, "x"));
        }

        private InvocationExpression GetElseExpression()
        {
            return Expression.Invoke(Expression.Constant(new Action(() => { })));
        }

        private static InvocationExpression GetThenExpression(Action<object, TEventArgs> eventHandler, ParameterExpression[] paramArray)
        {
            return Expression.Invoke(Expression.Constant(eventHandler), paramArray);
        }

        private static InvocationExpression GetIfExpression(Func<object, TEventArgs, bool> matcher, ParameterExpression[] paramArray)
        {
            return Expression.Invoke(Expression.Constant(matcher), paramArray);
        }

        private static Action<object, TEventArgs> AttachAdditionalActionsWhichAreCalledWithTheHandler(Action<object, TEventArgs> eventHandler, Action<object, TEventArgs> additionalAction)
        {
            eventHandler += additionalAction;
            return eventHandler;
        }

        private static ParameterInfo[] GetEventHandlerParameters(Type handlerType)
        {
            return handlerType.GetMethod("Invoke").GetParameters();
        }

        private static Type GetEventHandlerType(EventInfo eventInfo)
        {
            return eventInfo.EventHandlerType;
        }

        private static EventInfo GetSenderEventWithName(object sender, string eventName)
        {
            return sender.GetType().GetEvent(eventName);
        }

        public static implicit operator EventHandler<TEventArgs>(EventTester<TEventArgs> eventTester)
        {
            return (EventHandler<TEventArgs>)eventTester.eventHandler;
        }

        public static implicit operator EventHandler(EventTester<TEventArgs> eventTester)
        {
            return (EventHandler)eventTester.eventHandler;
        }
    }

The event tester offers a lot of constructor overloads but I’ll only explain the constructor in line 031. The first parameter is the sender of the event and also the type which declares the event with name eventName. The third parameter is the action delegate which serves as event handler and will be attached to the event with name eventName when the auto attach behavior is specified. The fourth parameter is the event matcher. The event matcher is a function delegate which returns a boolean type indicating whether the event handler attached to the event with name eventName shall be executed and the execution shall be counted. The fifth¬† parameter defines the behavior whether the event handler shall be automatically attached to the event with name eventName. By default the event handler is not attached and the client must do it manually.

When creating a new instance of the event tester the behavior is assigned to the behavior field and the helper method Create is called. On line 044 the event information is retrieved by using reflection on the sender type. When the event information is retrieved the type of the event handler is determined (line 045) and all its parameter information is queried on that specific handler type (line 046). On line 048 the anonymous action delegate which increments the fire counter is attached to the retrieved event information. This forces to increment the fire counter every time the event handler is fired. On line 048 starts the expression tree magic which is actually the core topic of this article. Let’s deep dive!

On line 049 the method GetParameterExpressionsFor is called which does actually nothing more than turning all parameter information objects into ParameterExpression objects by using LINQ. Every parameter is named “x” in absence of creativity. The enumerable of ParameterExpression is then turned into a ParameterExpression array on line 050.¬† The next expression tree part we want to achieve is the statement described in the pseudo code below:

if(matcher(sender, eventargs))
{
   // Fire event handler delegate and increment fire counter
}
else
{
   // Do nothing
}

To achieve this we need to build an expression tree representing an if/else which is called ConditionalExpression. We can build a ConditionalExpression by using the static method Condition on the Expression class. The condition method takes three parameters which are also expressions. The first parameter is the test expression tree or the IF condition. The second parameters is the expression tree which is called when the test expression matches (therefore the THEN part). The third parameter is the expression tree which is called when the test expression does not match therefore the ELSE part.

The method GetIfExpression takes two parameters. The first parameter is the matcher function delegate and the second parameter is the ParameterExpression array which contains the parameter expression which must be passed to the matcher function delegate. The get the actual test expression we simply build an InvokeExpression by calling the Expression.Invoke method. To call the matcher delegate we simply build a ConstantExpression by calling Expression.Constant passing in the matcher function delegate. The InvokeExpression needs the ParameterExpression to know what kind of parameters must be passed to the ConstantExpression which is the expression that is actually invoked.

The method GetThenExpression is similar to the GetIfExpression but simply builds an InvokeExpression which calls the event handler delegate. The GetElseExpression method builds an InvokeExpression which calls an anonymous delegate which does nothing (therefore no actual logic is executed when reaching the ELSE part).

The next part of the article is going to describe the left parts of the expression tree building and the left helper methods.

About the author

Daniel Marbach

Add comment

By Daniel Marbach

Recent Posts