繁体   English   中英

更改表达式的返回类型<func<> > </func<>

[英]Changing the return type of an expression<func<>>

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

我有以下 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; }
}

我有一个List<ImportCheck<Contact>>循环遍历,并为每个人在Contact object 上设置一个属性(属性都是不同的类型)。 为了使我能够设置嵌套对象的属性,我需要结果类型与目标类型相同。 如果联系人的所有属性都是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) ,其中 member 是Expression<Func<T,object>> target是 object, value是要将属性设置为的值。

请在下面找到示例:

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

当然; 您可以创建一个新的表达式树,将对象从对象强制转换为您想要的任何类型(当然,不保证强制转换将保持不变)-并且您可以在新表达式中使用旧表达式树的一部分(即整个 lambda 主体)树。

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

你的方法有几个问题:

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

我建议你以不同的方式处理这个问题。 与其处理类型不正确的Expression<Func<T,object>>对象并尝试从中生成 setter(和 getter?),我建议您从准确类型的Expression<Func<T,TProperty>> - 甚至只是一个PropertyInfo并生成类型化的getter 和 setter。 一旦你有一个Func<T,TProperty> getter和一个Action<T,TProperty> setter ,你可以很容易地包装它们以生成不太具体的操作和函数:

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

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

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

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

对于一个非常简单的情况,这是更改 Expression<Func<>> 的返回值的简单方法:

        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

只需在表达式主体上使用Expression.Convert

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

测试:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM