Weak References stays alive - c#

I have an interface: IRemoteDataChangedListener
public interface IRemoteDataChangedListener<TData>
{
void DataReceived(TData newData);
}
And a class, RealtimeEventService
public class RealtimeEventService : IRealtimeEventService
{
private readonly IEventListener listener;
private readonly List<Tuple<Type, WeakReference>> dataCreated;
public RealtimeEventService(IEventListener eventListener)
{
this.dataCreated = new List<Tuple<Type, WeakReference>>();
this.listener = eventListener;
this.listener.EventReceived += this.ListenerOnEventReceived;
}
private void ListenerOnEventReceived(EventMessage message)
{
Type type = message.GetType();
if (type == typeof(NotificationReadEventMessage))
{
this.DataChanged((NotificationReadEventMessage)message);
}
}
public void SubscribeDataChanged<TEventMessage>(IRemoteDataChangedListener<TEventMessage> dataChangedListener) where TEventMessage : EventMessage, new()
{
this.dataCreated.Add(Tuple.Create(typeof(TEventMessage), new WeakReference(dataChangedListener)));
}
internal void DataChanged<TKey>(TKey newData)
where TKey : class, new()
{
LoopAndFilter<TKey>(this.dataCreated, listener => listener.DataReceived(newData));
}
private static void LoopAndFilter<TKey>(ICollection<Tuple<Type, WeakReference>> collection,
Action<IRemoteDataChangedListener<TKey>> success) where TKey : class
{
foreach (var reference in collection.ToArray())
{
if (!reference.Item2.IsAlive)
{
collection.Remove(reference);
continue;
}
if (reference.Item1 != typeof(TKey))
continue;
success((IRemoteDataChangedListener<TKey>)reference.Item2.Target);
}
}
#endregion
}
Whenever I create a test class that inherits IRemoteDataChangedListener with NotificationReadEventMessage as generic argument, and use an instance of this class with SubscribeDataChanged(), it gets hooked up just fine, and the method gets called.
Problem is, when I set the instance reference to null and run GC.Collect(), it should then be null, and the next time RealtimeEventService's LoopAndFilter method runs, it should detect that it is no longer alive, and remove the Weakreference from the list.
However it does not. When I inspect the value (In LoopAndFilter), after setting the instance reference to null in the test, the value still shows up as Alive being true.
And now I've been staring at this code for hours, and I simply cannot find anywhere I'd have a strong reference to the class...
Any help?
#Edit: Unit test (Using the Moq and Should libraries):
public class RealtimeEventServiceTests
{
[Fact]
public void VerifyWeakReferencesWorksAsIntended()
{
var eventListenerMock = new Mock<IEventListener>();
IRealtimeEventService service = new RealtimeEventService(eventListenerMock.Object);
bool called = false;
RemoteDataTest dataChangedListener = new RemoteDataTest();
dataChangedListener.Called += (sender, args) => called = true;
service.SubscribeDataChanged(dataChangedListener);
called.ShouldBeFalse();
((RealtimeEventService)service).DataChanged(new NotificationReadEventMessage());
called.ShouldBeTrue();
called = false;
dataChangedListener = null;
GC.Collect();
called.ShouldBeFalse();
((RealtimeEventService)service).DataChanged(new NotificationReadEventMessage());
called.ShouldBeFalse();
}
}
public class RemoteDataTest : IRemoteDataChangedListener<NotificationReadEventMessage>
{
public event EventHandler Called;
public void DataReceived(NotificationReadEventMessage newData)
{
if (Called != null) Called(this, null);
}
}

As it turns out, when I got home and compiled it at home, it ran just fine. And when I got back to work, it worked fine there as well.
Guess it's just one of those spooky bugs that magically vanish at inexplicable times. I'm just glad to be rid of it.
I did take the advice of Ewan & Scott Chamberlain, so thanks for that!

Related

ReactiveExtension that was working on old computer is now failing

