我如何作为代表传递财产?

Ben*_*ter 17 .net c# vb.net c#-3.0 c#-4.0

这是一个理论问题,我已经找到了解决问题的方法,这使我走上了不同的道路,但我认为这个问题仍然很有意思.

我可以像对待方法一样将对象属性作为委托传递吗?例如:

假设我有一个数据读取器加载了数据,并且每个字段的值都需要传递到已经检查过DBNull的不同类型的属性中.如果试图获得单个字段,我可能会写如下:

if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"];
Run Code Online (Sandbox Code Playgroud)

但是,如果我说100个字段,那很快就会变得笨拙.执行此操作的调用有两种方式可能看起来不错:

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

作为扩展方法,它看起来也不错:

myClass.Property = rdr["field1"].GetDefaultOrValue<string>();
Run Code Online (Sandbox Code Playgroud)

要么:

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

在第二个实例中,属性需要作为委托传递才能进行设置.

所以问题分为两部分:

  1. 这可能吗?
  2. 那会是什么样的?

[因为这只是理论上的,VB或C#中的答案对我来说同样可以接受]

编辑:这里有一些光滑的答案.谢谢大家.

Kir*_*oll 16

我喜欢用表达式树来解决这个问题.只要您有一个想要获取"属性委托"的方法,请使用参数类型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);
}
Run Code Online (Sandbox Code Playgroud)

关于这一点的好处是,获取的语法看起来也是一样的.

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);
}
Run Code Online (Sandbox Code Playgroud)

或者,如果你感到懒惰:

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj,
    Expression<Func<T, TProperty>> expression
)
{
    return expression.Compile()(obj);
}
Run Code Online (Sandbox Code Playgroud)

调用看起来像:

SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]);
GetPropertyFromDbValue(myClass, o => o.Property1);
Run Code Online (Sandbox Code Playgroud)


Jam*_*art 11

(添加第二个答案,因为它采用完全不同的方法)

为了解决原来的问题,这更多的是你的对象在一个DataReader想名为值映射一个很好的API特性,考虑System.ComponentModel.TypeDescriptor-一个经常被忽视的替代自己做反射dirtywork.

这是一个有用的片段:

var properties = TypeDescriptor.GetProperties(myObject)
    .Cast<PropertyDescriptor>()
    .ToDictionary(pr => pr.Name);
Run Code Online (Sandbox Code Playgroud)

这会创建一个对象的propertydescriptors字典.

现在我可以这样做:

properties["Property1"].SetValue(myObject, rdr["item1"]);
Run Code Online (Sandbox Code Playgroud)

PropertyDescriptor的SetValue方法(与System.Reflection.PropertyInfo等效的方法不同)将为您进行类型转换 - 将字符串解析为int,依此类推.

什么是关于这个有用的是可以想见的属性驱动的方法来迭代通过属性集合(PropertyDescriptor有一个Attributes属性,让你得到被添加到财产的任何自定义属性)找出使用这在DataReader的价值; 或者有一个接收propertyname字典的方法 - columnname mappings迭代并为你执行所有这些集合.

我怀疑像这样的方法可能会以一种lambda-expression反射技巧 - 在这种情况下 - 不需要的方式为您提供所需的API快捷方式.


Jam*_*art 8

忽略这是否在您的特定情况下有用(我认为您采用的方法可以正常工作),您的问题是"有没有办法将属性转换为委托".

好吧,有种可能.

实际上(幕后)每个属性都包含一个或两个方法 - set方法和/或get方法.你可以 - 如果你能掌握这些方法 - 让代表们包装它们.

例如,一旦你掌握了一个System.Reflection.PropertyInfo表示类型TProp对象的类型属性的对象TObj,我们就可以创建一个Action<TObj,TProp>(即一个委托,它接受一个设置属性的对象和一个值来设置它) )包装该setter方法如下:

Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod())
Run Code Online (Sandbox Code Playgroud)

或者我们可以Action<TProp>在这样的特定实例上创建一个包装setter TObj:

Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod())
Run Code Online (Sandbox Code Playgroud)

我们可以使用静态反射扩展方法来包装那么多:

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);
}
Run Code Online (Sandbox Code Playgroud)

现在我可以在这样的对象上获得一个'setter'委托给一个属性:

MyClass myObject = new MyClass();
Action<string> setter = myObject.GetPropertySetter(o => o.Property1);
Run Code Online (Sandbox Code Playgroud)

这是强类型的,基于属性本身的类型,所以它在重构和编译时类型检查时是健壮的.

当然,在您的情况下,您希望能够使用可能为null的对象设置属性,因此围绕setter的强类型包装器不是整个解决方案 - 但它确实为您提供了传递给您的SetPropertyFromDbValue方法的东西.


Jon*_*eet 6

不,没有什么类似于属性的方法组转换.您可以做的最好的事情是使用lambda表达式来形成Func<string>(对于getter)或Action<string>(对于setter):

SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
                               rdr["field1"]);
Run Code Online (Sandbox Code Playgroud)