Reactive Extensions, Subject<T> - c#

I'm having some hard time understanding the Subject object.
Consider the following code:
var sub = new Subject<int>();
sub.Subscribe(x => Console.WriteLine(x)); //subscriber #1
sub.Subscribe(x => Console.WriteLine(x)); //subscriber #2
sub.OnNext(2);
I'm creating a subject of int, and when i execute OnNext it calls the other subscribers (#1 and #2).
The thing that i don't get is that i read that Subject means an object which is both observable and observer but how does this explains why when i call OnNext the other subscribers are called.
I would understand if OnNext of the subject would propagate it to all subscribers = publish to all others (which make sense) but when I checked the source code i couldn't see anything that does it, see below.
Can someone maybe understand from the code below what exactly makes the OnNext(2) propagate to the other subscriptions? (#1, #2)?
public sealed class Subject : ISubject, ISubject, IObserver, IObservable, IDisposable
{
// Fields
private volatile IObserver _observer;
// Methods
public Subject()
{
this._observer = NopObserver<T>.Instance;
}
public void Dispose()
{
this._observer = DisposedObserver<T>.Instance;
}
public void OnCompleted()
{
IObserver<T> comparand = null;
IObserver<T> completed = DoneObserver<T>.Completed;
do
{
comparand = this._observer;
}
while (((comparand != DisposedObserver<T>.Instance) && !(comparand is DoneObserver<T>)) && (Interlocked.CompareExchange<IObserver<T>>(ref this._observer, completed, comparand) != comparand));
comparand.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
{
throw new ArgumentNullException("error");
}
IObserver<T> comparand = null;
DoneObserver<T> observer3 = new DoneObserver<T> {
Exception = error
};
DoneObserver<T> observer2 = observer3;
do
{
comparand = this._observer;
}
while (((comparand != DisposedObserver<T>.Instance) && !(comparand is DoneObserver<T>)) && (Interlocked.CompareExchange<IObserver<T>>(ref this._observer, observer2, comparand) != comparand));
comparand.OnError(error);
}
public void OnNext(T value)
{
this._observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
{
throw new ArgumentNullException("observer");
}
IObserver<T> comparand = null;
IObserver<T> observer3 = null;
do
{
comparand = this._observer;
if (comparand == DisposedObserver<T>.Instance)
{
throw new ObjectDisposedException("");
}
if (comparand == DoneObserver<T>.Completed)
{
observer.OnCompleted();
return Disposable.Empty;
}
DoneObserver<T> observer4 = comparand as DoneObserver<T>;
if (observer4 != null)
{
observer.OnError(observer4.Exception);
return Disposable.Empty;
}
if (comparand == NopObserver<T>.Instance)
{
observer3 = observer;
}
else
{
Observer<T> observer5 = comparand as Observer<T>;
if (observer5 != null)
{
observer3 = observer5.Add(observer);
}
else
{
observer3 = new Observer<T>(new ImmutableList<IObserver<T>>(new IObserver<T>[] { comparand, observer }));
}
}
}
while (Interlocked.CompareExchange<IObserver<T>>(ref this._observer, observer3, comparand) != comparand);
return new Subscription<T>((Subject<T>) this, observer);
}
private void Unsubscribe(IObserver<T> observer)
{
IObserver<T> comparand = null;
IObserver<T> instance = null;
Label_0004:
comparand = this._observer;
if ((comparand != DisposedObserver<T>.Instance) && !(comparand is DoneObserver<T>))
{
Observer<T> observer4 = comparand as Observer<T>;
if (observer4 != null)
{
instance = observer4.Remove(observer);
}
else
{
if (comparand != observer)
{
return;
}
instance = NopObserver<T>.Instance;
}
if (Interlocked.CompareExchange<IObserver<T>>(ref this._observer, instance, comparand) != comparand)
{
goto Label_0004;
}
}
}
// Properties
public bool HasObservers
{
get
{
return (((this._observer != NopObserver<T>.Instance) && !(this._observer is DoneObserver<T>)) && (this._observer != DisposedObserver<T>.Instance));
}
}
// Nested Types
private class Subscription : IDisposable
{
// Fields
private IObserver<T> _observer;
private Subject<T> _subject;
// Methods
public Subscription(Subject<T> subject, IObserver<T> observer)
{
this._subject = subject;
this._observer = observer;
}
public void Dispose()
{
IObserver<T> observer = Interlocked.Exchange<IObserver<T>>(ref this._observer, null);
if (observer != null)
{
this._subject.Unsubscribe(observer);
this._subject = null;
}
}
}
}

I know that but what bothers me is that it didn't make sense. I dug further into the code and found out that their internal implementation of observer contains more observers, see below.
And if you check the OnNext method you can see that they are iterating over all the observers and calling their OnNext method.
Now everything make sense to me, i understood the logic but couldn't see where it was implemented.
internal class Observer<T> : IObserver<T>
{
private readonly ImmutableList<IObserver<T>> _observers;
public Observer(ImmutableList<IObserver<T>> observers)
{
this._observers = observers;
}
internal IObserver<T> Add(IObserver<T> observer)
{
return new Observer<T>(this._observers.Add(observer));
}
public void OnCompleted()
{
foreach (IObserver<T> observer in this._observers.Data)
{
observer.OnCompleted();
}
}
public void OnError(Exception error)
{
foreach (IObserver<T> observer in this._observers.Data)
{
observer.OnError(error);
}
}
public void OnNext(T value)
{
foreach (IObserver<T> observer in this._observers.Data)
{
observer.OnNext(value);
}
}
internal IObserver<T> Remove(IObserver<T> observer)
{
int index = Array.IndexOf<IObserver<T>>(this._observers.Data, observer);
if (index < 0)
{
return this;
}
if (this._observers.Data.Length == 2)
{
return this._observers.Data[1 - index];
}
return new Observer<T>(this._observers.Remove(observer));
}
}

Subject is an observable because you can subscribe to it. You do it in your example (you did subscribe two subscribers).
Subject is also an observer because you can do the following:
someObservable.Subscribe(subject);
That way your subject will receive the events from someObservable and propagate them to its own subscribers.
P.S. in your code you called the OnNext() method yourself. But that is exactly what will someObservable do when you subscribe to it with your subject.

Related

Short circuit yield return & cleanup/dispose

Take this pseudo example code:
static System.Runtime.InteropServices.ComTypes.IEnumString GetUnmanagedObject() => null;
static IEnumerable<string> ProduceStrings()
{
System.Runtime.InteropServices.ComTypes.IEnumString obj = GetUnmanagedObject();
var result = new string[1];
var pFetched = Marshal.AllocHGlobal(sizeof(int));
while(obj.Next(1, result, pFetched) == 0)
{
yield return result[0];
}
Marshal.ReleaseComObject(obj);
}
static void Consumer()
{
foreach (var item in ProduceStrings())
{
if (item.StartsWith("foo"))
return;
}
}
Question is if i decide to not enumerate all values, how can i inform producer to do cleanup?
Even if you are after a solution using yield return, it might be useful to see how this can be accomplished with an explicit IEnumerator<string> implementation.
IEnumerator<T> derives from IDisposable and the Dispose() method will be called when foreach is left (at least since .NET 1.2, see here)
static IEnumerable<string> ProduceStrings()
{
return new ProduceStringsImpl();
}
This is the class implementing IEnumerable<string>
class ProduceStringsImpl : IEnumerable<string>
{
public IEnumerator<string> GetEnumerator()
{
return new EnumProduceStrings();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
And here we have the core of the solution, the IEnumerator<string> implementation:
class EnumProduceStrings : IEnumerator<string>
{
private System.Runtime.InteropServices.ComTypes.IEnumString _obj;
private string[] _result;
private IntPtr _pFetched;
public EnumProduceStrings()
{
_obj = GetUnmanagedObject();
_result = new string[1];
_pFetched = Marshal.AllocHGlobal(sizeof(int));
}
public bool MoveNext()
{
return _obj.Next(1, _result, _pFetched) == 0;
}
public string Current => _result[0];
void IEnumerator.Reset() => throw new NotImplementedException();
object IEnumerator.Current => Current;
public void Dispose()
{
Marshal.ReleaseComObject(_obj);
Marshal.FreeHGlobal(_pFetched);
}
}
I knew i can! Despite guard, Cancel is called only one time in all circumtances.
You can instead encapsulate logic with a type like IterationResult<T> and provide Cleanup method on it but its essentially same idea.
public class IterationCanceller
{
Action m_OnCancel;
public bool Cancelled { get; private set; }
public IterationCanceller(Action onCancel)
{
m_OnCancel = onCancel;
}
public void Cancel()
{
if (!Cancelled)
{
Cancelled = true;
m_OnCancel();
}
}
}
static IEnumerable<(string Result, IterationCanceller Canceller)> ProduceStrings()
{
var pUnmanaged = Marshal.AllocHGlobal(sizeof(int));
IterationCanceller canceller = new IterationCanceller(() =>
{
Marshal.FreeHGlobal(pUnmanaged);
});
for (int i = 0; i < 2; i++) // also try i < 0, 1
{
yield return (i.ToString(), canceller);
}
canceller.Cancel();
}
static void Consumer()
{
foreach (var (item, canceller) in ProduceStrings())
{
if(item.StartsWith("1")) // also try consuming all values
{
canceller.Cancel();
break;
}
}
}

Simple event system, can't remove listeners from list

I made a simple event system by following a tutorial, the registration of listeners and firing events works well, but I can't remove any listener from it.
delegate void EventListener(EventInfoBase eventInfo);
Dictionary<System.Type, List<EventListener>> eventListeners;
public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
eventListeners = new Dictionary<System.Type, List<EventListener>>();
}
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
eventListeners[eventType] = new List<EventListener>();
}
EventListener wrapper = (ei) => { listener((T)ei); };
eventListeners[eventType].Add(wrapper);
}
public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
return;
}
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
return;
}
EventListener wrapper = (ei) => { listener((T)ei); };
EventListener toRemove = eventListeners[eventType].Find(x => x.Equals(wrapper));
//EventListener toRemove = eventListeners[eventType].Find(x => x.Target == wrapper.Target && x.Method == wrapper.Method);
if (toRemove != null)
{
eventListeners[eventType].Remove(toRemove); // Never gets called
}
}
This is how it's called (it's a singleton):
EventsSystem.Instance.RegisterListener<EventInfoWin>(OnWin);
EventsSystem.Instance.UnregisterListener<EventInfoWin>(OnWin);
So I expected the listener to be removed from appropriate list, but it stays there. The UnregisterListener method does nothing. Any way to fix it quickly without rewriting everything?
Your problem is that in RegisterListener<T> you create an anonymous wrapper method of type EventListener and in UnregisterListener<T> you create another wrapper method. These wrappers will never match.
What you can do is to store the original listener along with the wrapper. That will allow you to match with the original listener, but execute the wrapper (to execute the original listener you would need reflection - hence the wrapper). If you switch from a dictionary to a list of tuples, you can do this in a straightforward way:
delegate void EventListener(object eventInfo);
List<(System.Type Type, Delegate Listener, EventListener Wrapper)> eventListeners;
public void RegisterListener<T>(System.Action<T> listener)
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
eventListeners = new List<(System.Type, Delegate, EventListener)>();
}
if (!eventListeners.Any(entry => entry.Type.Equals(eventType) &&
entry.Listener.Equals(listener))) {
eventListeners.Add((eventType, listener, ei => listener((T)ei)));
}
}
public void UnregisterListener<T>(System.Action<T> listener)
{
System.Type eventType = typeof(T);
if (eventListeners == null)
{
return;
}
var toRemove = eventListeners.FirstOrDefault(entry => entry.Type.Equals(eventType) &&
entry.Listener.Equals(listener));
eventListeners.Remove(toRemove);
}
You can try it out here.
You cannot use the wrapper delegate as you do. the reason is, that this is creating another "object" on adding, which will not be be recognizable later when you want to remove it.
As Jon Skeet wrote, you can just save the action directly, but as an object. I've tested it, and I did not find a way to have an list with Action of EventInfoBase putting in an Action of EventInfoWin.
So that is what it could look like:
EDIT: I've created a wrapper again, but with the original action as a token to find it again.
delegate void EventListener(EventInfoBase eventInfo);
private class EventWrapper
{
public EventListener Action { get; set; }
public object Token { get; set; }
}
Dictionary<System.Type, List<EventWrapper>> eventListeners = new Dictionary<System.Type, List<EventWrapper>>();
public void RegisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
eventListeners[eventType] = new List<EventWrapper>();
}
EventListener action = (ei) => { listener((T)ei); };
var wrapper = new EventWrapper() { Action = action, Token = listener };
eventListeners[eventType].Add(wrapper);
}
public void UnregisterListener<T>(System.Action<T> listener) where T : EventInfoBase
{
System.Type eventType = typeof(T);
if (!eventListeners.ContainsKey(eventType) || eventListeners[eventType] == null)
{
return;
}
var toRemove = eventListeners[eventType].FirstOrDefault(x => x.Token.Equals(listener));
if (toRemove != null)
{
eventListeners[eventType].Remove(toRemove);
}
}