I'm using the code from Weak Events in .Net, the easy way to handle monitoring changes to an observable collection. The code has worked without any problems for months. I recently updated to a new computer. After getting everything setup and pulling down the code from my repository I encountered a strange problem. The code no longer works!
Here's the relevant portion of my code, it all takes place in the constructor:
public class PurchaseOrderReceipt : BaseEntity
{
/// <summary>
/// Initializes a new instance of the <see cref="PurchaseOrderReceipt" /> class.
/// </summary>
public PurchaseOrderReceipt()
{
this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
this.DateReceived = DateTime.Now;
this.ReceiptItems.ObserveCollectionChanged()
.SubscribeWeakly(this, (target, eventArgs) => target.ReceiptItemsChanged());
}
The exception is thrown on the SubscribeWeakly line with the following error message: ArgumentException: onNext must refer to a static method, or else the subscription will still hold a strong reference to target
I can recreate the problem in LinqPad just by creating an instance of the PurchaseOrderReceipt.
Odder still if I write a simple class in LinqPad that mirrors the setup in the PurchaseOrderReceipt class than it works.
LinqPad code:
void Main()
{
var x = new Test();
x.ReceiptItems.Add(new PurchaseOrderItemReceipt());
}
public class Test:BaseEntity
{
public ObservableCollection<PurchaseOrderItemReceipt> ReceiptItems {get; set;}
public Test()
{
this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this,(target, eventargs) => target.TestChanged());
}
private void TestChanged()
{
"Changed!".Dump();
}
}
Changed! is printed out in the results window.
Here's the CustomReactiveExtension class from the link at the top.
public static class CustomReactiveExtension
{
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);
}
public static IDisposable SubscribeWeakly<T, TTarget>(this IObservable<T> observable, TTarget target, Action<TTarget, T> onNext) where TTarget : class
{
var reference = new WeakReference(target);
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");
}
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
var currentTarget = reference.Target as TTarget;
if (currentTarget != null)
{
onNext(currentTarget, item);
}
else
{
subscription.Dispose();
}
});
return subscription;
}
}
Any ideas?
I am not 100% certain but my guess is that either different versions of the compiler or different compilation options are resulting in your lambda being compiled to an instance method rather than a static method.
The easiest solution to this would be to explicitly implement a static method to be used as your onNext callback, i.e:
private static void OnReceiptItemsChanged(PurchaseOrderReceipt target,
EventPattern<NotifyCollectionChangedEventArgs> eventPattern)
{
// TODO Do something here
}
And then use SubscribeWeakly like so:
this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this, OnReceiptItemsChanged);
Now regardless of which compiler you use or which compilation options the callback is always a static method.

Automatically calling an init function whenever an object is used for the 1st time

I have an object that only initializes itself with barebones data when constructed (fast), and loads itself for real (slow) when first accessed. The idea is that I'm creating a lot of these barebones objects at startup and hash them into a map, then fully load each object whenever it is individually accessed for the first time. The problem is that I cannot guarantee how clients will interact with this object, there are multiple public methods that might be invoked.
Is there a good pattern to support this kind of situation? The obvious (and my current) solution is to track state with an internal bool, check against that bool in every function that might be invoked, and load that way. But that requires code duplication of that behavior across all public functions, and is vulnerable to errors.
I can imagine a single point-of-entry method that then dishes out behaviors based on a client request type etc., but before I go consider going down that road I want to see if there's a commonly accepted approach/pattern that I might not be aware of. I'm doing this in C#, but any insight is appreciated.
If I understood what you want to achieve, you are looking for the Proxy Design Pattern, more specifically, a virtual Proxy.
Refer to http://www.dofactory.com/net/proxy-design-pattern
A small example would be something like:
public abstract class IObjectProvider
{
public abstract IObjectProvider Object{get;}
public abstract void doStuff();
}
public class RealObject : IObjectProvider
{
public RealObject()
{
//Do very complicated and time taking stuff;
}
public override IObjectProvider Object
{
get { return this; }
}
public override void doStuff()
{
//do this stuff that these objects normally do
}
}
public class ObjectProxy : IObjectProvider
{
private IObjectProvider objectInstance = null;
public override IObjectProvider Object
{
get
{
if (objectInstance == null)
objectInstance = new RealObject();
return objectInstance;
}
}
public override void doStuff()
{
if(objectInstance!=null)
objectInstance.doStuff();
}
}
public class SkeletonClass
{
public IObjectProvider Proxy1 = new ObjectProxy();
public IObjectProvider Proxy2 = new ObjectProxy();
}
static void Main(String[] args)
{
//Objects Not Loaded
SkeletonClass skeleton = new SkeletonClass();
//Proxy1 loads object1 on demand
skeleton.Proxy1.Object.doStuff();
//Proxy2 not loaded object2 until someone needs it
}
Here's an example of dynamic proxy approach.
using System;
using System.Diagnostics;
using Castle.DynamicProxy; //Remember to include a reference, too. It's nugettable package is Castle.Core
namespace ConsoleApp
{
public class ActualClass
{
//Have static instances of two below for performance
private static ProxyGenerator pg = new ProxyGenerator();
private static ActualClassInterceptor interceptor = new ActualClassInterceptor();
//This is how we get ActualClass items that are wrapped in the Dynamic Proxy
public static ActualClass getActualClassInstance()
{
ActualClass instance = new ActualClass();
return pg.CreateClassProxyWithTarget<ActualClass>(instance, interceptor);
}
//Tracking whether init has been called
private bool initialized = false;
//Will be used as evidence of true initialization, i.e. no longer null
private int? someValue = null;
public void Initialize()
{
if (!initialized)
{
//do some initialization here.
someValue = -1; //Will only get set to non-null if we've run this line.
initialized = true;
}
}
//Any methods you want to intercept need to be virtual!
public virtual int replaceValue(int value)
{
//below will blow up, if someValue has not been set to -1 via Initialize();
int oldValue = someValue.Value;
someValue = value;
return oldValue;
}
//block off constructor from public to enforce use of getActualClassInstance
protected ActualClass() { }
}
public class ActualClassInterceptor : ActualClass, IInterceptor
{
public void Intercept(IInvocation invocation)
{
//Call initialize before proceeding to call the intercepted method
//Worth noting that this is the only place we actually call Initialize()
((ActualClass)invocation.InvocationTarget).Initialize();
invocation.Proceed();
}
}
class Program
{
static void Main(string[] args)
{
ActualClass instance1 = ActualClass.getActualClassInstance();
ActualClass instance2 = ActualClass.getActualClassInstance();
int x1 = instance1.replaceValue(41);
int x2 = instance2.replaceValue(42);
int y1 = instance1.replaceValue(82);
Debug.Assert(y1 == 41);
int y2 = instance2.replaceValue(84);
Debug.Assert(y2 == 42);
var read = Console.ReadKey();
}
}
}

