Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
189 views
in Technique[技术] by (71.8m points)

c# - Execute function kept in the attribute upon property change

I have a property with has an attribute, which in turn has Func<object, object>, I want that function to be executed (using the updated property's value as in T) upon the property change. What's the slickest way of doing so?

Note: I'm aware of the facts that Attributes are static and aren't designed to be executed upon their assignees change/invocaction. I just need to get it working as close as can to the prototype I've created.

Some code:

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
        }
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your attribute on the TestProperty does not compile because delegates are not allowed as attribute arguments. See this answer from Eric Lippert for details about which types are allowed.

Regarding a workaround using reflection: You could certainly specify the type owning the method, and the name of the method in the attribute since System.Type and string are valid attribute argument types. Something like this:

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

However, this still won't do anything with the delegate when the property is set. Attributes are only metadata that you can read out during runtime using reflection (more specifically using MemberInfo.GetCustomAttributes(...)), analyse them and perform any operation based on the attribute values. This all needs to be done manually. Unfortunately, the .NET framework does not offer the functionality to automatically perform some operation based on the attributes that are applied to a member. This would make life a lot easier for property change notifications as well.

So you would have to implement the handling of the attributes manually. That means, implementing the get and set accessors, checking whether the attribute is applied to that property, determine the delegate that should be executed, and exeute it using reflection. Of course, that does not make sense because you would rather add a call to the method in the setter instead.

tl;dr:

Possible solution: You should have a look at PostSharp, a library supporting aspect-oriented programming in .NET. It can be used to inject boiler-plate code into methods or other members after compilation. It does this by analyzing your MSIL code and searching for so-called "aspects" (which are actually attributes, like yours). If found, it modifies the MSIL as specified by the attribute. You would have to derive your attribute from a PostSharp base attribute/aspect and then override the appropriate methods. In your case, you would have to derive from the LocationInterceptionAspect and then override the OnSetValue(...) method. In this method you would determine the delegate using the attribute arguments (as given above) and then call this using reflection. "Intercepting Properties and Fields" in the PostSharp documentation gives a very good introduction how to do this.

I think you would end up with something like this:

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

And in your code you would apply this aspect to your properties:

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

Note that I omitted most of the error and null checking to keep it simple and short.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...