|
Short update on just one point: It does work as a weak event the way he posted it (without my modified WeakSubscriber class) if you comment out that if-check like you did. But then you would also be able to do this:
private class WeakSubscriber
{
public void Subscribe(ObservableCollection<object> collection)
{
collection.ObserveCollectionChanged().SubscribeWeakly(this, HandleEvent);
}
private void HandleEvent(WeakSubscriber target, EventPattern<NotifyCollectionChangedEventArgs> item)
{
Console.WriteLine("Event received by Weak subscription");
}
} And here the onNext-delegate carries a reference to the WeakSubscriber-object so that it will not be GC'ed when you release your "own" reference to it. That's what he wanted to prohibit with that if-check. But that if-check doesn't work in conjunction with an anonymous delegate ((target, item) => target.HandleEvent(item) ) (which is why I replaced it with that intermediate static HandleEvent-method) because an anonymous delegate gets converted into an anonymous class by the compiler of which a non-static method is being called. (Since that anonymous class doesn't carry a reference to the WeakSubscriber-object it still works without the if-check.)
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Yes, I understood that, and the target is actually the Weakreferance. My question is more of what happens inside the Rx compiler when you subscribe to the event:
public static IObservable<EventPattern<NotifyCollectionChangedEventArgs>> ObserveCollectionChanged(this INotifyCollectionChanged collection)
{
return Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
handler => (sender, e) => handler(sender, e),
handler => collection.CollectionChanged += handler,
handler => collection.CollectionChanged -= handler);
}
I get that these event get converted into a stream by use of Subject class. Since the event is subscribed before the weak reference is implemented, in fact it comes directly from the method above. What you are telling me (hopefully I'll get this right ) is that a non-static void, will generate a strong reference, and the static (shared) void creates a weak reference. So the event subscription in the code above is not important?
|
|
|
|
|
Observable.FromEventPattern<..>(..) doesn't subscribe to the event immediately. This only happens once you subscribe to the Observable, so here in SubscribeWeakly<..>(..). Then it executes that handler => collection.CollectionChanged += handler -delegate to subscribe to the event and calls the onNext-delegate provided by Subscribe(..) whenever that event occurs.
But if you call SubscribeWeakly<..>(..) with an onNext-delegate that calls an instance (non-static) method of WeakSubscriber then that delegate holds a normal (=strong) reference to the instance of the class of that method - because it has to call the method on the class instance. That delegate becomes part of the delegate that is constructed in SubscribeWeakly<..>(..) (item => {..}) and passed to the Observable when calling Subscribe(..) on it. So then the Observable holds a normal (=strong) reference to the WeakSubscriber-instance which then can't get GC'ed any more even if you release your reference to it. The code in ObserveCollectionChanged(..) is of course important but not related to this issue.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Kenneth Haugland wrote: I also have sort of a side question, and that is how do I enable to write an Expression in the code that allows me to write:
ObservePropertyChanged(Myclass, s => s.MyPropertyInMyClass) Caution: Code dump incoming!
using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
namespace RxPropertyChanged
{
class A : NotifierBase
{
public string ID { get; set; }
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
private string name;
}
class WeakPropertyChangedSubscriber<TTarget, TProperty>
where TTarget : INotifyPropertyChanged
{
private Action<EventPattern<PropertyChangedEventArgs>> EventAction;
public WeakPropertyChangedSubscriber(TTarget target, Expression<Func<TTarget, TProperty>> propertyExpr, Action<EventPattern<PropertyChangedEventArgs>> eventAction)
{
EventAction = eventAction;
target.FromPropertyChanged(propertyExpr).SubscribeWeakly(this, HandleEvent);
}
private static void HandleEvent(WeakPropertyChangedSubscriber<TTarget, TProperty> subscriber, EventPattern<PropertyChangedEventArgs> item)
{
subscriber.EventAction(item);
}
}
public static class IObservableExtensions
{
public static IObservable<EventPattern<PropertyChangedEventArgs>> FromPropertyChanged<TTarget, TProperty>(this TTarget target, Expression<Func<TTarget, TProperty>> propertyExpr)
where TTarget : INotifyPropertyChanged
{
var propertyInfo = (propertyExpr.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The specified expression does not reference a property.", nameof(propertyExpr));
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
eventHandler => (s, e) => eventHandler(s, e),
propertyChangedEventHandler => target.PropertyChanged += propertyChangedEventHandler,
propertyChangedEventHandler => target.PropertyChanged -= propertyChangedEventHandler)
.Where(evt => evt.EventArgs.PropertyName == propertyInfo.Name);
}
public static IDisposable SubscribeWeakly<TEventPattern, TSubscriber>(this IObservable<TEventPattern> observable, TSubscriber subscriber, Action<TSubscriber, TEventPattern> onNext)
where TSubscriber : class
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
var reference = new WeakReference(subscriber);
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
var currentTarget = reference.Target as TSubscriber;
if (currentTarget != null)
{
onNext(currentTarget, item);
}
else
{
Console.WriteLine("subscription.Dispose()");
subscription.Dispose();
}
});
return subscription;
}
}
class Program
{
static void Main(string[] args)
{
A a1 = new A() { ID = "a1" };
A a2 = new A() { ID = "a2" };
Action<A, EventPattern<PropertyChangedEventArgs>> eventAction = (obj, evt) => Console.WriteLine($"{obj.ID}.{evt.EventArgs.PropertyName} = {obj.Name}");
var subscriberA1 = new WeakPropertyChangedSubscriber<A, string>(a1, x => x.Name, evt => eventAction(a1, evt));
var subscriberA2 = new WeakPropertyChangedSubscriber<A, string>(a2, x => x.Name, evt => eventAction(a2, evt));
a1.Name = "name1.1";
a2.Name = "name2.1";
subscriberA1 = null;
GC.Collect();
Console.WriteLine("Full collection completed");
a1.Name = "name1.2";
a2.Name = "name2.2";
Console.Read();
}
}
}
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Thanks, this is really helpful. Also, I finally gave in and bought a book on Linq.
|
|
|
|
|
Which book did you buy? I bought one or two but they weren't really good. Ended up learning from online resources.
Was my last reply in the other branch of the thread an answer to your question?
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Yes, your code solves my question, but I think I need to learn more about the usage of TTarget and TProperty stuff and their links to Expression. What are the conditions of using them, what are the limitations, and how can I set up links to them etc. I feel that these questions would take 100 pages or more to explain, so I had to read up on something. If I can read a book to get the basics down first I end up having a million silly questions to ask to get it right.
I already have Linq to objects book[^], and that was really good at showing basic usage and an overall overview of how to use and create custom queries. So I got Linq in Action[^] and hope that is will provide some more answers. After the original buzz of Linq in 2008 -2009 there seem to be very few new books, except one from Microsoft.
Edit:
After looking at your examples and reading a bit I finally understood what I was supposed to do now, so I can show you in code what I meant:
public static IObservable<EventPattern<PropertyChangedEventArgs>> ObservePropertyChanged<TSource,TProperty>(this TSource collection, Expression<Func<TSource, TProperty>> propertyExpr) where TSource : INotifyPropertyChanged
{
Contract.Requires(collection != null);
Contract.Requires(propertyExpr != null);
var body = propertyExpr.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
string propertyName = propertyInfo.Name;
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
handler => (sender, e) => handler(sender, e),
handler => collection.PropertyChanged += handler,
handler => collection.PropertyChanged -= handler)
.Where(evt => evt.EventArgs.PropertyName == propertyName);
}
This should greatly reduce typing errors (and enable intellisence) compared to requesting a string of the property name only. I assume that TProperty is basically just the result that returns an unidentified object?
modified 31-Jan-16 11:56am.
|
|
|
|
|
Kenneth Haugland wrote: This should greatly reduce typing errors (and enable intellisence) compared to requesting a string of the property name only. Yes, that's the purpose of the Expression.
Kenneth Haugland wrote: I assume that TProperty is basically just the result that returns an unidentified object? TProperty is just the expected type of the property (string in the case of the code I posted before). The expression doesn't do anything by itself, it just enables us to specify the property in a safer way because the compiler checks for us that its return type is of TProperty. The expression then ends up being "dissected" by us again in ObservePropertyChanged(..) in order to get the property name.
It's not completely compile-time safe because you could specify something else than a property which is also returning the correct type of TProperty. That's why those checks are there in ObservePropertyChanged(..) to ensure (at runtime) that the expression is "pointing" at a property. But even that isn't completely safe because you'd still able to call it like this x => someNonTTargetTypedObject.SomeTPropertyTypedProperty.
That could be also be checked though. Maybe an exercise for you
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Sascha Lefèvre wrote: to ensure (at runtime) that the expression is "pointing" at a property
Even thats not enough, the property coud be WriteOnly
|
|
|
|
|
Your code works very well, but I don't really like changing the pattern from the original implementation, so I came up with this:
internal class WeakPropertyChangedSubscriber
{
public WeakPropertyChangedSubscriber(object WeakClass, IObservable<EventPattern<PropertyChangedEventArgs>> observable,ref IDisposable Result, Action<EventPattern<PropertyChangedEventArgs>> eventAction)
{
Result = observable.InternalSubscribeToWeakPropertyChange(eventAction, WeakClass, WeakPropertyChangedSubscriber.HandleEvent);
}
public static void HandleEvent(Action<EventPattern<PropertyChangedEventArgs>> subscriber, object reffing, EventPattern<PropertyChangedEventArgs> item)
{
subscriber(item);
}
}
public static IDisposable SubscribeWeakly<T>(this IObservable<T> observable, object WeakClass, Action<EventPattern<PropertyChangedEventArgs>> onNext)
{
IDisposable Result = null;
IObservable<EventPattern<PropertyChangedEventArgs>> ObservableCast = (IObservable<EventPattern<PropertyChangedEventArgs>>)observable;
WeakPropertyChangedSubscriber WeakReferanceChanged = new WeakPropertyChangedSubscriber(WeakClass, ObservableCast, ref Result, onNext);
return Result;
}
private static IDisposable InternalSubscribeToWeakPropertyChange<TEventPattern, TSubscriber, TWeakReferance>(this IObservable<TEventPattern> observable, TSubscriber subscriber, TWeakReferance refing, Action<TSubscriber, TWeakReferance, TEventPattern> onNext)
where TSubscriber : class where TWeakReferance : class
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
var reference = new WeakReference(refing);
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
var currentTarget = reference.Target as TWeakReferance;
if (currentTarget != null)
{
onNext(subscriber,currentTarget, item);
}
else
{
Console.WriteLine("subscription.Dispose()");
subscription.Dispose();
}
});
return subscription;
}
This way you can use it almost like the normal subscribe. Only difference is that you have to say what object that should have a weak reference:
var er = ObservableEx.Observe(C, o => o.Name).SubscribeWeakly(C,arg => PropertyChanged(arg.Sender, arg.EventArgs));
C.Name = "test";
C = null;
GC.Collect();
C = new ViewModelB();
C.Name = "2";
There has to be a way to get the class trough reflection? I coudnt find it now though.
|
|
|
|
|
I have a form with some listviews and some label's (to punctualize the LVs and LBs are in a Panel on the form), in a nutshell, these LVs show up some items from a source, all works OK, when i show the form (all it's controls are disabled initially) the LVs loads some items and show up exactly as i expected.
I decided to rearrange theese LVs and LBs with a tablelayoutpanel (TBP), so i placed a TBP inside the form and i moved LVs and LBs in the TBP in this manner:
Label1 | Label2 | Label3 (AutoSize)
-------+--------+--------
LV1 +LV2 +LV3 (50%)
-------+--------+--------
Label4 | Label5 | Label6 (AutoSize)
-------+--------+--------
LV4 +LV5 +LV6 (50 %)
and i haven't modified the code.
When i launch the program ... the LVs don't show anything ....
to countercheck i move one LV ouside the TBP and items return to show ups ...
Where i'm in Wrong ?
Thanks in Advance & sorry for my Ugly English
modified 30-Jan-16 7:54am.
|
|
|
|
|
I found the problem.
In the load events of the form there is an initialization that use a recursive alghoritm. Using TableLayoutPane this recursion in blocked because the cicle on This.Controls do not return the controls that i moved in TableLayoutPanel so that controls can't be initialized.
|
|
|
|
|
Glad you found the answer: there's an internet delay here, where I am, and I didn't see your post until after I posted my questions.
There is a well-known problem with Items not showing up in the WinForm ListView when it's in 'Detail View, and is loaded at run-time. You can find several threads on that on StackOverFlow.
«In art as in science there is no delight without the detail ... Let me repeat that unless these are thoroughly understood and remembered, all “general ideas” (so easily acquired, so profitably resold) must necessarily remain but worn passports allowing their bearers short cuts from one area of ignorance to another.» Vladimir Nabokov, commentary on translation of “Eugene Onegin.”
|
|
|
|
|
Thanks a Lot .... another previous problem was the Details behavior ... but i haven't hunted the problem i used another type of view.
Thanks to you now i can see the light in the tunnel ... i hope that it is not a train! 8)
|
|
|
|
|
Please clarify:
at design-time:
1. your ListView Controls all have their 'View Property set to 'Details ? ... or ?
2. you have completely configured the TableLayoutPanel ?
3. you have added the ListView Controls by drag-drop ?
at run-time:
please show a selection of your code where you are "loading" the ListView Controls.
«In art as in science there is no delight without the detail ... Let me repeat that unless these are thoroughly understood and remembered, all “general ideas” (so easily acquired, so profitably resold) must necessarily remain but worn passports allowing their bearers short cuts from one area of ignorance to another.» Vladimir Nabokov, commentary on translation of “Eugene Onegin.”
|
|
|
|
|
So I basically wanted to use Rx[^] for property changes. But except for the mundane cases[^] its not so straight forward, as you would need some extensions to consume things.
After some reading and searching I found what (I think) I was looking for:
Observable from any property in a INotifyPropertyChanged class[^]
The code that I tried to use was this:
public static class ObservableEx
{
public static IObservable<TResult> FromPropertyChanged<T, TResult>(T target, Expression<Func<T, TResult>> property)
{
Contract.Requires(target != null);
Contract.Requires(property != null);
var body = property.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
string propertyName = propertyInfo.Name;
var propertyDescriptor = (from p in TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>()
where string.Equals(p.Name, propertyName, StringComparison.Ordinal)
select p)
.Single();
if (!propertyDescriptor.SupportsChangeEvents)
throw new ArgumentException("The specified property does not support change events.", "property");
var getter = property.Compile();
return from e in Observable.FromEvent<EventHandler, EventArgs>(
h => propertyDescriptor.AddValueChanged(target, h),
h => propertyDescriptor.RemoveValueChanged(target, h))
select getter(target);
}
}
My problem is in the outcommented line:
No overload for 'Invoke' matches delegate 'EventHandler'
I used the code like this (were A is a class instance that has a property called Name):
var change = ObservableEx.FromPropertyChanged(A, o => o.Name);
change.Subscribe(x=> Debug.WriteLine(x));
How do I fix this?
|
|
|
|
|
Well, I would change from using FromEvent to using FromEventPattern, so my implementation at this point would look something like this:
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => h.Invoke,
h => h.PropertyChanged += h,
h => h.PropertyChanged -= h)
.Where(evt => evt.EventArgs.PropertyName == the_property_name_you_are_watching)
.Select(evt => getter(target)).Subscribe(changedHandler);
This space for rent
|
|
|
|
|
I stripped the implementation down and got this:
IDisposable DisposableEvent;
...
DisposableEvent = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
h => MyClass.PropertyChanged += h,
h => MyClass.PropertyChanged -= h)
.Where(evt => evt.EventArgs.PropertyName == "Name")
.Subscribe(arg => PropertyChanged(arg.Sender, arg.EventArgs));
...
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
DisposableEvent.Dispose();
}
I didnt seem to have a need for the h=>h.Invoke wasnt entirely sure why it was there in the first palce either. Im also a bit confused as to why the arg in Subscribe method returns System.Reactive.EventPattern<PropertyChangedEventArgs> , so that I have to use a delegate in order to use the PropertyChanged void. The last Select(evt => getter(target)) I simpy dont understand at all.
|
|
|
|
|
Kenneth Haugland wrote: The last Select(evt => getter(target)) I simpy dont understand at all.
The FromEvent method returns an IObservable<TEventArgs> instance. Every property change would push the EventArgs to the observers, which wouldn't be particularly useful.
The Select converts the returned value to an IObservable<TResult> , where TResult is the type of the property you're observing. It does this by ignoring the EventArgs , and retrieving the current value of the property instead.
NB: Since it doesn't use a WeakReference , the returned object will keep the target alive, which could potentially cause a memory leak.
It looks like the FromEventPattern method is a better fit.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Richard Deeming wrote: The Select converts the returned value to an IObservable<TResult> , where TResult is the type of the property you're observing. It does this by ignoring the EventArgs , and retrieving the current value of the property instead.
I see, makes sence now.
Richard Deeming wrote: NB: Since it doesn't use a WeakReference , the returned object will keep the target alive, which could potentially cause a memory leak.
Yes, that was the thing I was getting at in the end. The examples around the web seems to jsut wrap the ProeprtyObserver inside a Propertyobserver, only changing the the sender from a straong to a weak referance. They dont really seem to care about the WeakEventManager at all. The reason I liked the first example, was that I could see that I could implement the WeakEventManager directly in it, exchanging the PropertyDescriptor. But seems like that is not nessecary?
|
|
|
|
|
I suspect the Rx libraries have changed since that message was posted in 2011.
In the version I have (2.2.0.0), the FromEvent overload that you're trying to call has the following signature:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(
Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
The conversion parameter that's causing confusion is a function which takes a single parameter (d ) and returns an EventHandler instance. The parameter is an action which takes a single EventArgs parameter.
Therefore, d.Invoke has the signature:
void Invoke(EventArgs e);
whereas the EventHandler delegate you're trying to return has the signature:
void Invoke(object sender, EventArgs e);
Obviously, those signatures are not compatible. You need to return another lambda method / delegate which matches the EventHandler signature, and passes the second parameter (e ) to the action (d ):
d => (sender, e) => d(e)
These nested lambdas can be quite confusing. Breaking it down, you end up with something similar to this:
private sealed class TheClosure
{
public Action<EventArgs> TheActionToCall;
public void TheEventHandler(object sender, EventArgs e)
{
TheActionToCall(e);
}
}
private static EventHandler Conversion(Action<EventArgs> d)
{
TheClosure closure = new TheClosure();
closure.TheActionToCall = d;
return closure.TheEventHandler;
}
...
Func<Action<EventArgs>, EventHandler> conversion = Conversion;
return Observable.FromEvent<EventHandler, EventArgs>(conversion, ...).Select(ignored => getter(target));
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
|
You're feeding a PropertyChangedEventHandler into the Observable but AddValueChanged(..) and RemoveValueChanged(..) expect a plain EventHandler. So for the first argument of Observable.FromEvent(..) you have to create a delegate that takes the PropertyChangedEventHandler and returns an EventHandler:
return Observable.FromEvent<EventHandler, EventArgs>(
propertyChangedHandler =>
{
EventHandler handler = (s, e) => propertyChangedHandler(e);
return handler;
},
h => propertyDescriptor.AddValueChanged(target, h),
h => propertyDescriptor.RemoveValueChanged(target, h))
.Select(x => propertyExpr.Compile()(target)); Or shorter syntax:
return Observable.FromEvent<EventHandler, EventArgs>(
propertyChangedHandler => (s, e) => propertyChangedHandler(e),
h => propertyDescriptor.AddValueChanged(target, h),
h => propertyDescriptor.RemoveValueChanged(target, h))
.Select(x => propertyExpr.Compile()(target));
Edit: Too slow..
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Yes, working my way through the answers
But I cant get your last line of code to compile, I had to do this:
public static class ObservableEx
{
public static IObservable<TResult> FromPropertyChanged<T, TResult>(T target, Expression<Func<T, TResult>> property)
{
Contract.Requires(target != null);
Contract.Requires(property != null);
var body = property.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
string propertyName = propertyInfo.Name;
var propertyDescriptor = (from p in TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>()
where string.Equals(p.Name, propertyName, StringComparison.Ordinal)
select p)
.Single();
if (!propertyDescriptor.SupportsChangeEvents)
throw new ArgumentException("The specified property does not support change events.", "property");
var getter = property.Compile();
return from e in Observable.FromEvent<EventHandler, EventArgs>(
propertyChangedHandler => (s, e) => propertyChangedHandler(e),
h => propertyDescriptor.AddValueChanged(target, h),
h => propertyDescriptor.RemoveValueChanged(target, h))
select getter(target);
}
}
And the result will only return the actual value thats changed, not the sender and event args.
|
|
|
|
|
Kenneth Haugland wrote: But I cant get your last line of code to compile Sorry, I renamed property to propertyExpr (because it's an Expression) without mentioning it.
Kenneth Haugland wrote: And the result will only return the actual value thats changed, not the sender and event args. That's the case for all the so far shown solutions in this thread. If you want to have the sender and event args you would have to change some stuff:
public class PropertyChangedResult<TTarget, TProperty>
{
public TTarget Target { get; private set; }
public TProperty Value { get; private set; }
public EventArgs EventArgs { get; private set; }
public PropertyChangedResult(TTarget target, TProperty value, EventArgs e)
{
Target = target;
Value = value;
EventArgs = e;
}
}
public static IObservable<PropertyChangedResult<TTarget, TProperty>> FromPropertyChanged<TTarget, TProperty>(TTarget target, Expression<Func<TTarget, TProperty>> propertyExpr)
{
Contract.Requires(target != null);
Contract.Requires(property != null);
var body = propertyExpr.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
var propertyInfo = body.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The specified expression does not reference a property.", "property");
string propertyName = propertyInfo.Name;
var propertyDescriptor = (from p in TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>()
where string.Equals(p.Name, propertyName, StringComparison.Ordinal)
select p)
.Single();
if (!propertyDescriptor.SupportsChangeEvents)
throw new ArgumentException("The specified property does not support change events.", "property");
return Observable.FromEvent<EventHandler, EventArgs>(
propertyChangedHandler => (s, e) => propertyChangedHandler(e),
h => propertyDescriptor.AddValueChanged(target, h),
h => propertyDescriptor.RemoveValueChanged(target, h))
.Select(e => new PropertyChangedResult<TTarget, TProperty>(target, propertyExpr.Compile()(target), e));
}
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|