Будет ли здесь работать дерево выражений?

У меня есть метод ниже, который я в настоящее время использую, чтобы получить длину БД определенных строковых полей (каждое свойство имеет атрибут 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 ответа

Решение

Да, вы можете использовать выражения здесь только для строгой типизации имен свойств, поэтому вместо записи имени свойства в виде строки вы можете использовать выражение с доступом к свойству, а затем код получит элемент 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; }
}

Это будет работать, но только если внутри Main переменная obj имела общеизвестный тип. 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 для этого, и лямбда также может использовать его немедленно. Тем не менее, вам нужно иметь obj переменная. Второй пример полезен, когда вы знаете конкретный тип, но не имеете никакой переменной или экземпляра для передачи. Это редко однако.

В обоих случаях вы получаете Expression<Func<T,..>> что вам нужно проанализировать. Чаще всего вы получаете MemberExpression (свойство, поле) и из него вам нужно будет извлечь имя свойства. Но имейте в виду, что пользователь может написать что угодно в лямбду - для компилятора, o => { foreach(..) {..}; return 1;} так же хорошо, как o => o.PropertyName, поэтому вы должны определить, было ли передано правильное выражение, и обеспечить хотя бы минимальную обработку developerror.

BTW1. Вместо Func вы также можете использовать Action, но я думаю, что они странны в использовании в этом контексте.

Однако, поскольку вы фактически хотите сократить длину свойства объекта до некоторого ограничения, определенного для этого свойства, я думаю, что следующие инструменты будут более полезными или эргономичными:

1) помощник, который немедленно записывает значение (получите propertyinfo как с любым другим выражением, затем используйте тот факт, что помощник был вызван this obj и использовать его для вызова PropertyInfo.SetValue)

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

2) помощник, который обходит свойства и применяет все ограничения ко всем свойствам (мы знаем тип времени выполнения объекта, мы можем сканировать его свойства, получать их атрибуты, мы точно знаем, что нужно обрезать)

obj.TrimAllIfRequired();
Другие вопросы по тегам