-->

Changing the return type of an expression>

2019-07-21 04:11发布

问题:

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.

回答1:

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);


回答2:

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.