Использование дерева выражения для чтения имени и значения свойства. Есть ли альтернатива?

Предположения

Предположим, у меня есть класс со свойством:

class ClassWithProperty
{
    public string Prop { get; private set; }

    public ClassWithProperty(string prop)
    {
        this.Prop = prop;
    }
}

А теперь предположим, что я создал экземпляр этого класса:

var test = new ClassWithProperty("test value");

Чего я хочу

Я хочу, чтобы это было распечатано на консоли:

Prop = 'test value'

Кусок пирога! Я использую этот код для получения желаемого результата:

Console.WriteLine("Prop = '{1}'", test.Prop);

Проблема

Я нарушаю DRY, потому что "Prop" дважды встречается в приведенном выше коде. Если я реорганизую класс и изменю имя свойства, мне также придется изменить строковый литерал. Также есть много шаблонного кода, если бы у меня был класс со многими свойствами.

Предлагаемое решение

string result = buildString(() => c.Prop);
Console.WriteLine(result);

Где метод buildString выглядит так:

private static string buildString(Expression<Func<string>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    string propertyName = memberExpression.Member.Name;

    Func<string> compiledFunction = expression.Compile();
    string propertyValue = compiledFunction.Invoke();

    return string.Format("{0} = '{1}'", propertyName, propertyValue);
}

Вопрос

Вышеупомянутое решение работает нормально, и я доволен им, но если есть более простой и менее «страшный» способ решить эту проблему, это сделает меня намного счастливее. Есть ли более легкая альтернатива для достижения того же результата с меньшим и более простым кодом? Может, что-нибудь без деревьев выражений?

Изменить

Основываясь на прекрасной идее Ману (см. Ниже), я мог бы использовать этот метод расширения:

static public IEnumerable<string> ListProperties<T>(this T instance)
{
    return instance.GetType().GetProperties()
        .Select(p => string.Format("{0} = '{1}'",
        p.Name, p.GetValue(instance, null)));
}

Он отлично подходит для получения строкового представления всех свойств экземпляра.

Но: как из этого перечисления выбрать типизированное конкретное свойство? Я бы снова использовал выражение «деревья» ... или я не вижу леса за деревьями?

Изменить 2

Использование здесь отражений или деревьев выражений - дело вкуса.

Идея Люка использовать инициализаторы проекции просто гениальна. Из его ответа я наконец создаю этот метод расширения (который в основном представляет собой версию его ответа LINQ):

public static IEnumerable<string> BuildString(this object source)
{
    return from p in source.GetType().GetProperties()
           select string.Format("{0} = '{1}'", p.Name, p.GetValue(source, null));
}

Теперь звонок будет выглядеть так:

new { c.Prop }.BuildString().First()

Это выглядит немного лучше, чем мой исходный вызов с лямбдой (но, думаю, это тоже дело вкуса). Предложение Люка, однако, превосходит мое решение, поскольку оно позволяет указать сколько угодно свойств (см. Ниже).


person Theo Lenndorff    schedule 02.12.2009    source источник


Ответы (5)


Вы можете передать анонимный тип методу BuildStrings, используя инициализаторы проекции для автоматического создания имен и значений свойств анонимного типа. Внутри метода вы должны использовать отражение, чтобы исследовать эти свойства.

Это также позволит вам передать несколько предметов, если хотите. Вы также можете передавать поля и локальные переменные, а также свойства, потому что все они будут проецироваться как свойства анонимного типа, которые затем могут быть опрошены с помощью GetProperties и т. Д. (Конечно, все, что на самом деле делает метод, это перечисляет все свойства переданный объект. Вы можете передать любой тип.)

string result = BuildStrings(new { test.Prop }).First();
Console.WriteLine(result);

// or

string foo = "Test";
int bar = 42;

string results = BuildStrings(new { foo, bar, test.Prop });
foreach (string r in results)
{
    Console.WriteLine(r);
}

// ...

public static IEnumerable<string> BuildStrings(object source)
{
    return source.GetType().GetProperties().Select(
        p => string.Format("{0} = '{1}'", p.Name, p.GetValue(source, null)));
}
person LukeH    schedule 03.12.2009
comment
Это альтернатива деревьям выражений с возможностью выбора определенного свойства. Мне это нравится ... - person Theo Lenndorff; 04.12.2009
comment
Это круто. Если вы хотите повысить производительность, вы можете кэшировать тип в статическую переменную и выполнить выбор против нее. - person Donny V.; 07.03.2011

Мне действительно нравится ваша первоначальная идея использования дерева выражений. Это хороший вариант использования для ET. Но это выглядит немного пугающим, потому что вы компилируете и выполняете дерево выражений, чтобы получить значение свойства, а все, что вам нужно, - это просто имя. Сделайте так, чтобы метод возвращал только имя, а затем используйте его, как вы делали в своей первой «не СУХИЙ» попытке.

    private static string buildString(Expression<Func<string>> expression) 
    { 
        MemberExpression memberExpression = (MemberExpression)expression.Body; 
        return memberExpression.Member.Name; 
    }

А потом

        Console.WriteLine("{0} = {1}", buildString(() => c.Prop), c.Prop);

Это не выглядит так страшно. Да, вы здесь дважды используете c.Prop, но я думаю, вы получите значительное улучшение производительности, потому что вам не нужна компиляция дерева выражений. И вы по-прежнему не используете строковые литералы.

person Alexandra Rusina    schedule 03.12.2009

var listOfPropertyNamesAndValues = this.GetType().GetProperties()
    .Select(prop => string.Format("{0} = '{1}'", 
    prop.Name, prop.GetValue(this,null)));

Если вы хотите получить конкретное свойство, вам нужно будет передать его имя в виде строки и получить его через отражение (просто добавьте предложение where в запрос выше). Хорошая новость в том, что вы больше не нарушаете DRY, плохая новость в том, что это небезопасно.

person Manu    schedule 02.12.2009
comment
typeof (это) eeeeeeeeeeeeeeeeeeeekkkkkkkkkk !! ;п - person leppie; 02.12.2009
comment
@Manu: Спасибо за это решение. Это очень полезно. Я уже включил ваше предложение в вопрос, но я наткнулся на небольшую проблему. - person Theo Lenndorff; 03.12.2009

Отражение?

person flesh    schedule 02.12.2009
comment
Да, это альтернатива, но я думаю, что этот код будет менее простым, чем использование деревьев выражений. Пожалуйста, опубликуйте пример кода, если я ошибаюсь. - person Theo Lenndorff; 02.12.2009
comment
Я бы не стал возражать, я полагаю, это личное предпочтение, предпочитаете ли вы отражение деревьям выражений. - person flesh; 02.12.2009
comment
Аааа ... У Ману есть пример. Да, тогда дело вкуса. ;-) - person Theo Lenndorff; 02.12.2009

Я видел это с помощью делегат и допрос IL, но это не совсем так. Короче нет: в C # нет infoof. Вот мнение Эрика Липперта по этому поводу: In Foof We Trust: диалог

person Marc Gravell    schedule 02.12.2009
comment
Просто немного погуглил, а также просмотрел эту ссылку (трудно понять, думаю, придется прочитать ее дважды ;-)). Откуда взялось это понятие? C, Smalltalk? Должен признаться, я никогда об этом не слышал. - person Theo Lenndorff; 02.12.2009