代码之家  ›  专栏  ›  技术社区  ›  BenAlabaster

如何作为委托传递属性?

  •  16
  • BenAlabaster  · 技术社区  · 14 年前

    这是一个理论上的问题,我已经找到了一个解决问题的方法,使我走上了一条不同的道路,但我认为这个问题仍然是潜在的有趣的。

    我可以用与方法相同的方式将对象属性作为委托传递吗?例如:

    假设我有一个加载了数据的数据读取器,需要将每个字段的值传递到检查了DBNull的不同类型的属性中。如果尝试获取单个字段,我可能会编写如下内容:

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

    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 回复  |  直到 14 年前
        1
  •  11
  •   James Hart    14 年前

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

    要解决最初的问题,更重要的是需要一个好的API将datareader中的命名值映射到对象上的属性,请考虑 System.ComponentModel.TypeDescriptor

    下面是一个有用的片段:

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

    创建对象的PropertyDescriptor的字典。

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

    PropertyDescriptor System.Reflection.PropertyInfo 将为您进行类型转换—将字符串解析为int,依此类推。

    属性描述器 有一个 Attributes 属性,使您能够获取添加到属性的任何自定义属性)计算出要在datareader中使用的值;或者使用一个方法来接收propertyname-columnname映射的字典,它可以迭代并为您执行所有这些集。

        2
  •  16
  •   Stephen Kennedy annamataws    9 年前

    我喜欢用表达式树来解决这个问题。当您有一个方法要接受“属性委托”时,请使用参数类型 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
  •  8
  •   James Hart    14 年前

    忽略这在您的特定环境中是否有用(我认为您所采用的方法工作得很好),您的问题是“有没有办法将属性转换为委托”。

    例如,一旦你掌握了 System.Reflection.PropertyInfo 表示类型属性的对象 TProp 在类型的对象上 TObj ,我们可以创建一个 Action<TObj,TProp>

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

    或者我们可以创建一个 Action<TProp> 将setter包装到 托比 这样地:

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

    我们可以用一个 static reflection 扩展方法:

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

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

    它是强类型的,基于属性本身的类型,因此在重构和编译时类型检查时非常健壮。

    当然,在您的例子中,您希望能够使用一个可能为null的对象来设置您的属性,因此围绕setter的强类型包装器并不是完整的解决方案—但它确实提供了一些可以传递给您的 SetPropertyFromDbValue 方法。

        4
  •  6
  •   Jon Skeet    14 年前

    Func<string> Action<string> (对于设置者):

    SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
                                   rdr["field1"]);
    
        5
  •  2
  •   Richard Friend    14 年前

    值得一提的是,你可以通过一些反射技巧来做到这一点。。 有点像。。。

    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
  •  0
  •   Alex Rouillard    14 年前

    正如其他人所指出的,静态反射是一种方法。

    这些课程是开箱即用的:

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