Skip to content

Commit 0e41774

Browse files
author
Todd Berman
committed
This streamlines the boiler plate nonsense in the Reactive* classes and moves it to an extension interface.
Putting this up just for a quick look, I did it to 2 of the umpteen classes that are similar, if this approach makes sense, I will do this for all the other classes with the same boiler plate and then put it up again for a final review before merging.
1 parent be7fbc9 commit 0e41774

File tree

4 files changed

+240
-315
lines changed

4 files changed

+240
-315
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using System.Reactive.Subjects;
4+
using System.Reactive.Concurrency;
5+
using System.Threading;
6+
using System.Reactive.Disposables;
7+
using System.Diagnostics.Contracts;
8+
using System.ComponentModel;
9+
using Splat;
10+
using System.Collections.Generic;
11+
12+
namespace ReactiveUI.Cocoa {
13+
14+
public interface IReactiveExtension : IEnableLogger {
15+
event PropertyChangingEventHandler PropertyChanging;
16+
event PropertyChangedEventHandler PropertyChanged;
17+
18+
void RaisePropertyChanging(PropertyChangingEventArgs args);
19+
void RaisePropertyChanged(PropertyChangedEventArgs args);
20+
}
21+
22+
public static class IReactiveExtensionExtensions {
23+
static ConditionalWeakTable<IReactiveExtension, ExtensionState> state = new ConditionalWeakTable<IReactiveExtension, ExtensionState>();
24+
25+
internal static void setupReactiveExtension(this IReactiveExtension This) {
26+
state.GetOrCreateValue(This);
27+
}
28+
29+
internal static IObservable<IObservedChange<object, object>> getChangedObservable(this IReactiveExtension This) {
30+
return state.GetOrCreateValue(This).ChangedSubject;
31+
}
32+
33+
internal static IObservable<IObservedChange<object, object>> getChangingObservable(this IReactiveExtension This) {
34+
return state.GetOrCreateValue(This).ChangingSubject;
35+
}
36+
37+
internal static IObservable<Exception> getThrownExceptionsObservable(this IReactiveExtension This) {
38+
return state.GetOrCreateValue(This).ThrownExceptions;
39+
}
40+
41+
/// <summary>
42+
/// When this method is called, an object will not fire change
43+
/// notifications (neither traditional nor Observable notifications)
44+
/// until the return value is disposed.
45+
/// </summary>
46+
/// <returns>An object that, when disposed, reenables change
47+
/// notifications.</returns>
48+
internal static IDisposable suppressChangeNotifications(this IReactiveExtension This)
49+
{
50+
var s = state.GetOrCreateValue(This);
51+
Interlocked.Increment(ref s.ChangeNotificationsSuppressed);
52+
return Disposable.Create(() => Interlocked.Decrement(ref s.ChangeNotificationsSuppressed));
53+
}
54+
55+
internal static void raisePropertyChanging(this IReactiveExtension This, string propertyName)
56+
{
57+
Contract.Requires(propertyName != null);
58+
59+
var s = state.GetOrCreateValue(This);
60+
61+
if (!This.areChangeNotificationsEnabled() || s.ChangingSubject == null)
62+
return;
63+
64+
This.RaisePropertyChanging(new PropertyChangingEventArgs(propertyName));
65+
66+
This.notifyObservable(new ObservedChange<object, object>() {
67+
PropertyName = propertyName, Sender = This, Value = null
68+
}, s.ChangingSubject);
69+
}
70+
71+
internal static void raisePropertyChanged(this IReactiveExtension This, string propertyName)
72+
{
73+
Contract.Requires(propertyName != null);
74+
75+
var s = state.GetOrCreateValue(This);
76+
77+
This.Log().Debug("{0:X}.{1} changed", This.GetHashCode(), propertyName);
78+
79+
if (!This.areChangeNotificationsEnabled() || s.ChangedSubject == null) {
80+
This.Log().Debug("Suppressed change");
81+
return;
82+
}
83+
84+
This.RaisePropertyChanged(new PropertyChangedEventArgs(propertyName));
85+
86+
This.notifyObservable(new ObservedChange<object, object>() {
87+
PropertyName = propertyName, Sender = This, Value = null
88+
}, s.ChangedSubject);
89+
}
90+
91+
internal static bool areChangeNotificationsEnabled(this IReactiveExtension This) {
92+
var s = state.GetOrCreateValue(This);
93+
94+
return (Interlocked.Read(ref s.ChangeNotificationsSuppressed) == 0);
95+
}
96+
97+
internal static void notifyObservable<T>(this IReactiveExtension This, T item, Subject<T> subject)
98+
{
99+
var s = state.GetOrCreateValue(This);
100+
101+
try {
102+
subject.OnNext(item);
103+
} catch (Exception ex) {
104+
This.Log().ErrorException("ReactiveObject Subscriber threw exception", ex);
105+
s.ThrownExceptions.OnNext(ex);
106+
}
107+
}
108+
109+
/// <summary>
110+
/// RaiseAndSetIfChanged fully implements a Setter for a read-write
111+
/// property on a ReactiveObject, using CallerMemberName to raise the notification
112+
/// and the ref to the backing field to set the property.
113+
/// </summary>
114+
/// <typeparam name="TObj">The type of the This.</typeparam>
115+
/// <typeparam name="TRet">The type of the return value.</typeparam>
116+
/// <param name="This">The <see cref="ReactiveObject"/> raising the notification.</param>
117+
/// <param name="backingField">A Reference to the backing field for this
118+
/// property.</param>
119+
/// <param name="newValue">The new value.</param>
120+
/// <param name="propertyName">The name of the property, usually
121+
/// automatically provided through the CallerMemberName attribute.</param>
122+
/// <returns>The newly set value, normally discarded.</returns>
123+
public static TRet RaiseAndSetIfChanged<TRet>(
124+
this IReactiveExtension This,
125+
ref TRet backingField,
126+
TRet newValue,
127+
[CallerMemberName] string propertyName = null)
128+
{
129+
Contract.Requires(propertyName != null);
130+
131+
if (EqualityComparer<TRet>.Default.Equals(backingField, newValue)) {
132+
return newValue;
133+
}
134+
135+
This.raisePropertyChanging(propertyName);
136+
backingField = newValue;
137+
This.raisePropertyChanged(propertyName);
138+
return newValue;
139+
}
140+
141+
/// <summary>
142+
/// Use this method in your ReactiveObject classes when creating custom
143+
/// properties where raiseAndSetIfChanged doesn't suffice.
144+
/// </summary>
145+
/// <param name="This">The instance of ReactiveObject on which the property has changed.</param>
146+
/// <param name="propertyName">
147+
/// A string representing the name of the property that has been changed.
148+
/// Leave <c>null</c> to let the runtime set to caller member name.
149+
/// </param>
150+
public static void RaisePropertyChanged(this IReactiveExtension This, [CallerMemberName] string propertyName = null)
151+
{
152+
This.raisePropertyChanged(propertyName);
153+
}
154+
155+
/// <summary>
156+
/// Use this method in your ReactiveObject classes when creating custom
157+
/// properties where raiseAndSetIfChanged doesn't suffice.
158+
/// </summary>
159+
/// <param name="This">The instance of ReactiveObject on which the property has changed.</param>
160+
/// <param name="propertyName">
161+
/// A string representing the name of the property that has been changed.
162+
/// Leave <c>null</c> to let the runtime set to caller member name.
163+
/// </param>
164+
public static void RaisePropertyChanging(this IReactiveExtension This, [CallerMemberName] string propertyName = null)
165+
{
166+
This.raisePropertyChanging(propertyName);
167+
}
168+
169+
class ExtensionState {
170+
public ExtensionState() {
171+
ChangingSubject = new Subject<IObservedChange<object, object>>();
172+
ChangedSubject = new Subject<IObservedChange<object, object>>();
173+
ThrownExceptions = new ScheduledSubject<Exception>(Scheduler.Immediate, RxApp.DefaultExceptionHandler);
174+
}
175+
176+
public Subject<IObservedChange<object, object>> ChangingSubject { get; private set; }
177+
public Subject<IObservedChange<object, object>> ChangedSubject { get; private set; }
178+
public ScheduledSubject<Exception> ThrownExceptions { get; private set; }
179+
180+
public long ChangeNotificationsSuppressed;
181+
}
182+
}
183+
}
184+

0 commit comments

Comments
 (0)