Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

WPF Control Factory

, 20 Apr 2010
This article explains some advantages and disadvantages of factories, and shows one to use for generating WPF Controls.
WpfControlsAndSample.zip
WpfControlsAndSample
Pfz.ClassLibraries
Pfz
Caching
Collections
DataTypes
Extensions
Pfz.snk
Properties
Ranges
Threading
Pfz.WpfControls
BoundGridParts
Extensions
Pfz.WpfControls.snk
Properties
TypeConverters
WpfControlFactorySample
bin
Debug
Pfz.dll
Pfz.WpfControls.dll
WpfControlFactorySample.exe
Properties
Settings.settings
WpfControlFactorySample.suo
using System;
using System.Collections.Generic;
using System.Threading;
using Pfz.Extensions.ReaderWriterLockExtensions;

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()
			{
				try
				{
					fLock.TryWriteLock
					(
						delegate
						{
							var oldDictionary = fDictionary;
							var newDictionary = 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)
									newDictionary.Add(pair.Key, pair.Value);
							
							fDictionary = newDictionary;
						}
					);
				}
				catch
				{
				}
			}
		#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 = null;

				int collectionCount = GC.CollectionCount(GC.MaxGeneration);
				
				fLock.ReadLock
				(
					() => fDictionary.TryGetValue(parameter, out item)
				);
				
				if (item != null)
				{
					item.CollectionCount = collectionCount;
					return item.Result;
				}
				
				TResult result = default(TResult);
				
				fLock.UpgradeableLock
				(
					delegate
					{
						// as we released the lock, we need to check-again.
						if (fDictionary.TryGetValue(parameter, out item))
						{
							item.CollectionCount = collectionCount;
							result = item.Result;
							return;
						}
						
						item = new Item();
						item.CollectionCount = collectionCount;
						item.Result = DoExecute(parameter);
						
						fLock.WriteLock
						(
							delegate
							{
								fDictionary.Add(parameter, item);
								result = item.Result;
							}
						);
					}
				);
				
				return result;
			}
		#endregion
		
		#region Clear
			/// <summary>
			/// Clears the cache.
			/// </summary>
			protected void Clear()
			{
				fLock.WriteLock
				(
					() => fDictionary.Clear()
				);
			}
		#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.WriteLock
				(
					() => fDictionary.Add(parameter, item)
				);
			}
		#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.WriteLock
				(
					() => fDictionary[parameter] = item
				);
			}
		#endregion
		#region Remove
			/// <summary>
			/// Removes a cached result.
			/// </summary>
			protected bool Remove(TParam parameter)
			{
				bool result = false;
				
				fLock.WriteLock
				(
					() => result = fDictionary.Remove(parameter)
				);
				
				return result;
			}
		#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)

Share

About the Author

Paulo Zemek
Architect
Canada Canada
I started to program computers when I was 11 years old, as a hobbist, 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 they 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
Microsoft MVP 2013

| Advertise | Privacy | Mobile
Web02 | 2.8.140922.1 | Last Updated 20 Apr 2010
Article Copyright 2010 by Paulo Zemek
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid