Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Creating a weak event

Rate me:
Please Sign up or sign in to vote.
3.47/5 (9 votes)
8 Apr 2009CPOL1 min read 34.9K   260   14  
This article shows how to use WeakDelegateSet class to create weak events. With weak events, objects can register themselves in events, but are allowed to be collected if the only references for them are such events.
using System;
using System.Collections.Generic;
using System.Threading;

namespace Pfz.Caching
{
	/// <summary>
	/// This class acts as a cache for function evalutations. This is inspired
	/// by the functional and immutable programming model, so calling a function
	/// twice with the same parameter, which will generate the same result, will 
	/// be evaluated only once.
	/// As a cache, such generated results can be garbage collected, but the
	/// cache is able to generate the same value again, given the same
	/// parameter. And, of course, the parameter can be an struct, so this 
	/// enables multi-parameters with a little extra effort.
	/// 
	/// To use this cache, you must override and implement the method Generate.
	/// It is common to create the cache as an inheritor of this class with 
	/// a private constructor, and make it acessible by a static function call.
	/// </summary>
	public abstract class ResultCache<TParam, TResult>
	{
		#region Private fields
			private ReaderWriterLockSlim fLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
			private Dictionary<TParam, Item> fDictionary = new Dictionary<TParam, Item>();
		#endregion
		
		#region Constructor
			/// <summary>
			/// Initializes the functional cache, and register it in the Collected event.
			/// </summary>
			public ResultCache()
			{
				GCUtils.Collected += p_Collected;
			}
		#endregion
		#region p_Collected
			private void p_Collected()
			{
				GCUtils.Collected += p_Collected;
				
				fLock.EnterWriteLock();
				try
				{
					var oldDictionary = fDictionary;
					fDictionary = new Dictionary<TParam, ResultCache<TParam, TResult>.Item>(oldDictionary.Count);
					int lastGeneration = GC.CollectionCount(GC.MaxGeneration)-1;
					foreach(var pair in oldDictionary)
						if (pair.Value.CollectionCount >= lastGeneration)
							fDictionary.Add(pair.Key, pair.Value);
				}
				finally
				{
					fLock.ExitWriteLock();
				}
			}
		#endregion
		
		#region DoExecute
			/// <summary>
			/// You must implement this method to effectivelly execute the function.
			/// Remember that such function must always return the same value considering
			/// the same input values.
			/// </summary>
			/// <param name="parameter">The parameter value.</param>
			/// <returns>Your generated result value.</returns>
			protected abstract TResult DoExecute(TParam parameter);
		#endregion
		#region Execute
			/// <summary>
			/// Evaluates the function, or uses the cached value if this function
			/// was already evaluated with the same parameters.
			/// 
			/// This method is virtual so it is possible to clone the result in cases
			/// where the result is modifiable just before returning it.
			/// As the value is cached, you must guarantee that the value is not modifiable 
			/// or, if it is, at least clone it so the returned value, if modified, will
			/// not destroy the cached result.
			/// </summary>
			/// <param name="parameter">The parameter value.</param>
			/// <returns>Your generated result value.</returns>
			public virtual TResult Execute(TParam parameter)
			{
				Item item;

				int collectionCount = GC.CollectionCount(GC.MaxGeneration);
				fLock.EnterReadLock();
				try
				{
					if (fDictionary.TryGetValue(parameter, out item))
					{
						item.CollectionCount = collectionCount;
						return item.Result;
					}
				}
				finally
				{
					fLock.ExitReadLock();
				}
				
				fLock.EnterUpgradeableReadLock();
				try
				{
					// as we released the lock, we need to check-again.
					if (fDictionary.TryGetValue(parameter, out item))
					{
						item.CollectionCount = collectionCount;
						return item.Result;
					}
					
					item = new Item();
					item.CollectionCount = collectionCount;
					item.Result = DoExecute(parameter);
					
					fLock.EnterWriteLock();
					try
					{
						fDictionary.Add(parameter, item);
						return item.Result;
					}
					finally
					{
						fLock.ExitWriteLock();
					}
				}
				finally
				{
					fLock.ExitUpgradeableReadLock();
				}
			}
		#endregion
		
		#region Clear
			/// <summary>
			/// Clears the cache.
			/// </summary>
			protected void Clear()
			{
				fLock.EnterWriteLock();
				try
				{
					fDictionary.Clear();
				}
				finally
				{
					fLock.ExitWriteLock();
				}
			}
		#endregion
		#region Add
			/// <summary>
			/// Adds a value to the cache. This is here so inherited classes
			/// that know they are generating values valid to be added to the
			/// cache add it. Remember that this method throws an exception
			/// when the value already exists and that Cache in general
			/// must be thread-safe.
			/// </summary>
			protected void Add(TParam parameter, TResult result)
			{
				Item item = new Item();
				item.CollectionCount = GC.CollectionCount(GC.MaxGeneration);
				item.Result = result;
			
				fLock.EnterWriteLock();
				try
				{
					fDictionary.Add(parameter, item);
				}
				finally
				{
					fLock.ExitWriteLock();
				}
			}
		#endregion
		#region AddOrReplace
			/// <summary>
			/// Adds or replaces a value from the cache. This method is here
			/// so inherited classes can replace existing cached values if,
			/// for some reason, such value changes.
			/// If you need to do this, remember that the cache can be used
			/// by many threads and even the cache itself being thread-safe,
			/// you may need your own locking to avoid incoherent states.
			/// </summary>
			protected void AddOrReplace(TParam parameter, TResult result)
			{
				Item item = new Item();
				item.CollectionCount = GC.CollectionCount(GC.MaxGeneration);
				item.Result = result;
			
				fLock.EnterWriteLock();
				try
				{
					fDictionary[parameter] = item;
				}
				finally
				{
					fLock.ExitWriteLock();
				}
			}
		#endregion
		#region Remove
			/// <summary>
			/// Removes a cached result.
			/// </summary>
			protected bool Remove(TParam parameter)
			{
				fLock.EnterWriteLock();
				try
				{
					return fDictionary.Remove(parameter);
				}
				finally
				{
					fLock.ExitWriteLock();
				}
			}
		#endregion
		
		#region Item - Nested class
			private sealed class Item
			{
				internal int CollectionCount;
				internal TResult Result;
			}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions