更改表达式的返回类型<func <>&gt;

时间:2015-07-09 21:38:43

标签: c# linq-expressions

假设我有一个Expression<Func<T,object>>是否可以根据Type变量动态更改返回类型Expression<Func<T,int>>

我有以下课程:

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

我有一个List<ImportCheck<Contact>>我循环通过,并为Contact对象设置了一个属性(属性都是不同的类型)。为了使我能够设置嵌套对象的属性,我需要将结果类型与目标类型相同。 如果联系人的所有属性都是int,那么我现在所拥有的就可以正常运行,因为我有一个不同类型的列表,这让我头疼。

这就是我设置子属性的方法:

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

然后调用MakeSet(member)(target,value),其中成员Expression<Func<T,object>> target是对象,value是设置属性的值。

2 个答案:

答案 0 :(得分:3)

请找到以下示例:

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

用法:

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

答案 1 :(得分:1)

不确定;你可以创建一个新的表达式树,其中包含从对象到你想要的任何类型的强制转换(当然,没有任何保证人可以保存) - 并且你可以在新表达式中使用旧表达式树的一部分(即整个lambda体)树。

但是,即使你创建了这样的东西,请注意,如果你想静态地表达表达式的类型 - 例如Expression<Func<T,int>>你需要静态地知道这个类型。泛型可以工作 - 但运行时Type变量不是。

您的方法存在一些问题:

  • 您认为fetcherExp.Body是会员访问权限,例如obj.TheProperty。但是,如果表达式的类型为Expression<Func<T,object>>,则任何value-type属性都将表示为“Convert(obj.TheProperty)”。
  • 你假设有一个与getter相对应的setter。
  • 您认为Type属性是正确的。

我建议你以不同的方式解决这个问题。我建议您从准确键入的Expression<Func<T,object>>开始 - 或者甚至只是Expression<Func<T,TProperty>>并生成,而不是处理不正确键入的PropertyInfo对象并尝试生成setter(和getter?) 键入 getter和setter。获得Func<T,TProperty> getterAction<T,TProperty> setter之后,您可以轻松地将其包装起来以生成不太具体的操作和功能:

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

类似的方法对于getter很有用(但这只对值类型属性的getter很重要,因为covariance意味着引用类型的getter都可以转换为Action<T,object>)。

如果您绝对需要最大的运行时性能,您可以使用Expression<...>执行完全相同的包装技巧并内联嵌套调用typedSetter,但请注意,您没有赢得那么多;对于大多数应用程序来说,一个和两个委托调用之间的区别不大。

TL; DR:不要使用Expression<Func<T,object>>作为无类型属性的中间表示;这样做会丢弃对创建getter / setter有用的类型信息。相反,使用类型化表达式Expression<Func<T,TProperty>>可以轻松生成无类型的getter和setter,并将那些作为中间表示传递。

相关问题