表达树会在这里工作吗?

时间:2016-03-31 13:10:26

标签: c# lambda expression-trees

我有以下方法,我目前用来获取某些字符串字段的DB长度(每个属性都有一个名为MetaDataFieldAttribute的属性)。它似乎工作得很好,但它非常“串联”。键入属性名称。

     public static void Main(string[] args)
        {
            ...
            obj.PropertyName = str.TrimIfRequired(obj, "PropertyName");
        }

        public static string TrimIfRequired(this string str, object obj, string property)
        {
            var pi = obj.GetType().GetProperty(property);
            var attribute = pi.GetCustomAttributes<MetaDataFieldAttribute>().FirstOrDefault();
            if (attribute == null) return str;

            int length = attribute.Precision;
            return str.Length > length ? str.Substring(0, length) : str;
        }

我对表达式树有一个相当基本的理解,我最初的想法是我可以使方法的对象/参数强类型而不是仅仅通过字符串。这可能吗?

2 个答案:

答案 0 :(得分:1)

是的,你可以在这里使用表达式来获得属性名称的强类型,所以不是将属性名称写为字符串,而是可以使用具有属性访问权限的表达式,然后代码将从表达式获取特定属性的MemberInfo项目从这里获取属性。因此,如果您将来更改propertyName,则此部分将无法编译,因为它将是强类型的。

这里是.NetFiddle的工作样本 - https://dotnetfiddle.net/qYOfhE

以下是代码本身:

using System;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main(string[] args)
    {
        var str = "TestTestTest";

        var propertyName = str.TrimIfRequired<Customer>((c) => c.FirstName);
        var propertyNameValue = str.TrimIfRequired<Customer>((c) => c.Age);
        Console.WriteLine(propertyName);
        Console.WriteLine(propertyNameValue);
    }
}

public static class Extensions
{
    public static string TrimIfRequired<T>(this string str, Expression<Func<T, object>> expr)
    {
        var me = expr.Body as MemberExpression;
        if (me == null && expr.Body is UnaryExpression)
        {
            me = ((UnaryExpression) expr.Body).Operand as MemberExpression;
        }

        if (me == null)
        {
            throw new ArgumentException("Invalid expression. It should be MemberExpression");
        }

        var pi = me.Member;
        var attribute = pi.GetCustomAttributes(typeof(MetaDataFieldAttribute), false).FirstOrDefault() as MetaDataFieldAttribute;
        if (attribute == null) return str;

        int length = attribute.Precision;
        return str.Length > length ? str.Substring(0, length) : str;
    }
}

public class Customer
{
    [MetaDataField(Precision = 6)]
    public string FirstName { get; set; }

    [MetaDataField(Precision = 2)]
    public int Age { get; set; }
}


public class MetaDataFieldAttribute : Attribute
{
    public int Precision { get; set; }
}

答案 1 :(得分:0)

它会起作用,但只有在Main内,才能使用&#39; obj&#39;变量有一个众所周知的类型。 object是不够的,因为任何给定object作为引用类型的表达式树都不会看到任何实际成员。但由于您已经分配给PropertyName,因此您似乎已经输入了该变量。

您的TrimIfRequired也需要更改,即:

public static string TrimIfRequired<T>(this string str, T obj, Expression<Func<T,object>> propexpr)

像这样使用:

obj.PropertyName = str.TrimIfRequired(obj, o => o.PropertyName);

public static string TrimIfRequired<T>(this string str, Expression<Func<T,object>> propexpr)

像这样使用(没有对象实例,但明确说明了类型)

obj.PropertyName = str.TrimIfRequired<MyType>(o => PropertyName);

第一个很好,因为你只是给obj而C#自动选择T,lambda也可以立即使用它。但是,您需要拥有obj变量。当你知道具体的类型,但没有任何变量或实例可以传递时,第二个例子很有用。然而,这种情况很少见。

在这两种情况下,您都需要分析Expression<Func<T,..>>。通常,您将获得MemberExpression(属性,字段),然后您需要提取属性名称。但请记住,用户可以在lambda中编写任何内容 - 对于编译器,o => { foreach(..) {..}; return 1;}o => o.PropertyName一样好,因此您应检测是否传递了正确的表达式并提供至少一些最小的开发人员错误处理

BTW1。你也可以使用Action代替Func,但我认为在这种情况下它们的用法很奇怪。

但是,由于您实际上想要将对象的属性长度修剪为该属性上定义的某些限制,我认为以下工具会更有用或符合人体工程学:

1)帮助者立即写入值(获取propertyinfo与任何其他表达式一样,然后使用在this obj上调用助手的事实并使用它来调用PropertyInfo.SetValue

obj.TrimIfRequired(o => o.PropertyName);

2)帮助遍历属性并对所有属性应用所有限制(我们知道对象的运行时类型,我们可以扫描其属性,获取其属性,我们确切知道需要修剪的内容)

obj.TrimAllIfRequired();