ConcurrentDictionary InvalidOperationException c#

this is the error:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List1.Enumerator.MoveNextRare()
at System.Collections.Generic.List1.Enumerator.MoveNext()
at System.Linq.Enumerable.WhereListIterator1.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable1 source)
Blockquote
I use the static Dictionary for web api
this is my class that i use for my web api :
public class UsersSecureProvider
{
public static ConcurrentDictionary<short, List<UserSecure>> _Users = new ConcurrentDictionary<short, List<UserSecure>>();
public bool Add(short Group, UserSecure Message)
{
try
{
var GetList = GetByKey(Group);
if (GetList != null)
{
GetList.Add(Message);
return Update(Group, GetList, GetList);
}
else
{
GetList = new List<UserSecure>();
GetList.Add(Message);
return Add(Group, GetList);
}
}
catch { }
return false;
}
private bool Add(short key, List<UserSecure> SendUser)
{
return _Users.TryAdd(key, SendUser);
}
public bool Remove(short Key)
{
List<UserSecure> listremove;
return _Users.TryRemove(Key, out listremove);
}
public List<UserSecure> GetByKey(short Group)
{
var listView = new List<UserSecure>();
if (_Users != null)
{
var getList = _Users.TryGetValue(Group, out listView);
}
return listView;
}
public bool Update(short Group, List<UserSecure> oldlist, List<UserSecure> newlist)
{
return _Users.TryUpdate(Group, newlist, oldlist);
}
public void Clear()
{
_Users.Clear();
}
public ConcurrentDictionary<short, List<UserSecure>> GetAll()
{
return _Users;
}
public bool UpdateListByUser(short Group, List<UserSecure> newlist)
{
var OldList = GetByKey(Group);
return _Users.TryUpdate(Group, newlist, OldList);
}
}
And I call the class
var _providers = new UsersSecureProvider();
List<UserSecure> GetAll = _providers.GetByKey(1);
if (GetAll != null && GetAll.Any() && GetAll.Where(w => w.UserID == UserID && w.Key == UniqueSecure).Count() > 0)
{
result = true;
}
else
{
_providers.Add(1, new UserSecure { UserID = UserID, Key = UniqueSecure });
}
why do i receive this error exception?
thank you.
This:
List<UserSecure> GetAll = _providers.GetByKey(1);
Returns a reference to the underlying collection. That same reference to a list which is probably being modified via one of the other WebAPI actions you have. You cannot both enumerate and modify the List<T>.
Instead, create a new List<T> and enumerate it:
List<UserSecure> GetAll = _providers.GetByKey(1).ToList();
If your application is multi threading it is recomanded to use semaphore. For example
private static object _sync = new object();
public List<UserSecure> GetByKey(short Group)
{
lock(_sync)
{
var listView = new List<UserSecure>();
if (_Users != null)
{
var getList = _Users.TryGetValue(Group, out listView);
}
return listView;
}
}

