我如何作为代表传递财产?
这是一个理论问题,我已经找到了解决问题的方法,这使我走上了不同的道路,但我认为这个问题仍然很有意思。
我可以像对待方法一样将对象属性作为委托传递吗? 例如:
假设我有一个数据读取器加载了数据,并且每个字段的值需要传递到已经检查过DBNull的不同类型的属性中。 如果试图获得单个字段,我可能会写如下:
if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"];
但是,如果我说100个字段,那很快就会变得笨拙。 执行此操作的调用有两种方式可能看起来不错:
myClass.Property = GetDefaultOrValue(rdr["field1"]); //Which incidentally is the route I took
作为扩展方法,它看起来也很不错:
myClass.Property = rdr["field1"].GetDefaultOrValue();
要么:
SetPropertyFromDbValue(myClass.Property1, rdr["field1"]); //Which is the one that I'm interested in on this theoretical level
在第二个实例中,属性需要作为委托传递才能进行设置。
所以问题分为两部分:
- 这可能吗?
- 那会是什么样的?
[因为这只是理论上的,VB或C#中的答案对我来说同样可以接受]
编辑:这里有一些光滑的答案。 谢谢大家。
(添加第二个答案,因为它采用完全不同的方法)
为了解决您的原始问题,更多的是想要一个很好的API来将datareader中的命名值映射到对象的属性,请考虑System.ComponentModel.TypeDescriptor
– 一个经常被忽视的替代方法,自己做反思性的脏工作。
这是一个有用的片段:
var properties = TypeDescriptor.GetProperties(myObject) .Cast() .ToDictionary(pr => pr.Name);
这会创建对象的propertydescriptors字典。
现在我可以这样做:
properties["Property1"].SetValue(myObject, rdr["item1"]);
PropertyDescriptor
的SetValue方法(与System.Reflection.PropertyInfo
的等效方法不同)将为您进行类型转换 – 将字符串解析为int,依此类推。
对此有用的是可以想象一种属性驱动的方法来迭代该属性集合( PropertyDescriptor
有一个Attributes
属性,允许您获取添加到属性中的任何自定义属性)确定要使用的datareader中的哪个值; 或者有一个接收propertyname字典的方法 – columnname mappings迭代并为你执行所有这些集合。
我怀疑像这样的方法可能会以lambda-expressionreflection技巧 – 在这种情况下 – 不会这样的方式为您提供所需的API快捷方式。
我喜欢用表达式树来解决这个问题。 每当您有一个想要获取“属性委托”的方法时,请使用参数类型Expression
。 例如:
public void SetPropertyFromDbValue( T obj, Expression> expression, TProperty value ) { MemberExpression member = (MemberExpression)expression.Body; PropertyInfo property = (PropertyInfo)member.Member; property.SetValue(obj, value, null); }
关于这一点的好处是,获取的语法看起来也是一样的。
public TProperty GetPropertyFromDbValue( T obj, Expression> expression ) { MemberExpression member = (MemberExpression)expression.Body; PropertyInfo property = (PropertyInfo)member.Member; return (TProperty)property.GetValue(obj, null); }
或者,如果你感到懒惰:
public TProperty GetPropertyFromDbValue( T obj, Expression> expression ) { return expression.Compile()(obj); }
调用看起来像:
SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]); GetPropertyFromDbValue(myClass, o => o.Property1);
忽略这在您的具体情况下是否有用(我认为您采用的方法效果很好),您的问题是“有没有办法将属性转换为委托”。
嗯,有种可能。
实际上(幕后)每个属性都包含一个或两个方法 – set方法和/或get方法。 你可以 – 如果你能掌握这些方法 – 让代表们包装它们。
例如,一旦你掌握了一个System.Reflection.PropertyInfo
对象,表示类型为TProp
的类型TObj
,我们就可以创建一个Action
(即一个带有一个对象的委托)在哪个设置属性和一个值来设置它)包装该setter方法如下:
Delegate.CreateDelegate(typeof (Action), propertyInfo.GetSetMethod())
或者我们可以创建一个Action
,它将setter包装在特定的TObj
实例上,如下所示:
Delegate.CreateDelegate(typeof (Action), instance, propertyInfo.GetSetMethod())
我们可以使用静态reflection扩展方法来包装那么多:
public static Action GetPropertySetter(this TObject instance, Expression> 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 ) Delegate.CreateDelegate(typeof(Action ), instance, setter); }
现在我可以在这样的对象上获取一个属性的’setter’委托:
MyClass myObject = new MyClass(); Action setter = myObject.GetPropertySetter(o => o.Property1);
这是强类型的,基于属性本身的类型,所以它在重构和编译时类型检查时是健壮的。
当然,在您的情况下,您希望能够使用可能为null的对象设置属性,因此围绕setter的强类型包装器不是整个解决方案 – 但它确实可以为您提供传递给SetPropertyFromDbValue
方法的内容。
不,没有什么类似于属性的方法组转换。 您可以做的最好的事情是使用lambda表达式来形成Func
(对于getter)或Action
(对于setter):
SetPropertyFromDbValue(value => myClass.Property1 = value, rdr["field1"]);
值得一提的是你可以用一些反思技巧做到这一点……就像…
public static void LoadFromReader(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(reader,"Property1","field1");
正如其他人所指出的那样,静态reflection是最佳选择。
这些课程开箱即用:
http://www.codeproject.com/Articles/36262/Getting-Fun-with-Net-Static-Reflection.aspx