Changing the return type of an expression<func<>>
Asked Answered
S

4

7

Say I have an Expression<Func<T,object>> is it possible to dynamically change the return type based on a Type variable to be something like Expression<Func<T,int>>

I have the following class:

public class ImportCheck<T> {

    public int id { get; set; }
    public string Name { get; set; }
    public Type Type { get; set; }
    public bool Required { get; set; }
    public int? MinLength { get; set; }
    public int? MaxLength { get; set; }
    public string Value { get; set; }
    public Expression<Func<T, object>> AssociatedProperty { get; set; }
}

I have a List<ImportCheck<Contact>> which I loop through and for each one set a property on the Contact object (the properties are all different types). To enable me to set the property of nested objects I need the result type to be the same as the target type. If all the properties of the contact were say int then what I have now would work fine it's the fact that I have a list of different types that is causing me the headache.

This is how I set a sub property:

private static Action<M, R> MakeSet<M, R>(Expression<Func<M, R>> fetcherExp) {
            if (fetcherExp.Body.NodeType != ExpressionType.MemberAccess) {
                throw new ArgumentException(
                    "This should be a member getter",
                    "fetcherExp");
            }

            //    Input model 
            var model = fetcherExp.Parameters[0];
            //    Input value to set 
            var value = Expression.Variable(typeof(R), "v");
            //    Member access 
            var member = fetcherExp.Body;
            //    We turn the access into an assignation to the input value 
            var assignation = Expression.Assign(member, value);
            //    We wrap the action into a lambda expression with parameters 
            var assignLambda = Expression.Lambda<Action<M, R>>(assignation, model, value);

            return assignLambda.Compile();
        }

This is then called like MakeSet(member)(target,value) where member is the Expression<Func<T,object>> target is the object and value is the value to set the property to.

Scotsman answered 9/7, 2015 at 21:38 Comment(4)
Can you be more detailed in what you're trying to do? In particular including example code that doesn't quite do what you want?Slacks
Can you show an example of trying to set the sub-property? In particular, is this explicit case-by-case code, or in some way generalized?Slacks
@EamonNerbonne added some detail about how I set the property. As my expression result is an object this comes through as a UnaryExpression rather than a MemberExpression which is what is causing me issuesScotsman
I'm guessing you're trying to abstract properties on a C# class to delegate getters and setters that deal with objects, right? I'm going to hone in on that, rather that deal with the exact question, since I suspect it's more useful (and much easier to do).Slacks
F
7

Please find the example below:

public class ReturnTypeVisitor<TSource, TReturnValue> : ExpressionVisitor{

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        var delegateType = typeof(Func<,>).MakeGenericType(typeof(TSource), typeof(TReturnValue));
        return Expression.Lambda(delegateType, Visit(node.Body), node.Parameters);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Member.DeclaringType == typeof(TSource))
        {
            return Expression.Property(Visit(node.Expression), node.Member.Name);
        }
        return base.VisitMember(node);
    }
}

Usage:

public class Foo{
    public Bar Bar { get; set; }
}

public class Bar { }

Expression<Func<Foo, object>> expression = p => p.Bar;
Expression<Func<Foo, Bar>> stronglyTypedReturnValue = (Expression<Func<Foo, Bar>>)new ReturnTypeVisitor<Foo, Bar>().Visit(expression);
Fluellen answered 3/2, 2017 at 4:13 Comment(0)
H
3

Just use Expression.Convert on expression body:

public static class ExpressionHelper
{
    public static Expression<Func<TSource, TConvertedResult>> ConvertResult<TSource, TResult, TConvertedResult>(Expression<Func<TSource, TResult>> expression)
    {
        return Expression.Lambda<Func<TSource, TConvertedResult>>(Expression.Convert(expression.Body, typeof(TConvertedResult)), expression.Parameters);
    }
}

Tests:

[TestFixture]
public class ExpressionHelperTest
{
    public class BaseClass
    {
        public BaseClass(bool boolean)
        {
            Boolean = boolean;
        }

        public bool Boolean { get; set; }
    }

    public class DerivedClass : BaseClass
    {
        public DerivedClass(bool boolean, int integer) : base(boolean)
        {
            Integer = integer;
        }

        public int Integer { get; set; }
    }

    [Test]
    public void ConvertResult_Simple_Test()
    {
        Expression<Func<int, bool>> notNullExpression = i => i != 0;
        Expression<Func<int, object>> notNullObjectResultExpression = ExpressionHelper.ConvertResult<int, bool, object>(notNullExpression);

        Func<int, bool> notNull = notNullExpression.Compile();
        Func<int, object> notNullObjectResult = notNullObjectResultExpression.Compile();

        Assert.True(notNull(1));
        Assert.False(notNull(0));
        Assert.AreEqual(true, notNullObjectResult(1));
        Assert.AreEqual(false, notNullObjectResult(0));

        Assert.Pass();
    }

