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

WPF Control Factory

, 20 Apr 2010 CPOL
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.MonitorLockExtensions;
using Pfz.Threading;

namespace Pfz.Caching
{
	/// <summary>
	/// Some methods and events to interact with garbage collection. You can 
	/// keep an object alive during the next collection or register to know 
	/// when a collection has just happened. This is useful if you don't use
	/// WeakReferences, but know how to free memory if needed. For example, 
	/// you can do a TrimExcess to your lists to free some memory.
	/// 
	/// Caution: GC.KeepAlive keeps the object alive until that line of code,
	/// while GCUtils.KeepAlive keeps the object alive until the next 
	/// generation.
	/// </summary>
	public static class GCUtils
    {
		#region Constructor
			private static bool fFinished;
			private static ManualResetEvent fCollectedEvent = new ManualResetEvent(false);
			static GCUtils()
			{
				AppDomain current = AppDomain.CurrentDomain;
				current.DomainUnload += new EventHandler(p_DomainUnload);
				current.ProcessExit += new EventHandler(p_ProcessExit);
				new Runner();
				
				Thread thread = new Thread(p_ExecuteCollected);
				thread.Name = "Pfz.GCUtils.Collected executor thread.";
				thread.Start();
			}
		#endregion
		#region Finalization event handles
			private static void p_ProcessExit(object sender, EventArgs e)
			{
				fFinished = true;
				fCollectedEvent.Set();
			}
			private static void p_DomainUnload(object sender, EventArgs e)
			{
				fFinished = true;
				fCollectedEvent.Set();
			}
		#endregion
    
		#region KeepAlive
			private static HashSet<object> fKeepedObjects = new HashSet<object>(fReferenceComparer);
			
			/// <summary>
			/// Keeps an object alive at the next collection. This is useful for WeakReferences as an way
			/// to guarantee that recently used objects will not be immediatelly collected. At the next
			/// generation, you can call KeepAlive again, making the object alive for another generation.
			/// </summary>
			/// <param name="item"></param>
			public static void KeepAlive(object item)
			{
				if (item == null)
					return;
			
				var keepedObjects = fKeepedObjects;
				
				AbortSafe.UnabortableLock
				(
					keepedObjects,
					() => keepedObjects.Add(item)
				);
			}
			
			/// <summary>
			/// Expires an object. Is the opposite of KeepAlive.
			/// </summary>
			/// <param name="item"></param>
			/// <returns>true if the object was in the KeepAlive list, false otherwise.</returns>
			public static bool Expire(object item)
			{
				if (item == null)
					return false;
			
				var keepedObjects = fKeepedObjects;

				bool result = false;
				AbortSafe.UnabortableLock
				(
					keepedObjects,
					() => result = keepedObjects.Remove(item)
				);
				return result;
			}
		#endregion
		#region p_ExecuteCollected
			private static void p_ExecuteCollected()
			{
				var thread = Thread.CurrentThread;
				while(true)
				{
					// we are background while waiting.
					thread.IsBackground = true;
					
					fCollectedEvent.WaitOne();
					
					if (fFinished)
						return;
						
					fCollectedEvent.Reset();

					// but we are not background while running.
					thread.IsBackground = false;
					
					List<WeakDelegateBase> listToRun = null;
					try
					{
						List<List<WeakDelegateBase>> availLater = new List<List<WeakDelegateBase>>();
						AbortSafe.Lock
						(
							fCollectedLock,
							delegate
							{
								var oldCollected = fCollected;
								listToRun = new List<WeakDelegateBase>(oldCollected.Count);
								var newCollected = new Dictionary<int, List<WeakDelegateBase>>(oldCollected.Count);
								foreach(var pair in oldCollected)
								{
									var oldList = pair.Value;
									oldList.TryLockWithTimeout
									(
										delegate
										{
											if (oldList.Count > 0)
											{
												newCollected.Add(pair.Key, oldList);
												availLater.Add(oldList);
											}
										}
									);
								}
								
								fCollected = newCollected;
							}
						);
						
						foreach(var list in availLater)
						{
							AbortSafe.UnabortableLock
							(
								list,
								delegate
								{
									int count = list.Count;
									for(int i=count-1; i>=0; i--)
									{
										WeakDelegateBase weakDelegate = list[i];
										
										if (weakDelegate.Target == null && !weakDelegate.Method.IsStatic)
											list.RemoveAt(i);
										else
											listToRun.Add(weakDelegate);
									}
									
									list.TrimExcess();
								}
							);
						}
					}
					catch
					{
					}
					
					// we use the listToRun instead of a foreach in each list as an way to avoid creating many objects,
					// which could (in very rare circunstances) throw an exception.
					// If an exception was thrown, no problem. If we have any item in
					// list, will try to run such item.
					if (listToRun != null)
					{
						int countListToRun = listToRun.Count;
						for (int i=0; i<countListToRun; i++)
						{
							WeakDelegateBase action = listToRun[i];
							action.Invoke(null);
						}
					}
				}
			}
		#endregion
		