subscribe as last method raised

Is there a way to subscribe a method though it would be called last when the onNext is raised?
m_subject.Subscribe(() => Console.writeLine("firstSubscription");
m_subject.SubscribeLast(() => Console.writeLine("secondSubscription");
m_subject.Subscribe(() => Console.writeLine("thirdSubscription");
m_subject.OnNext();
// prints:
// firstSubscription
// thirdSubscription
// secondSubscription
You cannot have a subscriber be executed last, but you can wrap all your calls in a single subscription.
Something like that:
Action action = () => {};
Action lastAction = () => {};
m_subject.Subscribe(() =>
{
action();
lastAction();
});
action += (() => Console.writeLine("firstSubscription");
lastAction += (() => Console.writeLine("secondSubscription");
action += (() => Console.writeLine("thirdSubscription");
m_subject.OnNext();
// prints:
// firstSubscription
// thirdSubscription
// secondSubscription
You can also do this by defining a custom Subject<T> which internally has a default subject and also a last subject.
Update
I added overloads of ObserveOn to store the IScheduler and SynchronizationContext and then apply those at the time of subscription. Similar technique can be used to enable SubscribeOn to work as well.
public class SubscribeLastSubject<T> : ISubject<T>, IDisposable
{
private readonly Subject<T> subject = new Subject<T>();
private readonly Subject<T> lastSubject = new Subject<T>();
private IScheduler observeScheduler;
private SynchronizationContext observerContext;
public void OnNext(T value)
{
subject.OnNext(value);
lastSubject.OnNext(value);
}
public void OnError(Exception error)
{
subject.OnError(error);
lastSubject.OnError(error);
}
public void OnCompleted()
{
subject.OnCompleted();
lastSubject.OnCompleted();
}
public IDisposable Subscribe(IObserver<T> observer)
{
return GetObservable().Subscribe(observer);
}
public IDisposable SubscribeLast(IObserver<T> observer)
{
return GetLastObservable().Subscribe(observer);
}
public IDisposable SubscribeLast(Action<T> action)
{
return GetLastObservable().Subscribe(action);
}
public SubscribeLastSubject<T> ObserveOn(IScheduler scheduler)
{
observeScheduler = scheduler;
return this;
}
public SubscribeLastSubject<T> ObserveOn(SynchronizationContext context)
{
observerContext = context;
return this;
}
public void Dispose()
{
subject.Dispose();
lastSubject.Dispose();
}
private IObservable<T> GetObservable()
{
if (observerContext != null)
{
return subject.ObserveOn(observerContext);
}
if (observeScheduler != null)
{
return subject.ObserveOn(observeScheduler);
}
return subject;
}
private IObservable<T> GetLastObservable()
{
if (observerContext != null)
{
return lastSubject.ObserveOn(observerContext);
}
if (observeScheduler != null)
{
return lastSubject.ObserveOn(observeScheduler);
}
return lastSubject;
}
}
Usage
var m_subject = new SubscribeLastSubject<string>();
m_subject.ObserveOn(Scheduler.CurrentThread).Subscribe(s => Console.WriteLine("firstSubscription"));
m_subject.ObserveOn(Scheduler.CurrentThread).SubscribeLast(s => Console.WriteLine("secondSubscription"));
m_subject.ObserveOn(Scheduler.CurrentThread).Subscribe(s => Console.WriteLine("thirdSubscription"));
m_subject.OnNext("1");
Console.ReadKey();
Output
firstSubscription
thirdSubscription
secondSubscription

Lists NotifyPropertyChanging

Well BindingList and ObservableCollection work great to keep data updated and to notify when one of it's objects has changed. However, when notifying a property is about to change, I think these options are not very good.
What I have to do right now to solve this (and I warn this is not elegant AT ALL), is to implement INotifyPropertyChanging on the list's type object and then tie that to the object that holds the list PropertyChanging event, or something like the following:
// this object will be the type of the BindingList
public class SomeObject : INotifyPropertyChanging, INotifyPropertyChanged
{
private int _intProperty = 0;
private string _strProperty = String.Empty;
public int IntProperty
{
get { return this._intProperty; }
set
{
if (this._intProperty != value)
{
NotifyPropertyChanging("IntProperty");
this._intProperty = value;
NotifyPropertyChanged("IntProperty");
}
}
}
public string StrProperty
{
get { return this._strProperty; }
set
{
if (this._strProperty != value)
{
NotifyPropertyChanging("StrProperty");
this._strProperty = value;
NotifyPropertyChanged("StrProperty");
}
}
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObjectThatHoldsTheList : INotifyPropertyChanging, INotifyPropertyChanged
{
public BindingList<SomeObject> BindingList { get; set; }
public ObjectThatHoldsTheList()
{
this.BindingList = new BindingList<SomeObject>();
}
// this helps notifie Changing and Changed on Add
private void AddItem(SomeObject someObject)
{
// this will tie the PropertyChanging and PropertyChanged events of SomeObject to this object
// so it gets notifies because the BindingList does not notify PropertyCHANGING
someObject.PropertyChanging += new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged += new PropertyChangedEventHandler(someObject_PropertyChanged);
this.NotifyPropertyChanging("BindingList");
this.BindingList.Add(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this helps notifies Changing and Changed on Delete
private void DeleteItem(SomeObject someObject)
{
if (this.BindingList.IndexOf(someObject) > 0)
{
// this unlinks the handlers so the garbage collector can clear the objects
someObject.PropertyChanging -= new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged -= new PropertyChangedEventHandler(someObject_PropertyChanged);
}
this.NotifyPropertyChanging("BindingList");
this.BindingList.Remove(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this notifies an item in the list is about to change
void someObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
NotifyPropertyChanging("BindingList." + e.PropertyName);
}
// this notifies an item in the list has changed
void someObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("BindingList." + e.PropertyName);
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Sorry, I know this is a lot of code, which takes me back to my main point IT'S A LOT OF CODE to implement this. So my question is, does anyone know a better, shorter, more elegant solution?
Thanks for your time and suggestions.
You can create a wrapper class, that implements ICustomTypeDescriptor. This wrapper will also implement necessary interfaces (such as INotifyPropertyChanging), intercept properties reads/writes on underlying object, and you will be able to call NotifyPropertyChanging() and NotifyPropertyChanged() methods implemented by a wrapper. The data consumers will work with wrapped objects same as they work with original objects.
But implementing such a wrapper will not be easy if you are not an experienced developer.
Here is the possible, yet not finished implementation of such a wrapper. It already supports INotifyPropertyChanged, and it's easy to understand how to implement INotifyPropertyChanging.
public class Wrapper : ICustomTypeDescriptor, INotifyPropertyChanged, IEditableObject, IChangeTracking
{
private bool _isChanged;
public object DataSource { get; set; }
public Wrapper(object dataSource)
{
if (dataSource == null)
throw new ArgumentNullException("dataSource");
DataSource = dataSource;
}
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes()
{
return new AttributeCollection(
DataSource.GetType()
.GetCustomAttributes(true)
.OfType<Attribute>()
.ToArray());
}
public string GetClassName()
{
return DataSource.GetType().Name;
}
public string GetComponentName()
{
return DataSource.ToString();
}
public TypeConverter GetConverter()
{
return new TypeConverter();
}
public EventDescriptor GetDefaultEvent()
{
return null;
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return Activator.CreateInstance(editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(DataSource, attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(DataSource);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
private IEnumerable<PropertyDescriptor> _Properties;
public IEnumerable<PropertyDescriptor> Properties
{
get
{
if (_Properties == null)
_Properties = TypeDescriptor.GetProperties(DataSource)
.Cast<PropertyDescriptor>()
.Select(pd => new WrapperPropertyDescriptor(pd) as PropertyDescriptor)
.ToList();
return _Properties;
}
}
public PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(Properties.ToArray());
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion ICustomTypeDescriptor
#region ToString, Equals, GetHashCode
public override string ToString()
{
return DataSource.ToString();
}
public override bool Equals(object obj)
{
var wrapper = obj as Wrapper;
if (wrapper == null)
return base.Equals(obj);
else
return DataSource.Equals(wrapper.DataSource);
}
public override int GetHashCode()
{
return DataSource.GetHashCode();
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
_isChanged = true;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public IDictionary<string, object> MakeDump()
{
var result = new Dictionary<String, object>();
foreach (var item in Properties)
result[item.Name] = item.GetValue(this);
return result;
}
#region IEditableObject Members
private IDictionary<string, object> LastDump;
public void BeginEdit()
{
LastDump = MakeDump();
}
public void CancelEdit()
{
if (LastDump != null)
{
foreach (var item in Properties)
item.SetValue(this, LastDump[item.Name]);
_isChanged = false;
}
}
public void EndEdit()
{
AcceptChanges();
}
#endregion IEditableObject
#region IChangeTracking
public void AcceptChanges()
{
LastDump = null;
_isChanged = false;
}
public bool IsChanged
{
get { return _isChanged; }
}
#endregion IChangeTracking
}
public class WrapperPropertyDescriptor : PropertyDescriptor
{
private Wrapper _wrapper;
private readonly PropertyDescriptor SourceDescriptor;
public WrapperPropertyDescriptor(PropertyDescriptor sourceDescriptor) :
base(sourceDescriptor)
{
if (sourceDescriptor == null)
throw new ArgumentNullException("sourceDescriptor");
SourceDescriptor = sourceDescriptor;
}
public override Type ComponentType
{
get
{
return SourceDescriptor.ComponentType;
}
}
public override bool IsReadOnly
{
get
{
return SourceDescriptor.IsReadOnly;
}
}
public override Type PropertyType
{
get
{
return SourceDescriptor.PropertyType;
}
}
public override object GetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var value = SourceDescriptor.GetValue(wrapper.DataSource);
if (value == null)
return value;
var type = value.GetType();
// If value is user class or structure it should
// be wrapped before return.
if (type.Assembly != typeof(String).Assembly)
{
if (typeof(IEnumerable).IsAssignableFrom(type))
throw new NotImplementedException("Here we should construct and return wrapper for collection");
if (_wrapper == null)
_wrapper = new Wrapper(value);
else
_wrapper.DataSource = value;
return _wrapper;
}
return value;
}
public override void SetValue(object component, object value)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var actualValue = value;
var valueWrapper = value as Wrapper;
if (valueWrapper != null)
actualValue = valueWrapper.DataSource;
// Make dump of data source's previous values
var dump = wrapper.MakeDump();
SourceDescriptor.SetValue(wrapper.DataSource, actualValue);
foreach (var item in wrapper.Properties)
{
var itemValue = item.GetValue(wrapper);
if (!itemValue.Equals(dump[item.Name]))
wrapper.OnPropertyChanged(item.Name);
}
}
public override void ResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
SourceDescriptor.ResetValue(wrapper.DataSource);
}
public override bool ShouldSerializeValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.ShouldSerializeValue(wrapper.DataSource);
}
public override bool CanResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.CanResetValue(wrapper.DataSource);
}
}
Again, this is not a complete version, but it can already be used in simple scenarios. The possible usage can look like this:
IList<Customer> customers = CustomerRepository.GetAllCustomers();
IList<Wrapper> wrappedCustomers = customers.Select(c => new Wrapper(c)).ToList();
/* If you don't like LINQ in the line above you can use foreach to transform
list of Customer object to a list of Wrapper<Customer> objects */
comboBoxCustomers.DataSource = wrappedCustomers;
// or
dataGridViewCustomers.DataSource = wrappedCustomers;
So with one simple line of code you have a collection of objects that support INotifyPropertyChanged, IEditableObject, IChangeTracking interfaces!
Good luck!
This is a classic example for cross-cutting concern, which cries for AOP approach. Aspect Oriented Programming is a paradigm which extends classical OOP and lets you solve problems like "I want all method calls on this object to be logged".
There are several ways to do this in .NET, this is a nice list of most of them:
http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx
One of the approaches listed is PostSharp, IL rewriter which let's you do AOP very easily. Here is an example of implementing INotifyPropertyChanged using this tool (another example comes with PostSharp, I think):
http://thetreeknowseverything.net/2009/01/21/auto-implement-inotifypropertychanged-with-aspects/

Categories