2

如何在不使用反射的情况下创建通用属性访问器?

我想创建一个通用属性访问器类,以便能够将属性对象传递给它,然后访问该属性值;获取和设置。

我不喜欢仅将反射与 Func 和 Action 以及 lambda 表达式一起使用。

下面代码的问题是它会复制属性值并将其传递过来,因此它实际上不会发送属性引用,而我希望能够使用获取和设置 Product 对象的 Number 属性GenericPropertyAccessor 类。

   static void Main(string[] args)
    {
        var product = new Product {Number = 1};

        var productNumberAccesstor = new GenericPropertyAccessor<int>(product.Number);

        productNumberAccesstor.Value = 23;

        Console.WriteLine(productNumberAccesstor.Value);

        Console.ReadLine();
    }
}

public class Product
{
    public int Number { get; set; }
    public string Name { get; set; }
}

public class GenericPropertyAccessor<T>
{
    private T _property;

    public GenericPropertyAccessor(T propertyExpression)
    {
        _property = propertyExpression;
    }

    public T Value
    {
        get
        {
            return Get(() => _property);
        }
        set
        {
            Set(valueInput => _property = valueInput , value);
        }
    }

    static T Get(Func<T> getFunc)
    {
        return getFunc();
    }

    static void Set(Action<T> setAction, T value)
    {
        setAction(value);
    }

}

基本上,我应该将什么传递给 GenericPropertyAccessor 的构造函数来启用它?

我知道一种解决方案如下,但是否有可能进一步简化呢?特别是在 GenericPropertyAccessor 的构造函数上。是否可以将 lambda 表达式移动到类中并且只传递一种属性引用。

 class Program
    {
        static void Main(string[] args)
        {
            var product = new Product {Number = 1};

            var productNumberAccesstor = new GenericPropertyAccessor<int>(() => product.Number, value => product.Number = value);

            productNumberAccesstor.Value = 23;

            Console.WriteLine(productNumberAccesstor.Value);

            Console.ReadLine();
        }
    }

    public class Product
    {
        public int Number { get; set; }
    }

    public class GenericPropertyAccessor<T>
    {
        private readonly Func<T> _getFunc;
        private readonly Action<T> _setAction;

        public GenericPropertyAccessor(Func<T> getFunc, Action<T> setAction)
        {
            _getFunc = getFunc;
            _setAction = setAction;
        }

        public T Value
        {
            get
            {
                return _getFunc();
            }
            set
            {
                _setAction(value);
            }
        }
    }

我的意思是如何在类中传递“product.Number”表达式?

4

2 回答 2

2

以下是实现此目的的两种方法:一种使用 LINQ Expression,另一种使用普通 Lambda 函数。表达式比 Lambda 更易于调用,但从使用角度来看,两者都可以进一步简化。

我们可以创建一个工厂类来稍微简化调用:

// the Factory method:
public static class Factory
{
    // Wrapper for Expression-based accessor:
    public static Accessor<T> Accessor<T>(Expression<Func<T>> e)
    {
        return new Accessor<T>(e);
    }

    // Wrapper for Lambda-based accessor:
    public static Accessor<T> Accessor<T>(Func<T> _get, Action<T> _set)
    {
        return new Accessor<T>(_get, _set)
    }
}

用表达式

如果您对使用反射的限制没有扩展到 LINQ 表达式,您可以通过仅传递一个() => object.Property样式 getter 表达式并从中生成 setter 来进一步简化调用,如下所示:

public class Accessor<TProp> : IAccessor<TProp>
{
    private readonly Func<TProp> _getter;
    private readonly Action<TProp> _setter;

    internal Accessor(Expression<Func<TProp>> e)
    {
        if (!(e.Body is System.Linq.Expressions.MemberExpression))
            throw new ArgumentException("Invalid expression type supplied.", "e");
        MemberExpression memacc = (MemberExpression)e.Body;
        if (memacc.Type != typeof(TProp))
            throw new ArgumentException("Invalid expression return type.", "e");

        var parmValue = Expression.Parameter(typeof(TProp), "value");
        var assign = Expression.Assign(memacc, parmValue);

        _getter = e.Compile();
        _setter = Expression.Lambda<Action<TProp>>(assign, new[] { parmValue }).Compile();
    }

    public TProperty Value
    {
        get { return _getter(_instance); }
        set { _setter(_instance, value); }
    }
}

// usage:
var obj = new TextBox();
var acc = Factory.Accessor(() => obj.Enabled);

Expression编译器支持使这成为可能。而不是一直编译 lambda 以编写代码,而是将其编译为表达式树并传递。这种表达式的形式让我们可以非常简单地轻松创建我们需要的 get 和 set 方法。

没有表达式

如果不使用表达式,您几乎只能手动提供 get 和 set 方法。将以下构造函数添加(或替换)到 Accessor 类:

    internal Accessor(Func<TProp> getter, Action<TProp> setter)
    {
        _getter = getter;
        _setter = setter;
    }

用法

要生成访问器,只需调用适当的工厂函数:

// Object to work on
var obj = new TextBox();

// accessor using Expressions
var acc1 = Factory.Accessor(() => obj.Enabled);

// accessor using Lambdas
var acc2 = Factory.Accessor() => obj.Enabled, v => obj.Enabled = v);

我个人更喜欢基于表达式的版本。

于 2015-04-26T10:19:01.103 回答
2

您需要传入您当前在内部创建的委托:

public class GenericPropertyAccessor<T>
{
    private Func<T> _getter;
    private Action<T> _setter;

    public GenericPropertyAccessor(Func<T> getter, Action<T> setter)
    {
        _getter = getter;
        _setter = setter;
    }

    public T Value
    {
        get
        {
            return _getter();
        }
        set
        {
            _setter(value);
        }
    }
}

用法:

new GenericPropertyAccessor<int>(() => product.Number, v => product.Number = v);
于 2013-09-16T10:25:50.977 回答