        #region Collected
			private static volatile Dictionary<int, List<WeakDelegateBase>> fCollected = new Dictionary<int, List<WeakDelegateBase>>();
			private static object fCollectedLock = new object();
			
			/// <summary>
			/// This event is called after a GarbageCollection has just finished,
			/// in another thread. As this happens after the collection has finished,
			/// all other threads are running too, so you must guarantee that
			/// your event is thread safe.
			/// </summary>
			public static event Action Collected
			{
				add
				{
					int hashCode = value.GetHashCode();
				
					bool mustReturn = false;
					List<WeakDelegateBase> list = null;
					AbortSafe.UnabortableLock
					(
						fCollectedLock,
						delegate
						{
							var collected = fCollected;
							
							// if there is no item with the same hashCode, we
							// can insert a new one directly.
							if (!collected.TryGetValue(hashCode, out list))
							{
								WeakDelegateBase weakDelegate = new WeakDelegateBase(value);
								list = new List<WeakDelegateBase>(1);
								list.Add(weakDelegate);
								collected.Add(hashCode, list);
								mustReturn = true;
							}
						}
					);
					
					if (mustReturn)
						return;
					
					// ok, an item with the same hashCode exists, so
					// first we check if this item is already in the list.
					// if there is, we simple return.
					AbortSafe.UnabortableLock
					(
						list,
						delegate
						{
							foreach(WeakDelegateBase action in list)
								if (action.Target == value.Target && action.Method == value.Method)
									return;
							
							WeakDelegateBase weakDelegate = new WeakDelegateBase(value);
							list.Add(weakDelegate);
						}
					);
				}
				remove
				{
					int hashCode = value.GetHashCode();
				
					List<WeakDelegateBase> list = null;
					
					AbortSafe.Lock
					(
						fCollectedLock,
						() => fCollected.TryGetValue(hashCode, out list)
					);
					
					if (list == null)
						return;
					
					AbortSafe.Lock
					(
						list,
						delegate
						{
							int count = list.Count;
							for(int i=0; i<count; i++)
							{
								WeakDelegateBase weakDelegate = list[i];
								if (weakDelegate.Method == value.Method && weakDelegate.Target == value.Target)
								{
									AbortSafe.Run(() => list.RemoveAt(i));
									return;
								}
							}
						}
					);
				}
			}
        #endregion
        
        #region Runner - nested class
			private sealed class Runner
			{
				~Runner()
				{
					// If we don't test, we will keep re-registering forever
					// when the application is finishing.
					if (fFinished)
						return;
						
					GC.ReRegisterForFinalize(this);
					
					// no lock is needed as we simple put a new object and don't
					// even try to read the old object.
					fKeepedObjects = new HashSet<object>(fReferenceComparer);
					
					fCollectedEvent.Set();
				}
			}
        #endregion
        #region ReferenceComparer - nested class
			private static readonly ReferenceComparer fReferenceComparer = new ReferenceComparer();
			
			/// <summary>
			/// Class used to compare two references.
			/// They must point to the same instance (not an equal instance) to be
			/// considered equal.
			/// This also helps making comparisons faster for objects that
			/// implement Equals.
			/// </summary>
			private sealed class ReferenceComparer:
				IEqualityComparer<object>
			{
				bool IEqualityComparer<object>.Equals(object x, object y)
				{
					return x == y;
				}
				int IEqualityComparer<object>.GetHashCode(object obj)
				{
					return obj.GetHashCode();
				}
			}
        #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
Engineer Microsoft Corporation
United States United States
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.
 
Now I just started working as a Senior Software Engineer at Microsoft.
 
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-2014

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