Как передать свойство в качестве делегата?

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

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

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

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

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

  • Возможно ли это?
  • Как это будет выглядеть?

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

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

Ответы

Ответ 1

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

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

Здесь полезный фрагмент:

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

Это создает словарь идентификаторов свойств вашего объекта.

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

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

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

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

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

Ответ 2

Мне нравится использовать деревья выражений для решения этой проблемы. Всякий раз, когда у вас есть метод, в котором вы хотите принять "делегат свойства", используйте тип параметра 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);
}

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

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

Ответ 3

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

SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
                               rdr["field1"]);

Ответ 4

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

Ну, может быть.

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

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

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

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

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

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

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

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

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

Ответ 5

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

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