WeakReference is dead

I am toying with an event aggregator using a weak reference to the method in my subscriber object I wish to handle the event.
When subscribing the weak reference is created successfully and my subscribers collection is updating accordingly. When I attempt to publish an event however, the weak reference has been cleaned up by the GC. Below is my code:
public class EventAggregator
{
private readonly ConcurrentDictionary<Type, List<Subscriber>> subscribers =
new ConcurrentDictionary<Type, List<Subscriber>>();
public void Subscribe<TMessage>(Action<TMessage> handler)
{
if (handler == null)
{
throw new ArgumentNullException("handler");
}
var messageType = typeof (TMessage);
if (this.subscribers.ContainsKey(messageType))
{
this.subscribers[messageType].Add(new Subscriber(handler));
}
else
{
this.subscribers.TryAdd(messageType, new List<Subscriber> {new Subscriber(handler)});
}
}
public void Publish(object message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
var messageType = message.GetType();
if (!this.subscribers.ContainsKey(messageType))
{
return;
}
var handlers = this.subscribers[messageType];
foreach (var handler in handlers)
{
if (!handler.IsAlive)
{
continue;
}
var actionType = handler.GetType();
var invoke = actionType.GetMethod("Invoke", new[] {messageType});
invoke.Invoke(handler, new[] {message});
}
}
private class Subscriber
{
private readonly WeakReference reference;
public Subscriber(object subscriber)
{
this.reference = new WeakReference(subscriber);
}
public bool IsAlive
{
get
{
return this.reference.IsAlive;
}
}
}
}
I subscribe and publish via:
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
ea.Publish(new SomeEvent { ... });
I am probably doing something very daft, that said I am struggling to see my error.
There are a few issues here (others have mentioned some of them already), but the primary one is that the compiler is creating a new delegate object that no one is holding a strong reference to. The compiler takes
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
and inserts the appropriate delegate conversion, giving effectively:
ea.Subscribe<SomeEvent>(new Action<SomeEvent>(SomeHandlerMethod));
Then later this delegate is collected (there is only your WeakReference to it) and the subscription is hosed.
You also have thread-safety issues (I'm assuming you are using ConcurrentDictionary for this purpose). Specifically the access to both the ConcurrentDictionary and the Lists are not thread-safe at all. The Lists need to be locked and you need to properly use ConcurrentDictionary to make updates. For example, in your current code, it is possible that two separate threads are in the TryAdd block and one of them will fail causing a subscription to be lost.
We can fix these problems, but let me outline the solution. The weak event pattern can be tricky to implement in .Net because of those automatically generated delegate instances. What will do instead is capture the delegate's Target in a WeakReference, if it has one (It may not if it is a static method). Then if the method is an instance method we will construct an equivalent Delegate that has no Target and thus there will be no strong reference.
using System.Collections.Concurrent;
using System.Diagnostics;
public class EventAggregator
{
private readonly ConcurrentDictionary<Type, List<Subscriber>> subscribers =
new ConcurrentDictionary<Type, List<Subscriber>>();
public void Subscribe<TMessage>(Action<TMessage> handler)
{
if (handler == null)
throw new ArgumentNullException("handler");
var messageType = typeof(TMessage);
var handlers = this.subscribers.GetOrAdd(messageType, key => new List<Subscriber>());
lock(handlers)
{
handlers.Add(new Subscriber(handler));
}
}
public void Publish(object message)
{
if (message == null)
throw new ArgumentNullException("message");
var messageType = message.GetType();
List<Subscriber> handlers;
if (this.subscribers.TryGetValue(messageType, out handlers))
{
Subscriber[] tmpHandlers;
lock(handlers)
{
tmpHandlers = handlers.ToArray();
}
foreach (var handler in tmpHandlers)
{
if (!handler.Invoke(message))
{
lock(handlers)
{
handlers.Remove(handler);
}
}
}
}
}
private class Subscriber
{
private readonly WeakReference reference;
private readonly Delegate method;
public Subscriber(Delegate subscriber)
{
var target = subscriber.Target;
if (target != null)
{
// An instance method. Capture the target in a WeakReference.
// Construct a new delegate that does not have a target;
this.reference = new WeakReference(target);
var messageType = subscriber.Method.GetParameters()[0].ParameterType;
var delegateType = typeof(Action<,>).MakeGenericType(target.GetType(), messageType);
this.method = Delegate.CreateDelegate(delegateType, subscriber.Method);
}
else
{
// It is a static method, so there is no associated target.
// Hold a strong reference to the delegate.
this.reference = null;
this.method = subscriber;
}
Debug.Assert(this.method.Target == null, "The delegate has a strong reference to the target.");
}
public bool IsAlive
{
get
{
// If the reference is null it was a Static method
// and therefore is always "Alive".
if (this.reference == null)
return true;
return this.reference.IsAlive;
}
}
public bool Invoke(object message)
{
object target = null;
if (reference != null)
target = reference.Target;
if (!IsAlive)
return false;
if (target != null)
{
this.method.DynamicInvoke(target, message);
}
else
{
this.method.DynamicInvoke(message);
}
return true;
}
}
}
And a test program:
public class Program
{
public static void Main(string[] args)
{
var agg = new EventAggregator();
var test = new Test();
agg.Subscribe<Message>(test.Handler);
agg.Subscribe<Message>(StaticHandler);
agg.Publish(new Message() { Data = "Start test" });
GC.KeepAlive(test);
for(int i = 0; i < 10; i++)
{
byte[] b = new byte[1000000]; // allocate some memory
agg.Publish(new Message() { Data = i.ToString() });
Console.WriteLine(GC.CollectionCount(2));
GC.KeepAlive(b); // force the allocator to allocate b (if not in Debug).
}
GC.Collect();
agg.Publish(new Message() { Data = "End test" });
}
private static void StaticHandler(Message m)
{
Console.WriteLine("Static Handler: {0}", m.Data);
}
}
public class Test
{
public void Handler(Message m)
{
Console.WriteLine("Instance Handler: {0}", m.Data);
}
}
public class Message
{
public string Data { get; set; }
}
The delegate object that wraps your SomeHandlerMethod behind the scenes is probably garbage collected between Subscribe and Publish.
Try the following:
Action<SomeEvent> action = SomeHandlerMethod;
ea.Subscribe<SomeEvent>(SomeHandlerMethod);
ea.Publish(new SomeEvent { ... });
GC.KeepAlive(action);
Perhaps the old syntax is a bit clearer in this case:
Action<SomeEvent> action = new Action<SomeEvent>(SomeHandlerMethod);
Another thing to watch out for if your code is multithreaded is the race condition where a subscribed event might not be added (TryAdd can return false).
As for a solution, see atomaras answer:
public void Subscribe<TMessage>(IHandle<TMessage> handler)
{
[...]
public interface IHandler<T>
{
Handle(T event);
}
Or:
public void Subscribe<TMessage>(Action<TMessage> handler)
{
[...]
object targetObject = handler.Target;
MethodInfo method = handler.Method;
new Subscriber(targetObject, method)
[...]
subscriber.method.Invoke(subscriber.object, new object[]{message});
I don't know if the reflection MethodInfo object could be stored in a WeakReference, i.e. if it is temporary or not, and if it's stored strongly referenced whether or not it'll hold on to the assembly containing the Type (if we're talking about a dll-plugin)...
You are passing in an instance of an Action that noone keeps a strong reference to so it's immediatelly available for Garbage Collection. Your action does hold a strong reference to your instance with the method though (if it's not static).
What you can do if you want to maintain the same API signature (you have the option of passing in an IHandle interface also if you want) is change the Subscribe parameter to be an Expression, parse it and locate the instance of the Target object of the Action and keep a WeakReference to that instead.
See here on how to do it Action delegate. How to get the instance that call the method

How do I run code before every test run in MSpec?

I'm trying to run some initialization code before a test. I've tried the suggestions in other questions, but it doesn't seem to work. My domain model raises events via the following class:
public static class DomainEvents
{
private static readonly object #lock = new object();
private static Action<IDomainEvent> raiseEvent;
public static void Raise<TEvent>(TEvent #event) where TEvent : class, IDomainEvent
{
// omitted for brevity
}
public static void RegisterEventPublisher(Action<IDomainEvent> eventPublisher)
{
lock (#lock)
{
raiseEvent = eventPublisher;
}
}
}
For testing purposes I would like to capture these events in a static list. What is the best way of doing this?
Update
The problem was caused by the order in which the tests are run (which as Alexander points out below, is not guaranteed). In one of my specs I had registered a mock event publisher. The fact that the spec would often run in different orders meant that a) to begin with I didn't know I had the issue (the "problem" spec always ran last) and b) Once I started having the issue, the number of failing tests would often vary between runs (making it even more confusing).
The lesson learned - clean up any static resources after each context has run. You can do this by implementing ICleanupAfterEveryContextInAssembly.
Maybe I'm misunderstanding the issue, but the basic pattern is:
public class WhenSomeDomainEventIsRaised
{
private IList<IDomainEvent> EventsRaised = new List<IDomainEvent>();
Establish context = () =>
{
// subscribe to events; when raised, add to EventsRaised list
}
}
If you want to do this for all tests or a subset of tests:
public abstract class DomainSpecification
{
protected IList<IDomainEvent> EventsRaised = new List<IDomainEvent>();
Establish context = () =>
{
// subscribe to events; when raised, add to EventsRaised list
}
}
You can have all specs that need this behaviour inherit from this class, and MSpec will take care of running all Establish blocks along the inheritance hierarchy.
This works for me:
using System;
using System.Collections.Generic;
using Machine.Specifications;
namespace AssemblyContextSpecs
{
public static class DomainEvents
{
static readonly object #lock = new object();
static Action<IDomainEvent> raiseEvent;
public static void Raise<TEvent>(TEvent #event) where TEvent : class, IDomainEvent
{
raiseEvent(#event);
}
public static void RegisterEventPublisher(Action<IDomainEvent> eventPublisher)
{
lock (#lock)
{
raiseEvent = eventPublisher;
}
}
}
public interface IDomainEvent
{
}
class FooEvent : IDomainEvent
{
}
public class DomainEventsContext : IAssemblyContext
{
internal static IList<IDomainEvent> Events = new List<IDomainEvent>();
public void OnAssemblyStart()
{
DomainEvents.RegisterEventPublisher(x => Events.Add(x));
}
public void OnAssemblyComplete()
{
}
}
public class When_a_domain_event_is_raised
{
Because of = () => DomainEvents.Raise(new FooEvent());
It should_capture_the_event =
() => DomainEventsContext.Events.ShouldContain(x => x.GetType() == typeof(FooEvent));
}
}
Shouldn't RegisterEventPublisher rather be RegisterEventSubscriber?

How to store Actions that do not prevent garbage collection of variables they use in scope

I'm trying to fix a garbage collection problem of a MVVM application which uses the following model of Undo stack.
The example is very minimalistic and real world code is much different, uses a factory class of undo lists per ViewModel instead of a single undolist but is representative:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Linq;
namespace ConsoleApplication9
{
public class UndoList
{
public bool IsUndoing { get; set; }
private Stack<Action> _undo = new Stack<Action>();
public Stack<Action> Undo
{
get { return _undo; }
set { _undo = value; }
}
private static UndoList _instance;
// singleton of the undo stack
public static UndoList Instance
{
get
{
if (_instance == null)
{
_instance = new UndoList();
}
return _instance;
}
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// execute the last undo operation
public void Undo()
{
UndoList.Instance.IsUndoing = true;
var action = UndoList.Instance.Undo.Pop();
action();
UndoList.Instance.IsUndoing = false;
}
// push an action into the undo stack
public void AddUndo(Action action)
{
if (UndoList.Instance.IsUndoing) return;
UndoList.Instance.Undo.Push(action);
}
// create push an action into the undo stack that resets a property value
public void AddUndo(string propertyName, object oldValue)
{
if (UndoList.Instance.IsUndoing) return;
var property = this.GetType().GetProperties().First(p => p.Name == propertyName);
Action action = () =>
{
property.SetValue(this, oldValue, null);
};
UndoList.Instance.Undo.Push(action);
}
}
public class TestModel : ViewModel
{
private bool _testProperty;
public bool TestProperty
{
get
{
return _testProperty;
}
set
{
base.AddUndo("TestProperty", _testProperty);
_testProperty = value;
}
}
// mock property indicating if a business action has been done for test
private bool _hasBusinessActionBeenDone;
public bool HasBusinessActionBeenDone
{
get
{
return _hasBusinessActionBeenDone;
}
set
{
_hasBusinessActionBeenDone = value;
}
}
public void DoBusinessAction()
{
AddUndo(() => { inverseBusinessAction(); });
businessAction();
}
private void businessAction()
{
// using fake property for brevity of example
this.HasBusinessActionBeenDone = true;
}
private void inverseBusinessAction()
{
// using fake property for brevity of example
this.HasBusinessActionBeenDone = false;
}
}
class Program
{
static void Test()
{
var vm = new TestModel();
// test undo of property
vm.TestProperty = true;
vm.Undo();
Debug.Assert(vm.TestProperty == false);
// test undo of business action
vm.DoBusinessAction();
vm.Undo();
Debug.Assert(vm.HasBusinessActionBeenDone == false);
// do it once more without Undo, so the undo stack has something
vm.DoBusinessAction();
}
static void Main(string[] args)
{
Program.Test();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
// at this point UndoList.Instance.Undo
// contains an Action which references the TestModel
// which will never be collected...
// in real world code knowing when to clear this is a problem
// because it is a singleton factory class for undolists per viewmodel type
// ideally would be to clear the list when there are no more references
// to the viewmodel type in question, but the Actions in the list prevent that
}
}
}
You see that when any viewModel goes out of scope the actions in the UndoList keep references to them. The real code groups various viewmodels into grouped undolists (viewModels that contain child viewmodels share the same undo stack), so it is difficult to know when and where to put the clearing.
I was wondering if there is some method to make those actions expire if they are the only one keeping references to the variables inside them?
Suggestions welcome!
I've got a solution for you. I don't like the use of the UndoList as a singleton, but I've kept it to provide you with a direct answer to your question. In practice I wouldn't use a singleton.
Now, you will find it very difficult to avoid capturing references to your view models in your actions. It would make your code very ugly if you tried. The best approach is to make your view models implement IDisposable and make sure that you dispose of them when they go out of scope. Remember that the garbage collector never calls Dispose so you must.
Using IDisposable is the standard model for cleaning up when an
instance is no longer needed.
So the first thing to define is a helper class that executes an action when it is disposed.
public sealed class AnonymousDisposable : IDisposable
{
private readonly Action _dispose;
private int _isDisposed;
public AnonymousDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
if (Interlocked.Exchange(ref _isDisposed, 1) == 0)
{
_dispose();
}
}
}
Now I can write things like this to remove elements from lists:
var disposable = new AnonymousDisposable(() => list.Remove(item));
Later, when I call disposable.Dispose() the item is removed from the list.
Now here's your code re-implemented.
I've changed UndoList to be a static class, not a singleton. You can change it back if need be.
public static class UndoList
{
public static bool IsUndoing { get; private set; }
private static List<Action> _undos = new List<Action>();
public static IDisposable AddUndo(Action action)
{
var disposable = (IDisposable)null;
if (!IsUndoing)
{
disposable = new AnonymousDisposable(() => _undos.Remove(action));
_undos.Add(action);
}
return disposable ?? new AnonymousDisposable(() => { });
}
public static bool Undo()
{
IsUndoing = true;
var result = _undos.Count > 0;
if (result)
{
var action = _undos[_undos.Count - 1];
_undos.Remove(action);
action();
}
IsUndoing = false;
return result;
}
}
You'll notice that I've replaced the stack with a list. I did that because I need to remove items from inside the list.
Also, you can see that AddUndo now returns an IDisposable. Calling code needs to keep the return disposable and call Dispose when it wants to remove the action from the list.
I've also internalized the Undo action. It didn't make sense to have it in the view model. Calling Undo effectively pops the top item off of the list and executes the action and returns true. However, if the list is empty it returns false. You can use this for testing purposes.
The ViewModel class now looks like this:
public class ViewModel : IDisposable, INotifyPropertyChanged
{
public ViewModel()
{
_disposables = new List<IDisposable>();
_disposable = new AnonymousDisposable(() =>
_disposables.ForEach(d => d.Dispose()));
}
private readonly List<IDisposable> _disposables;
private readonly IDisposable _disposable;
public void Dispose()
{
_disposable.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void AddUndo(Action action)
{ ... }
protected void SetUndoableValue<T>(Action<T> action, T newValue, T oldValue)
{ ... }
}
It implements IDisposable and internally, keeps track of a list of disposables and an anonymous disposable that will dispose of the items in the list when the view model itself is disposed of. Whew! A mouthful, but I hope that makes sense.
The AddUndo method body is this:
protected void AddUndo(Action action)
{
var disposable = (IDisposable)null;
Action inner = () =>
{
_disposables.Remove(disposable);
action();
};
disposable = UndoList.AddUndo(inner);
_disposables.Add(disposable);
}
Internally it calls UndoList.AddUndo passing in an action that will remove the returned IDisposable from the view model's list of undo actions when UndoList.Undo() is called - as well as, importantly, actually executing the action.
So this means that when the view model is disposed all of its outstanding undo actions are removed from the undo list and when Undo is called the associated disposable is removed from the view model. And this ensures that you are not keeping references to the view model when it is disposed of.
I created a helper function called SetUndoableValue that replaced your void AddUndo(string propertyName, object oldValue) method which wasn't strongly-typed and could cause you to have run-time errors.
protected void SetUndoableValue<T>(Action<T> action, T newValue, T oldValue)
{
this.AddUndo(() => action(oldValue));
action(newValue);
}
I made both of these methods protected as public seemed too promiscuous.
The TestModel is more-or-less the same:
public class TestModel : ViewModel
{
private bool _testProperty;
public bool TestProperty
{
get { return _testProperty; }
set
{
this.SetUndoableValue(v => _testProperty = v, value, _testProperty);
}
}
public bool HasBusinessActionBeenDone { get; set; }
public void DoBusinessAction()
{
this.AddUndo(this.inverseBusinessAction);
businessAction();
}
private void businessAction()
{
this.HasBusinessActionBeenDone = true;
}
private void inverseBusinessAction()
{
this.HasBusinessActionBeenDone = false;
}
}
And finally, here's the code that tests the UndoList functions correctly:
using (var vm = new TestModel())
{
Debug.Assert(UndoList.Undo() == false);
vm.TestProperty = true;
Debug.Assert(UndoList.Undo() == true);
Debug.Assert(UndoList.Undo() == false);
Debug.Assert(vm.TestProperty == false);
vm.DoBusinessAction();
Debug.Assert(UndoList.Undo() == true);
Debug.Assert(vm.HasBusinessActionBeenDone == false);
vm.DoBusinessAction();
}
Debug.Assert(UndoList.Undo() == false);
Please let me know if I can provide any more detail on anything.
If you can't clean it up any other way you could use WeakReference to hold property, but I think there would be other issues because this would still cause a Action instance to exist with a null reference attached to it.
As a quick look I would be more inclined to use the singleton to hold a registration to the model and let the model manage a instance list of all the undo actions attached to it. When the model goes out of scope call a clean-up method on it or implement a IDisposable type interface on it may if this fits. However depending on the implementation you may not need the singleton anyway.

Categories