Firing an event / function on a property? (C#)
Asked Answered
B

8

10

I am using a class that I cannot edit, it has a property (a boolean) of which it would be nice to be informed when it changes, I can't edit the properties get or set as I am importing the class from a .dll (which I don't have the code for).

How do I create an event/function that is fired when the property is changed?

Additional
It is only changed within its own class, directly to the underlying private variable.

E.g.

private bool m_MyValue = false;

public bool MyValue
  {
  get { return m_MyValue; }
  }

private void SomeFunction()
  {
  m_MyValue = true;
  }
Brooklyn answered 6/8, 2009 at 10:40 Comment(0)
R
10

You can't, basically... not without using something like the debugger API to inject code at execution time and modifying the IL of the original library (and I'm not recommending either of those solutions; aside from anything else it may violate the licence of the library).

Basically if a property doesn't support notification, it doesn't support notification. You should look for a different way of approaching your problem. (Would polling work, for example?)

Rostand answered 6/8, 2009 at 10:44 Comment(3)
I think i'm going to have to go down the polling route, not something I was hoping for because the data may be reduntant during poll timings (I want to catch the data before it is used by other functionality)Brooklyn
or wrap the class with your own class that does support INotify or your own implementations?Palaeontography
@Andy: It depends. If you do that then you can't use the class as the original type, which other bits of the code may need.Rostand
L
5

You cant do this directly [as Jon Skeet said], unless it's virtual, you're in a position to intercept all instance creations of the class and there are no changes to a backing field that influences the real 'value' of the propget.

The only way to brute force this is to use Mono.Cecil or MS CCI to instrument the prop setter a la this DimeCast on Cecil. (Or PostSharp)

However this wouldn't trap internal changes to the backing field (if there even is one). (Which is why wrapping probably wont work).

UPDATE: Given your update that you're definitely trying to trap the underlying field change, the only way to do that is to use PS / CCI / Cecil and analyse the flow to intercept all field updates. In short, not very feasible.

Lemieux answered 6/8, 2009 at 10:43 Comment(0)
A
3

Arguably, the only real way to do this is to create some kind of "watcher" component, running in a separate thread, whose job is to read the property at intervals and raise an event when the property's value changes. Of course this solution sails in the murky waters of threading and synchronization.

On the assumption that your application is single-threaded in respect to this object, your cleanest solution is to make method calls to this object via a proxy object. It would have the job of checking the before and after state of the property and raising an event in the case it has changed.

Here's a simple example of what I'm talking about:

public class SomeProxy
{
    public SomeProxy(ExternalObject obj)
    {
         _obj = obj;
    }

    public event EventArgs PropertyChanged;

    private bool _lastValue;

    private ExternalObject _obj;

    protected virtual void OnPropertyChanged()
    {
        if(PropertyChanged != null)
            PropertyChanged();
    }

    protected virtual void PreMethodCall()
    {
        _lastValue = _obj.SomeProperty;
    }

    protected virtual void PostMethodCall()
    {
        if(_lastValue != _obj.SomeProperty)
            OnPropertyChanged();
    }

    // Proxy method.
    public SomeMethod(int arg)
    {
        PreMethodCall();
        _obj.SomeMethod(arg); // Call actual method.
        PostMethodCall();
    }
}

Obviously you can build this proxy pattern into a suitable object - you just have to be aware that all calls have to go through the proxy for the event to be raised when you expect it to be.

Anitaanitra answered 6/8, 2009 at 12:0 Comment(1)
+1 for the full example (tho the OP makes it pretty clear that he wants to treat it as a black box, so the answer isnt esp appropriate IMNSHO)Lemieux
C
2

As previously mentioned, the most direct method (and that which requires the least change to code) is to use an AOP library such as PostSharp.

However, a solution can be achieved using traditional C#/.NET by using the dependency property pattern, used throughtout WPF to great effect. I suggest to read up on this, and consider implementing such a system (or at least a simplified version of it) for your project, if appropiate.

Columbic answered 6/8, 2009 at 12:4 Comment(0)
A
1

You will need to create a class that wraps the class in the dll, within the setter property just raise an event there using the normal methods.

Aforesaid answered 6/8, 2009 at 10:44 Comment(1)
But that wont trap internal updates that the component makes, either via its setter, or direct changes to the internal state which the p;ropget would reflect.Lemieux
M
1

Could you inherit from the class and hide the property? Something like this...

class MyClass : BaseClass
{
    // hide base property.
    public new bool MyProperty
    {
        get
        {
            return base.MyProperty;
        }

        set
        {
            base.MyProperty = value;
            RaiseMyPropertyChangedEvent();
        }
    }
}
Maltzman answered 6/8, 2009 at 10:47 Comment(4)
Same as #1238637. (And update to Q means this wont work)Lemieux
@Thomas: No, the 'new' modifier keyword doesn't require the base to be virtual.Ewald
ok, sorry, I didn't see the 'new'... however it will only work if you manipulate the object through a variable of type MyClass. If the variable is of type BaseClass, the base implementation will be called, since the property is not polymorphAiredale
This is property hiding, not overriding :)Ruberta
E
0

I think Alex' idea of a wrapper is good, however, given that the most important thing to you is that you know that the value is changed before use, you could simply move the notification to the getter, circumventing the worries of internal value change. That way you get something similar to polling, yet reliable:

class MyClass : BaseClass
{
    //local value
    private bool local;

    //first access yet?
    private bool accessed = false;

    // Override base property.
    public new bool MyProperty
    {
        get
        {
            if(!accessed)
            {
                // modify first-get case according to your taste, e.g.
                local = base.MyProperty;
                accessed = true;
                RaiseMyPropertyChangedBeforeUseEvent();
            }
            else
            {
                if(local != base.MyProperty)
                {
                    local = base.MyProperty;
                    RaiseMyPropertyChangedBeforeUseEvent();
                }
            }

            return local;
        }

        set
        {
            base.MyProperty = value;
        }
    }
}
Ewald answered 6/8, 2009 at 12:11 Comment(0)
T
-1

You can try to inherit it and use it's child instead of it. Override the "set" of the property so it raises the event.

EDIT: ... only if property is virtual in the parent class ...

Tout answered 6/8, 2009 at 10:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.