Как передать свойство в качестве делегата?
Это теоретический вопрос, у меня уже есть решение моей проблемы, которая поменяла меня по другому пути, но я думаю, что вопрос по-прежнему потенциально интересен.
Можно ли передавать свойства объекта в виде делегатов так же, как я могу с помощью методов? Например:
Скажем, у меня есть устройство чтения данных, загруженное данными, и каждое значение поля необходимо передать в свойства разных типов, проверенных для 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");
Ответ 6
Как указывали другие, статическая рефлексия - это путь.
Эти классы работают из коробки:
http://www.codeproject.com/Articles/36262/Getting-Fun-with-Net-Static-Reflection.aspx