【问题标题】:How can I pass a property as a delegate?如何将属性作为委托传递?
【发布时间】:2010-07-30 13:46:08
【问题描述】:

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

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

假设我有一个加载了数据的数据读取器,并且每个字段的值都需要传递到已检查 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

在第二种情况下,需要将属性作为委托传递才能设置它。

所以问题分为两部分:

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

[由于这只是理论上的,我同样可以接受 VB 或 C# 中的答案]

编辑:这里有一些巧妙的答案。谢谢大家。

【问题讨论】:

    标签: c# .net vb.net c#-3.0 c#-4.0


    【解决方案1】:

    我喜欢使用表达式树来解决这个问题。每当您有一个方法想要获取“属性委托”时,请使用参数类型Expression&lt;Func&lt;T, TPropertyType&gt;&gt;。例如:

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

    这样做的好处是,gets 的语法看起来也一样。

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

    【讨论】:

    • 这太棒了,我刚刚受到@JonSkeet 的回复启发,产生了类似的想法。我想我可以稍微简化一下,但我每天都在尝试在其他 1000 人中思考这个问题。问题......就像你做的那样:P
    • +1 来自我。这不会编译,所以我在修复中进行了编辑。我希望我是对的——如果不是,请原谅我。此外,这提供了一组很棒的扩展方法,尤其是public static PropertyInfo GetPropertyInfo&lt;T, TProperty&gt;(this Expression&lt;Func&lt;T, TProperty&gt;&gt; expression),我可以将其用作另一个库函数的基础之一,以使用泛型获取 Linq to Sql 字符串字段的最大长度。谢谢!
    • @StephenKennedy,谢谢,很好的收获!所以需要一个编译按钮。 ;)
    【解决方案2】:

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

    要解决您最初的问题,即更多关于想要一个很好的 API 将数据读取器中的命名值映射到对象上的属性,请考虑使用 System.ComponentModel.TypeDescriptor - 一个经常被忽视的替代方法,而不是自己进行反射性脏工作。

    这是一个有用的sn-p:

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

    这会创建一个包含对象属性描述符的字典。

    现在我可以这样做了:

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

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

    对此有用的是,可以想象一种属性驱动的方法来遍历该属性集合(PropertyDescriptor 有一个 Attributes 属性,允许您获取添加到属性中的任何自定义属性)找出哪个要使用的数据读取器中的值;或者有一个方法可以接收属性名字典 - 列名映射,它会迭代并为您执行所有这些集合。

    我怀疑这样的方法可能会为您提供所需的 API 快捷方式,而 lambda 表达式反射技巧(在这种情况下)不会。

    【讨论】:

    • 我也喜欢这个含义。很好的答案。
    【解决方案3】:

    忽略这在您的特定情况下是否有用(我认为您采用的方法效果很好),您的问题是“有没有办法将属性转换为委托”。

    嗯,有可能。

    每个属性实际上(在幕后)都由一个或两个方法组成——一个 set 方法和/或一个 get 方法。而且你可以——如果你能掌握这些方法——制作包装它们的委托。

    例如,一旦您在 TObj 类型的对象上获得了一个表示 TProp 类型属性的 System.Reflection.PropertyInfo 对象,我们可以创建一个 Action&lt;TObj,TProp&gt;(即一个对象来设置属性和一个值来设置它),它包装了 setter 方法,如下所示:

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

    或者我们可以创建一个Action&lt;TProp&gt;,将setter包装在TObj的特定实例上,如下所示:

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

    现在我可以为这样的对象的属性获取一个“setter”委托:

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

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

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

    【讨论】:

    • 我非常喜欢这个。谢谢。
    【解决方案4】:

    不,没有什么类似于属性的方法组转换。最好的办法是使用 lambda 表达式来形成 Func&lt;string&gt;(对于 getter)或 Action&lt;string&gt;(对于 setter):

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

    【讨论】:

    • 有趣的是,我没有想到 lambda,当然这比我在这个特定场景中的第一个语句更笨拙。不过这很有趣,我想知道这是否可以更进一步以某种方式包裹它……或者我的大脑正在远离我:P
    • 泛型类型可以传递给委托吗?
    • 也许我应该提出另一个问题:D
    【解决方案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");
    

    【讨论】:

    • 我曾考虑过反射,但这是我一直担心会拖累性能的领域之一。因此,我只在我的应用程序中它最不可能影响的领域进入了一个利基市场。在我要加载数百个对象或将数据解析到数百个字段的区域中,我担心它会严重影响性能。
    • 虽然我给了你 +1,因为它确实回答了这个问题,但它与我正在寻找的内容并不完全一致(我没有在问题中指定)。跨度>
    【解决方案6】:

    正如其他人所指出的,静态反射是要走的路。

    这些类开箱即用:

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

    【讨论】:

      猜你喜欢
      • 2015-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-29
      • 2022-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多