Is there a way to call a method when any property

2019-03-16 09:47发布

问题:

So what I'm trying to do is call a single propertyWasSet() function when any property within a C# class is set (conversely, propertyWasGot() when it is get). I would also like to know which property's 'get' was invoked.

I would like to maintain a dictonary of properties that are 'set', and check upon the 'get' action if they have been set yet (and throw an error if it hasn't been).

I've be looking through msdn documentation for reflection, delegates, etc..., but I'm not entirely sure this is possible.

Is there a way to do this? or fire an event upon calling one of these functions that can be intercepted in a base class or something?

回答1:

I wrote an interceptor the other week for Set which can easily be extended for Get, it uses RealProxy, which means your base class needs to derive off MarshalByRefObject.

Another fancy option is to have your class abstract, and use Reflection Emit to construct a concrete class that wraps up all the properties.

Also you could look at code generators to get around this or PostSharp...

Performance for this solution is not stellar, but it should be plenty fast for most UI binding. It could be improved by generating LCG methods for proxy invocation.

public interface IInterceptorNotifiable {
    void OnPropertyChanged(string propertyName);
}

/// <summary>
/// A simple RealProxy based property interceptor
/// Will call OnPropertyChanged whenever and property on the child object is changed
/// </summary>
public class Interceptor<T> where T : MarshalByRefObject, IInterceptorNotifiable, new() {

    class InterceptorProxy : RealProxy {
        T proxy; 
        T target;
        EventHandler<PropertyChangedEventArgs> OnPropertyChanged;

        public InterceptorProxy(T target)
            : base(typeof(T)) {
            this.target = target;
        }

        public override object GetTransparentProxy() {
            proxy = (T)base.GetTransparentProxy();
            return proxy;
        }


        public override IMessage Invoke(IMessage msg) {

            IMethodCallMessage call = msg as IMethodCallMessage;
            if (call != null) {

                var result = InvokeMethod(call);
                if (call.MethodName.StartsWith("set_")) {
                    string propName = call.MethodName.Substring(4);
                    target.OnPropertyChanged(propName);
                } 
                return result;
            } else {
                throw new NotSupportedException();
            } 
        }

        IMethodReturnMessage InvokeMethod(IMethodCallMessage callMsg) {
            return RemotingServices.ExecuteMessage(target, callMsg);
        }

    }

    public static T Create() {
        var interceptor = new InterceptorProxy(new T());
        return (T)interceptor.GetTransparentProxy();
    }


    private Interceptor() {
    }

}

Usage:

  class Foo : MarshalByRefObject, IInterceptorNotifiable {
        public int PublicProp { get; set; }
        public string lastPropertyChanged;

        public void OnPropertyChanged(string propertyName) {
            lastPropertyChanged = propertyName;
        }

    }


    [Test]
    public void TestPropertyInterception() {

        var foo = Interceptor<Foo>.Create();
        foo.PublicProp = 100;

        Assert.AreEqual("PublicProp", foo.lastPropertyChanged);

    }
}


回答2:

You may want to look into PostSharp for this sort of task. It is designed to run on top of C# (or any .NET language for that matter) and has the benefit of not cluttering up your code with reflection in addition. In fact, I don't believe you could find a solution that purely uses C#/Reflection without manually adding code to each of your properties, so I would definitely recommend PostSharp as the way to go.



回答3:

I think what you need is very similar to WPF dependency property system. You might want to look at its implementation. Anyhow, you can add the intercepting code to getter and setter of each property too.



回答4:

There's nothing like this if you don't create it yourself.



回答5:

The SET part of your request is very similar to WPF dependency property system. But the GET part is such unusual that is missing even in the WPF dependency property system!