    [Test]
    public void ConvertResult_Inheritance_Test()
    {
        Expression<Func<(bool boolean, int integer), DerivedClass>> derivedClassExpression = x => new DerivedClass(x.boolean, x.integer);
        Expression<Func<(bool boolean, int integer), BaseClass>> baseClassExpression = ExpressionHelper.ConvertResult<(bool boolean, int integer), DerivedClass, BaseClass>(derivedClassExpression);
        Expression<Func<(bool boolean, int integer), DerivedClass>> derivedClassFromBaseClassExpression = ExpressionHelper.ConvertResult<(bool boolean, int integer), BaseClass, DerivedClass>(baseClassExpression);

        Func<(bool boolean, int integer), DerivedClass> derivedClass = derivedClassExpression.Compile();
        Func<(bool boolean, int integer), BaseClass> baseClass = baseClassExpression.Compile();
        Func<(bool boolean, int integer), DerivedClass> derivedClassFromBaseClass = derivedClassFromBaseClassExpression.Compile();

        (bool boolean, int integer) trueAndOne = (true, 1);
        (bool boolean, int integer) falseAndZero = (false, 0);

        Assert.True(derivedClass(trueAndOne).Boolean);
        Assert.False(derivedClass(falseAndZero).Boolean);
        Assert.AreEqual(1, derivedClass(trueAndOne).Integer);
        Assert.AreEqual(0, derivedClass(falseAndZero).Integer);

        Assert.True(baseClass(trueAndOne).Boolean);
        Assert.False(baseClass(falseAndZero).Boolean);

        Assert.True(derivedClassFromBaseClass(trueAndOne).Boolean);
        Assert.False(derivedClassFromBaseClass(falseAndZero).Boolean);
        Assert.AreEqual(1, derivedClassFromBaseClass(trueAndOne).Integer);
        Assert.AreEqual(0, derivedClassFromBaseClass(falseAndZero).Integer);

        Assert.Pass();
    }
}
Hannie answered 11/4, 2022 at 14:17 Comment(0)
S
1

Sure; you can create a new expression tree with a cast from object to whatever type you want (no guarrantees the cast will hold, of course) - and you can use parts of the old expression tree (namely the entire lambda body) in your new expression tree.

However, even if you create such a thing, note that if you want to express the type of the expresion statically - e.g. Expression<Func<T,int>> you're going to need to know the type statically. Generics would work - but a runtime Type variable isn't.

There are several problems with your approach:

  • You assume that fetcherExp.Body is a member access, e.g. obj.TheProperty. However, if the expression is of type Expression<Func<T,object>> then any value-type property will be represented as a "Convert(obj.TheProperty)".
  • You assume there's a setter corresponding to the getter.
  • You assume that the Type property is correct.

I suggest you approach this problem differently. Instead of dealing with improperly typed Expression<Func<T,object>> objects and trying to generate setters (and getters?) from that, I suggest you start from an accurately typed Expression<Func<T,TProperty>> - or even just a PropertyInfo and generate typed getters and setters. Once you have a Func<T,TProperty> getter and an Action<T,TProperty> setter, you can easily wrap those to generate less specific actions and funcs:

public static Action<T,object> UntypeSetter<T,TProperty>(Action<T,TProperty> typedSetter) =>
    (o, val) => typedSetter(o, (TProperty)val);

A similar approach is useful for getters (but this only matters for getters of value-type properties since covariance means that reference type getters can all be cast to Action<T,object>).

If you absolutely need maximal runtime performance, you can do exactly the same wrapping trick with Expression<...>s and inline the nested call to typedSetter, but note that you're not winning that much; the difference between one and two delegate calls is unlikely to matter for most applications.

TL;DR: Don't use Expression<Func<T,object>> as an intermediate representation of an untyped property; doing so throws away type information useful to creating getters/setters. Instead, use a typed expression Expression<Func<T,TProperty>> to easily generate untyped getters and setters, and pass those around as your intermediate representation.

Slacks answered 9/7, 2015 at 21:44 Comment(1)
Thanks, do you have an exampleScotsman
H
0

Here is a simple way to change the return value of an Expression<Func<>> for a very simple case:

        Expression<Func<int, bool>> x = t => t > 1;
        
        var prmi = Expression.Parameter(typeof(int), "i");
        var lambda = Expression.Lambda<Func<int, int>>(Expression.Convert(Expression.Invoke(x,prmi), typeof(int)),prmi);

        var i = lambda.Compile().Invoke(2); //returns an int
Hermie answered 12/1, 2022 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.