属性更改时保留在属性中的执行函数

时间:2014-02-17 12:50:57

标签: c#

我有一个具有属性的属性,而该属性又有Func<object, object>,我希望在属性更改时执行该函数(使用更新的属性的值为in T)。最简单的方法是什么?

注意:我知道属性 static 的事实并不是为了在他们的受让人更改/调用时执行。我只需要让它尽可能接近我创建的原型。

一些代码:

using System;
using System.Windows;

namespace AnnotatedBinding
{
    public class AnnotatedPropertyAttribute: Attribute
    {
        // static 
        public AnnotatedPropertyAttribute(Func<object, object> evaluator)
        {
            Evaluator = evaluator;
        }

        public Func<object, object> Evaluator
        {
            get; private set;
        }
    }

    public class Test
    {
        [AnnotatedProperty(Test.TestEvaluator)] // not compiling!, guess it's fixable by passing in a member info and then calling Reflection Invoke?
        public string TestProperty
        {
            get; set;
        }

        public static Func<object, object> TestEvaluator = (x) => MessageBox.Show(x.ToString());
    }

    public class Shell
    {
        public void Run()
        {
            var test = new Test();

            test.TestProperty = "blah";// I want my message box here

            test.TestProperty = "blah";// and I don't want it here
        }
    }
}

2 个答案:

答案 0 :(得分:2)

TestProperty上的属性无法编译,因为不允许委托作为属性参数。有关允许哪些类型的详细信息,请参阅this answer from Eric Lippert

关于使用反射的变通方法:您当然可以指定拥有该方法的类型,以及属性中的方法名称,因为System.Typestring是有效的属性参数类型。像这样:

[AnnotatedProperty(typeof(Test), "TestEvaluator")]
public string TestProperty { get; set; }

但是,在设置属性时,仍然无法对委托做任何事情。属性只是您可以在运行时使用反射(更具体地使用MemberInfo.GetCustomAttributes(...))读取的元数据,分析它们并基于属性值执行任何操作。这一切都需要手动完成。遗憾的是,.NET框架不提供基于应用于成员的属性自动执行某些操作的功能。这将使财产变更通知的生活变得更加容易。

因此您必须手动实现属性的处理。这意味着,实现getset访问器,检查属性是否应用于该属性,确定应该执行的委托,并使用反射来执行它。当然,这没有意义,因为你宁愿在setter中添加对方法的调用。

<强> TL; DR:

可能的解决方案:您应该查看PostSharp,这是一个支持.NET中面向方面编程的库。它可用于在编译后将样板代码注入方法或其他成员。它通过分析您的MSIL代码并搜索所谓的&#34;方面来实现这一点。 (实际上属于你的属性)。如果找到,它将修改属性指定的MSIL。您必须从PostSharp基本属性/方面派生属性,然后覆盖适当的方法。在您的情况下,您必须从LocationInterceptionAspect派生,然后覆盖OnSetValue(...)方法。在此方法中,您将使用属性参数(如上所述)确定委托,然后使用反射调用它。 "Intercepting Properties and Fields" in the PostSharp documentation非常好地介绍了如何做到这一点。

我认为你最终会得到这样的结果:

public class ExecuteDelegateOnPropertySetAspect : LocationInterceptionAspect
{
    public ExecuteDelegateOnPropertySetAspect(Type methodOwner, string methodName, object[] arguments)
    {
        this.MethodOwner = methodOwner;
        this.MethodName = methodName;
        this.Arguments = arguments;
    }

    public Type MethodOwner { get; set; }
    public string MethodName { get; set; }
    public object[] Arguments { get; set; }

    public override void OnSetValue(LocationInterceptionArgs args)
    {
        // get method with the specified name from the specified owner type
        MethodInfo method = this.MethodOwner.GetMethod(this.MethodName);

        // method must be static, otherwise we would need an instance to call it
        if (method != null && method.IsStatic)
        {
            if (method.GetParameters().Length == this.Arguments.Length)
            {
                // call the method with the given arguments
                method.Invoke(null, this.Arguments);
            }
        }

        // execute the original setter code
        args.ProceedSetValue();
    }
}

在您的代码中,您可以将此方面应用于您的属性:

public class Test
{
    public static void TestMethod(string someMessage)
    {
        MessageBox.Show(someMessage);
    }

    [ExecuteDelegateOnPropertySetAspect(typeof(Test), "TestMethod", new object[] { "Hello world!" })]
    public string TestProperty { get; set; }
}

请注意,我省略了大部分错误和空值检查以保持简单和简短。

答案 1 :(得分:0)

您似乎误解了C#中的属性概念 属性具有getter和setter函数。当您设置属性或获取其值时,它们将自动执行。

所以您需要做的就是将属性的set函数更改为:

public class Test
{
    private string _testProperty;
    private bool testPropertyIsSet = false;

    public string TestProperty
    {
        get { return this._testProperty; }
        set 
        {  
            _testProperty = value;
            if (!testPropertyIsSet)
            {
                 // Do something here when your property gets set for the first time
            }
            testPropertyIsSet  = true;
        }
    }
}

然后叫它:

public void Run()
{
    var test = new Test();

    test.TestProperty = "blah";
    test.TestProperty = "blah2";
}