【问题标题】:Can properties be accessed as types?属性可以作为类型访问吗?
【发布时间】:2013-09-18 16:39:22
【问题描述】:

我可以将对象属性作为类型访问吗?

我正在使用一个 API,我必须在其中迭代对象集合,并访问其中两个对象的 Text 属性,用于读取或写入。我目前有两种读写方法:

Result ReadTexts()
    var attribs = SOME_CODE;
    string first = "", second = "";

    for(int i=1 ; i <= attribs.Count ; i++) {
        if(attribs[i] IS_FIRST_ONE_NEEDED) {
            first = attribs[i].Text;
        } else if(attribs[i] IS_SECOND_ONE_NEEDED) {
            second = attribs[i].Text;
        }
    }
    return new Result(first, second);
}

void WriteTexts(string first, string second) {
    var attribs = SOME_CODE;

    for(int i=1 ; i <= attribs.Count ; i++) {
        if(attribs[i] IS_FIRST_ONE_NEEDED) {
            attribs[i].Text = first;
        } else if(attribs[i] IS_SECOND_ONE_NEEDED) {
            attribs[i].Text = second;
        }
    }
}

我更喜欢使用更实用的样式,将迭代和检查集合中的两个对象合并到一个方法中,而不是重复此代码,因为实际上 SOME_CODE 以及 IS_FIRST_ONE_NEEDED 和 IS_SECOND_ONE_NEEDED 在实际情况比上面的示例代码。这种方法看起来像:

void AccessTexts(Action<StringProperty> first, Action<StringProperty> second) {
    var attribs = SOME_CODE;

    for(int i=1 ; i <= attribs.Count ; i++) {
        if(attribs[i] IS_FIRST_ONE_NEEDED) {
            first(attribs[i].Text);
        } else if(attribs[i] IS_SECOND_ONE_NEEDED) {
            second(attribs[i].Text);
        }
    }
}

然后用像这样的 lambda 表达式调用它

AccessTexts(( prop => prop = "abc"), ( prop => prop = "def"));

写作,或

AccessTexts(( prop => firstString = prop), ( prop => secondString = prop));

用于阅读。这会更短,并且避免重复大量代码。

但我认为这是不可能的,因为属性在 .net 中并未作为真实类型公开,而只是基于特殊方法的可用性 - getter 和 setter。因此,没有类型StringProperty,因为我在“我想写什么”的代码示例中将其用作委托参数的类型。

我是对的,还是有一些方法可以按照我想要的方式实现它?

【问题讨论】:

    标签: c# .net properties lambda functional-programming


    【解决方案1】:

    您可以创建自己的代表属性的类。正如您所展示的,属性本质上只是一个 get 和 set 方法,所以这就是我们的类需要表示的全部内容。

    至于如何创建这样的东西,一种选择是让类型直接接受 getter 和 setter 作为委托。另一种选择是让它接受PropertyInfo 和一个对象,然后可以使用反射来实现getter 和setter 方法。最后,如果您愿意,您甚至可以使用 Expression 来表示属性访问,然后从中提取 PropertyInfo。

    首先,实际的包装器本身:

    public class PropertyWrapper<T>
    {
        private Func<T> getter;
        private Action<T> setter;
    
        public PropertyWrapper(PropertyInfo property, object instance)
        {
            if (!typeof(T).IsAssignableFrom(property.PropertyType))
                throw new ArgumentException("Property type doesn't match type supplied");
    
            setter = value => property.SetValue(instance, value);
            getter = () => (T)property.GetValue(instance);
        }
    
        public PropertyWrapper(Func<T> getter, Action<T> setter)
        {
            this.setter = setter;
            this.getter = getter;
        }
    
        public T Get()
        {
            return getter();
        }
    
        public void Set(T value)
        {
            setter(value);
        }
    
        public T Value
        {
            get { return getter(); }
            set { setter(value); }
        }
    }
    

    然后您可以使用这样的帮助器(它是从另一个类中提取出来的,以便进行泛型类型推断:

    public class PropertyWrapper
    {
        public static PropertyWrapper<TProp> Create<TObject, TProp>(
            TObject instance, Expression<Func<TObject, TProp>> expression)
        {
            var memberEx = expression.Body as MemberExpression;
            var prop = memberEx.Member as PropertyInfo;
    
            return new PropertyWrapper<TProp>(prop, instance);
        }
    }
    

    以下是使用两种不同语法构造此类对象的简单示例:

    var list = new List<int>();
    
    var prop1 = new PropertyWrapper<int>(
        () => list.Capacity, cap => list.Capacity = cap);
    
    var prop2 = PropertyWrapper.Create(list, l => l.Capacity);
    
    prop2.Value = 42;
    Console.WriteLine(list.Capacity); //prints 42
    

    【讨论】:

    • 所以 .net 中没有“属性”类型,但您的解决方案使用反射和 .net 中可用的 PropertyInfo 反射类型创建了一个 .net 类型。如果使用 pre-.net 4.5,SetValue()GetValue 需要额外的 null 最后一个参数。
    • @FrankPl 该解决方案不需要需要反射,它只是有两个可选的创建方法,它们使用反射来获得更简洁的创建语法。如果您想明确地写出 getter/setter,这是一个非反射解决方案。
    【解决方案2】:

    你所做的绝对是可行的。您正在为您的函数AccessTexts 提供相关属性的访问器,以便该函数不关心访问是如何完成的。

    通常,这将使用由被迭代的对象实现的接口或由包装类实现的接口来解决。

    您也可以使用反射或dynamic 进行访问。

    无论如何,您都需要AccessTexts 和真实对象之间的代理。

    【讨论】:

    • 你的意思是我会定义一个类,比如Proxy,它有一个构造函数来封装一个集合对象,还有一个属性,比如Text,它将getter和setter都委托给原始对象。那么我会使用该类型作为操作的参数类型吗?然后将集合对象本身用作操作/委托的参数会更容易。这不会解决我只公开属性的要求,但会解决避免代码重复的主要问题。
    猜你喜欢
    • 2012-11-19
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 2012-01-03
    • 1970-01-01
    相关资源
    最近更新 更多