Как я могу передать собственность в качестве делегата? - PullRequest
16 голосов
/ 30 июля 2010

Это теоретический вопрос, у меня уже есть решение моей проблемы, которое привело меня по другому пути, но я думаю, что вопрос все еще потенциально интересен.

Могу ли я передать свойства объекта в качестве делегатовточно так же я могу с методами?Например:

Допустим, у меня загружен считыватель данных с данными, и значение каждого поля должно быть передано в свойства различных типов, которые были проверены на наличие DBNull.Если я попытаюсь получить одно поле, я мог бы написать что-то вроде:

if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"];

Но если я скажу 100 полей, это становится громоздким очень быстро.Есть несколько способов, которыми вызов для этого может выглядеть неплохо:

myClass.Property = GetDefaultOrValue<string>(rdr["field1"]); //Which incidentally is the route I took

, который также может хорошо выглядеть как метод расширения:

myClass.Property = rdr["field1"].GetDefaultOrValue<string>();

Или:

SetPropertyFromDbValue<string>(myClass.Property1, rdr["field1"]); //Which is the one that I'm interested in on this theoretical level

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

Итак, вопрос состоит из двух частей:

  1. Этовозможно?
  2. Как бы это выглядело?

[Поскольку это только теоретическое, ответы на VB или C # одинаково приемлемы для меня]

Редактировать: Здесь есть несколько приятных ответов.Спасибо всем.

Ответы [ 6 ]

15 голосов
/ 30 июля 2010

Мне нравится использовать деревья выражений для решения этой проблемы. Всякий раз, когда у вас есть метод, в котором вы хотите взять «делегат свойства», используйте тип параметра Expression<Func<T, TPropertyType>>. Например:

public void SetPropertyFromDbValue<T, TProperty>(
    T obj,
    Expression<Func<T, TProperty>> expression,
    TProperty value
)
{
    MemberExpression member = (MemberExpression)expression.Body;
    PropertyInfo property = (PropertyInfo)member.Member;
    property.SetValue(obj, value, null);
}

Приятно то, что синтаксис также выглядит одинаково для get.

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj,
    Expression<Func<T, TProperty>> expression
)
{
    MemberExpression member = (MemberExpression)expression.Body;
    PropertyInfo property = (PropertyInfo)member.Member;
    return (TProperty)property.GetValue(obj, null);
}

Или, если вам лень:

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj,
    Expression<Func<T, TProperty>> expression
)
{
    return expression.Compile()(obj);
}

Вызов будет выглядеть так:

SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]);
GetPropertyFromDbValue(myClass, o => o.Property1);
11 голосов
/ 30 июля 2010

(добавление второго ответа, потому что он использует совершенно другой подход)

Чтобы решить исходную проблему, которая заключается в желании получить хороший API для сопоставления именованных значений в устройстве чтения данных со свойствами вашего объекта, рассмотримSystem.ComponentModel.TypeDescriptor - часто упускаемая из вида альтернатива выполнению грязной размышления самостоятельно.

Вот полезный фрагмент:

var properties = TypeDescriptor.GetProperties(myObject)
    .Cast<PropertyDescriptor>()
    .ToDictionary(pr => pr.Name);

, который создает словарь дескрипторов свойств вашего объекта.

Теперь я могу сделать это: метод SetValue

properties["Property1"].SetValue(myObject, rdr["item1"]);

PropertyDescriptor (в отличие от эквивалента System.Reflection.PropertyInfo) выполнит для вас преобразование типов - проанализирует строки как целые и т. Д.

Что полезно в этом, так это то, что можно вообразить управляемый атрибутами подход к итерации по этой коллекции свойств (PropertyDescriptor имеет свойство Attributes, позволяющее получить любые пользовательские атрибуты, которые были добавлены к свойству), вычисляякакое значение в хранилище данных использовать;или иметь метод, который получает словарь имен свойств - сопоставления имен столбцов, которые выполняют итерацию и выполняют все эти наборы для вас.

Я подозреваю, что такой подход может дать вам ярлык API, который вам нужен, таким образом, чтобы лямбдаобманчивое выражение - в данном случае - не будет.

8 голосов
/ 30 июля 2010

Игнорируя, полезно ли это в ваших конкретных обстоятельствах (где я думаю, что выбранный вами подход работает отлично), ваш вопрос «есть ли способ преобразовать свойство в делегат».

Ну, может быть, что-то вроде.

Каждое свойство (за кулисами) состоит из одного или двух методов - метода set и / или метода get.И вы можете - если вы можете овладеть этими методами - создать делегаты, которые обертывают их.

Например, как только вы получите объект System.Reflection.PropertyInfo, представляющий свойство типа TProp вобъект типа TObj, мы можем создать Action<TObj,TProp> (то есть делегат, который принимает объект, для которого нужно установить свойство и значение, чтобы установить его), который оборачивает этот метод установки следующим образом:

Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod())

Или мы можем создать Action<TProp>, который оборачивает установщик в конкретный экземпляр TObj следующим образом:

Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod())

Мы можем обернуть эту небольшую часть, используя статическое отражение метод расширения:

public static Action<T> GetPropertySetter<TObject, T>(this TObject instance, Expression<Func<TObject, T>> propAccessExpression)
{
    var memberExpression = propAccessExpression.Body as MemberExpression;
    if (memberExpression == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression");

    var accessedMember = memberExpression.Member as PropertyInfo;
    if (accessedMember == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression");

    var setter = accessedMember.GetSetMethod();

    return (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), instance, setter);
}

и теперь я могу получить делегат 'setter' для свойства объекта, подобного этому:

MyClass myObject = new MyClass();
Action<string> setter = myObject.GetPropertySetter(o => o.Property1);

Это строго типизировано, в зависимости от типа самого свойства, поэтому оно устойчиво к рефакторингу и проверке типов во время компиляции.

Конечно, в вашем случае вы хотите иметь возможностьустановить свойство с помощью возможно нулевого объекта, поэтому строго типОбертка ed вокруг установщика - не единственное решение, но она дает вам кое-что, чтобы перейти к вашему SetPropertyFromDbValue методу.

6 голосов
/ 30 июля 2010

Нет, нет ничего похожего на преобразование групп методов для свойств. Лучшее, что вы можете сделать, это использовать лямбда-выражение для формирования Func<string> (для метода получения) или Action<string> (для установки):

SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
                               rdr["field1"]);
2 голосов
/ 30 июля 2010

Стоит упомянуть, что вы можете сделать это с помощью некоторого обмана отражения ... что-то вроде ...

public static void LoadFromReader<T>(this object source, SqlDataReader reader, string propertyName, string fieldName)
    {
        //Should check for nulls..
        Type t = source.GetType();
        PropertyInfo pi = t.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
        object val = reader[fieldName];
        if (val == DBNull.Value)
        {
            val = default(T);
        }
        //Try to change to same type as property...
        val = Convert.ChangeType(val, pi.PropertyType);
        pi.SetValue(source, val, null);
    }

затем

myClass.LoadFromReader<string>(reader,"Property1","field1");
0 голосов
/ 30 июля 2010

Как отмечали другие, путь статического отражения - это путь.

Эти классы работают из коробки:

http://www.codeproject.com/Articles/36262/Getting-Fun-with-Net-Static-Reflection.aspx

...