-1

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
        }
    }
}
user1514042
  • 1,899
  • 7
  • 31
  • 57

2 Answers2

2

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.

Community
  • 1
  • 1
gehho
  • 9,049
  • 3
  • 45
  • 59
1

You seem to have misunderstood the concept of properties in C#.
The properties have a getter and setter function. They will automatically get executed when you set the property or get its value.

So all you need to do is to change the set function of your property to something like this:

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

Then call it:

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

    test.TestProperty = "blah";
    test.TestProperty = "blah2";
}
RononDex
  • 4,143
  • 22
  • 39
  • LOL. You seemed to minsunderstand my question - is that how Stacky works these days? Just in case if it wan't clear enough - I want to be able to pass external Evaluators by some reason. – user1514042 Feb 17 '14 at 13:08
  • @user1514042: reading your question it looks like you think the attribute's function is somehow executed when the property is set... – Paolo Tedesco Feb 17 '14 at 13:09
  • @Paolo Tedesco I know that:) – user1514042 Feb 17 '14 at 13:10
  • @user1514042 maybe you should clarify what you are asking then. I have no clue what you could mean else then what I interpreted from it. If a question can be easily interpreted in different ways, then it's not a good question – RononDex Feb 17 '14 at 13:11
  • @user1514042 and sorry that I wasted my valuable time answering your stupid question(s). That won't happen again for sure! I don't need guys like you who fool around and think everyone who does not understand their question is a fool. You're on my blacklist of SO users now that will never ever get an answer of me again – RononDex Feb 17 '14 at 13:13
  • 2
    @user1514042: no reason for LOLling or replying in a "is that how Stacky works these days?" tone... – Paolo Tedesco Feb 17 '14 at 13:15
  • 3
    @user1514042 Please don't say "LOL you understand nothing" when someone tries to help you. Your question was objectively not clear at the time RononDex answered, so please improve your question instead of adding poor comments like that on answers. – ken2k Feb 17 '14 at 13:15
  • Fire up a funciton kept in the property attribute upon that property change? Sounds better?:) – user1514042 Feb 17 '14 at 13:16
  • This kind of *answers* is a pure chancing, do you seriously believe that one using Funcs and Attributes couldn't hack into the property setter? This is chancing. Don't jump onto the question, if it leaves the room for interpretation. - In the comments - "Could it be as simple as..." - is the appropriate way, not the dumb answer. – user1514042 Feb 17 '14 at 13:21
  • @RononDex I'd suggest converting your answer into a comment as it doesn't go any near the actual matter of the question. – user1514042 Feb 17 '14 at 13:24
  • @user1514042 You should make clear why you want it, as when we read your question we always also think of better ways how to do it. We are no gods and can't read your mind. How are we supposed to know what experience knowledge you have. All we see is a poor formulated question and try to interpret what you could have meant with your question. As I said if you do not make absolutely clear what you are asking it is a **bad** question. And getting raped by the OP when trying to answering it is a total killer and gets everyone raging (understandably) – RononDex Feb 17 '14 at 13:24
  • I don't have to say WHY, I just need to know HOW! Or understand why it's not possible, rather than getting answers from the c# course students. – user1514042 Feb 17 '14 at 13:26
  • @user1514042 Are you having a bad day or something? You provided a poor question, RononDex tried its best to help with the small piece of information that was understandable in your question, and now you're raging because it doesn't answer your problem? You know what, this is the best you can do so **no one** will help. – ken2k Feb 17 '14 at 13:29
  • @user1514042 First of all, I am not a student, and you are getting really offensive and pissing me off here! Just write in your question "I need to use attributes because..." or "I don't want to use the setter function" and everything would be clear to everybody. All you are doing here is "how to achieve a function getting executed when setting a property for the first time". I don't f*cking need to explain myself to someone like you. Go find your answers some where else where people like to get raped for answering and wasting time for you. – RononDex Feb 17 '14 at 13:30
  • @RononDex What did I put up the code for?:) Or it still doesn't shed any light on what I'm trying to achieve, if so I'll vote for it to be closed myself. – user1514042 Feb 17 '14 at 13:30
  • Deleting answer as this is leading nowhere. Unless the OP gets a significant IQ boost of at least factor 2 this makes no sense – RononDex Feb 17 '14 at 13:30