Search code examples
c#.netexpression-trees

How to create parameter which should implement multiple interfaces in expression tree?


I want to create parameter (instance of ParameterExpression class), which should implement multiple interfaces (let they are IComparable, IFormattable) like that:

//TypeWithIComparableIFormattable composite_type = ...;
ParameterExpression parameter = Parameter(composite_type, "composite_param");
// Usage specific methods of composite param from implemented interfaces

Solution

  • Despite the fact that a variable/parameter can be an instance of different types, you cannot delcare it like this:

    IComparable, IFormattable obj;
    

    To do multi-interface implementation check, you may:

    1. Delcare your own interface, say IComparableAndFormattable, inheriting from IComparable and IFormattable. But this approach requires that the type of parameter must implement the interface.

    2. Perform a runtime check. It's wordy, but keeps the code staying flexible:

    Use Expression.TypeAs() method to convert your parameter to the desired type.

    var param = Expression.Parameter(typeof(object), "o");
    // IComparable comparable;
    var comparableDeclare = Expression.Parameter(typeof(IComparable), "comparable");
    // comparable = o as IComparable;
    var comparableAssign = Expression.Assign(comparableDeclare, Expression.TypeAs(param, typeof(IComparable)));
    // if (comparable == (IComparable)null) 
    // {
    //    throw new ArgumentException("The parameter must be a instance of IComparable.", nameof(o));
    // }
    var comparableCheck = Expression.IfThen(Expression.Equal(comparableDeclare, Expression.Constant(null, typeof(IComparable))),
         ThrowNotTypeOf(typeof(IComparable), param.Name));
    
    var formattableDeclare = Expression.Parameter(typeof(IFormattable), "formattable");
    // formattable = o as IFormattable;
    var formattableAssign = Expression.Assign(formattableDeclare, Expression.TypeAs(param, typeof(IFormattable)));
    // if (formattable == (IFormattable)null) 
    // {
    //    throw new ArgumentException("The parameter must be a instance of IFormattable.", nameof(o));
    // }
    var formattableCheck = Expression.IfThen(
        Expression.Equal(formattableDeclare, Expression.Constant(null, typeof(IFormattable))),
        ThrowNotTypeOf(typeof(IFormattable), param.Name));
    
    var block = Expression.Block(
        new [] {
            comparableDeclare, formattableDeclare
        }, // local variables
        comparableAssign, comparableCheck, formattableAssign, formattableCheck);
    
    foreach (var exp in block.Expressions)
    {
        Console.WriteLine(exp);
    }
    
    
    // Compile the expression tree
    var method = Expression.Lambda<Action<object>>(block, param).Compile();
    
    method.Invoke(new ComparableFormattable());
    

    where ThrowNotTypeOf is a helper method generating a throw new ArgumentExceptionstatement:

    private static Expression ThrowNotTypeOf(Type type, string paramName)
    {
        var ctor = typeof(ArgumentException).GetConstructor(new[] { typeof(string), typeof(string) });
        Debug.Assert(ctor != null);
    
        var messageArg = Expression.Constant($"The parameter must be an instance of '{type.Name}'.");
        var paramArg = Expression.Constant(paramName);
    
        return Expression.Throw(Expression.New(ctor, messageArg, paramArg));
    }