Click here to Skip to main content
15,880,392 members
Articles / Desktop Programming / WPF

WPF Control Factory

Rate me:
Please Sign up or sign in to vote.
4.25/5 (7 votes)
20 Apr 2010CPOL6 min read 37.6K   418   16  
This article explains some advantages and disadvantages of factories, and shows one to use for generating WPF Controls.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Pfz.Extensions.DictionaryExtensions;
using Pfz.Extensions.MonitorLockExtensions;

namespace Pfz.Extensions.TypeExtensions
{
	/// <summary>
	/// Adds some methods to the Type class so you can discover the
	/// sub-types easily.
	/// </summary>
	public static class PfzTypeExtensions
	{
		#region GetDirectSubClasses
			private static Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>> fSubClasses = new Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>>();
			/// <summary>
			/// Gets the sub-classes of the specific type, in the specific assembly.
			/// </summary>
			public static ReadOnlyCollection<Type> GetDirectSubClasses(this Type type, Assembly inAssembly)
			{
				ReadOnlyCollection<Type> result = null;
				KeyValuePair<Type, Assembly> pair = new KeyValuePair<Type,Assembly>(type, inAssembly);
				
				fSubClasses.LockWithTimeout
				(
					delegate
					{
						if (!fSubClasses.TryGetValue(pair, out result))
						{
							List<Type> list = new List<Type>();
							foreach(Type possibleSubType in inAssembly.GetTypes())
								if (possibleSubType.BaseType == type)
									list.Add(possibleSubType);
							
							result = new ReadOnlyCollection<Type>(list.ToArray());
							fSubClasses.Add(pair, result);
						}
					}
				);
				
				return result;
			}
		#endregion
		#region GetSubClassesRecursive
			private static Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>> fSubClassesRecursive = new Dictionary<KeyValuePair<Type, Assembly>, ReadOnlyCollection<Type>>();
			/// <summary>
			/// Gets the sub-classes of the specific type, in the specific assembly.
			/// </summary>
			public static ReadOnlyCollection<Type> GetSubClassesRecursive(this Type type, Assembly inAssembly)
			{
				ReadOnlyCollection<Type> result = null;
				KeyValuePair<Type, Assembly> pair = new KeyValuePair<Type,Assembly>(type, inAssembly);
				fSubClassesRecursive.LockWithTimeout
				(
					delegate
					{
						if (!fSubClassesRecursive.TryGetValue(pair, out result))
						{
							List<Type> list = new List<Type>();
							foreach(Type possibleSubType in inAssembly.GetTypes())
								if (possibleSubType != type && type.IsAssignableFrom(possibleSubType))
									list.Add(possibleSubType);
							
							result = new ReadOnlyCollection<Type>(list.ToArray());
							fSubClassesRecursive.Add(pair, result);
						}
					}
				);
				
				return result;
			}
		#endregion
		
		#region GetReferencedAssemblies
			/// <summary>
			/// Gets all assemblies that must be referenced if you want to
			/// use everything from the given type. Useful for CodeDOM.
			/// </summary>
			/// <param name="type">The type to get all referenced assemblies.</param>
			/// <returns>A hashset with all referenced assemblies.</returns>
			public static HashSet<Assembly> GetReferencedAssemblies(this Type type)
			{
				HashSet<Type> referencedTypes = new HashSet<Type>();
				HashSet<Assembly> referencedAssemblies = new HashSet<Assembly>();
				
				AddReferencedAssemblies(type, referencedAssemblies, referencedTypes);
				
				return referencedAssemblies;
			}
			
			/// <summary>
			/// Gets all assemblies that must be referenced if you want to
			/// use everything from the given types. Useful for CodeDOM.
			/// </summary>
			/// <param name="types">A collection of all needed types.</param>
			/// <returns>A HashSet of all referenced assemblies.</returns>
			public static HashSet<Assembly> GetReferencedAssemblies(this IEnumerable<Type> types)
			{
				HashSet<Type> processedTypes = new HashSet<Type>();
				HashSet<Assembly> processedAssemblies = new HashSet<Assembly>();
				
				foreach(Type type in types)
					AddReferencedAssemblies(type, processedAssemblies, processedTypes);
				
				return processedAssemblies;
			}
		#endregion
		#region AddReferencedAssemblies
			/// <summary>
			/// Adds all the referenced types/assemblies into the given hashsets.
			/// This is almost an internal implementation, but if you need to add
			/// many references but do not have a collection previouly created, you
			/// must use this method for performance reasons.
			/// </summary>
			/// <param name="type">The type to get all the referencies.</param>
			/// <param name="alreadyAddedAssemblies">A hashset, that is used to add the new referenced assemblies.</param>
			/// <param name="alreadyAddedTypes">A hashset, that is used to add the new referenced types and to avoid reprocessing them.</param>
			public static void AddReferencedAssemblies(this Type type, HashSet<Assembly> alreadyAddedAssemblies, HashSet<Type> alreadyAddedTypes)
			{
				if (type == null)
					return;
				
				// returns if this type was already processed.
				if (!alreadyAddedTypes.Add(type))
					return;
					
				alreadyAddedAssemblies.Add(type.Assembly);
				
				AddReferencedAssemblies(type.BaseType, alreadyAddedAssemblies, alreadyAddedTypes);
				AddReferencedAssemblies(type.DeclaringType, alreadyAddedAssemblies, alreadyAddedTypes);
				
				foreach(Type interfaceType in type.GetInterfaces())
					AddReferencedAssemblies(interfaceType, alreadyAddedAssemblies, alreadyAddedTypes);
					
				foreach(FieldInfo field in type.GetFields())
					AddReferencedAssemblies(field.FieldType, alreadyAddedAssemblies, alreadyAddedTypes);
				
				foreach(MethodInfo method in type.GetMethods())
				{
					AddReferencedAssemblies(method.ReturnType, alreadyAddedAssemblies, alreadyAddedTypes);
					
					foreach(ParameterInfo parameter in method.GetParameters())
						AddReferencedAssemblies(parameter.ParameterType, alreadyAddedAssemblies, alreadyAddedTypes);
				}

				foreach(PropertyInfo property in type.GetProperties())
				{
					AddReferencedAssemblies(property.PropertyType, alreadyAddedAssemblies, alreadyAddedTypes);
					
					foreach(ParameterInfo parameter in property.GetIndexParameters())
						AddReferencedAssemblies(parameter.ParameterType, alreadyAddedAssemblies, alreadyAddedTypes);
				}
				
				foreach(EventInfo eventInfo in type.GetEvents())
					AddReferencedAssemblies(eventInfo.EventHandlerType, alreadyAddedAssemblies, alreadyAddedTypes);
			}
		#endregion
		
		#region GetOrderedInterfaces
			private static Dictionary<Type, ReadOnlyCollection<Type>> fOrderedInterfaces = new Dictionary<Type, ReadOnlyCollection<Type>>();
			
			/// <summary>
			/// Gets the interfaces from this type ordered from the most "new" to the most
			/// "old" in the base types. Note that 2 or more interfaces added at the same
			/// "level" will not have an specific order.
			/// </summary>
			public static ReadOnlyCollection<Type> GetOrderedInterfaces(this Type type)
			{
				if (type == null)
					throw new ArgumentNullException("type");
			
				ReadOnlyCollection<Type> result;
				lock(fOrderedInterfaces)
					result = fOrderedInterfaces.GetValueOrDefault(type);
					
				if (result != null)
					return result;
				
				List<Type> orderedInterfacesList = new List<Type>();
				HashSet<Type> allInterfaces = new HashSet<Type>(type.GetInterfaces());
				HashSet<Type> interfacesToRemove = new HashSet<Type>();
				while(allInterfaces.Count > 0)
				{
					interfacesToRemove.Clear();
					
					foreach(var interfaceType in allInterfaces)
						foreach(var interfaceToRemove in interfaceType.GetInterfaces())
							interfacesToRemove.Add(interfaceToRemove);
					
					HashSet<Type> copy = new HashSet<Type>(allInterfaces);
					foreach(var interfaceType in interfacesToRemove)
						copy.Remove(interfaceType);
						
					foreach(var interfaceType in copy)
					{
						orderedInterfacesList.Add(interfaceType);
						allInterfaces.Remove(interfaceType);
					}
				}
				
				var orderedInterfacesArray = orderedInterfacesList.ToArray();
				result = new ReadOnlyCollection<Type>(orderedInterfacesArray);
				lock(fOrderedInterfaces)
					fOrderedInterfaces[type] = result;
				
				return result;
			}
		#endregion
		
		#region GetInterfaceProperties
			/// <summary>
			/// If this type is an interface, gets all the properties from this
			/// type to it's base interfaces.
			/// If this is not an interface, uses the custom GetProperty.
			/// </summary>
			public static IEnumerable<PropertyInfo> GetInterfaceProperties(this Type type)
			{
				if (type == null)
					throw new ArgumentNullException("type");
					
				foreach(var property in type.GetProperties())
					yield return property;

				if (type.IsInterface)
					foreach(var interfaceType in type.GetOrderedInterfaces())
						foreach(var propertyInfo in interfaceType.GetProperties())
							yield return propertyInfo;
			}
		#endregion
		#region GetInterfaceProperties
			/// <summary>
			/// If this type is an interface, gets all the properties from this
			/// it's base interfaces to this interface.
			/// If this is not an interface, uses the custom GetProperty.
			/// </summary>
			public static IEnumerable<PropertyInfo> GetInterfacePropertiesFromBase(this Type type)
			{
				if (type == null)
					throw new ArgumentNullException("type");
					
				if (type.IsInterface)
				{
					var interfaceTypes = type.GetOrderedInterfaces();
					for (int i=interfaceTypes.Count-1; i>=0; i--)
					{
						Type interfaceType = interfaceTypes[i];

						foreach(var propertyInfo in interfaceType.GetProperties())
							yield return propertyInfo;
					}
				}

				foreach(var property in type.GetProperties())
					yield return property;
			}
		#endregion
		#region TryGetInterfaceProperty
			/// <summary>
			/// Tries to get a property by it's name.
			/// If this is an interface, it also looks all the base interfaces to find
			/// the property.
			/// </summary>
			public static PropertyInfo TryGetInterfaceProperty(this Type type, string propertyName)
			{
				if (type == null)
					throw new ArgumentNullException("type");
				
				if (propertyName == null)
					throw new ArgumentNullException("propertyName");
				
				var result = type.GetProperty(propertyName);
				if (result != null)
					return result;
				
				if (type.IsInterface)
				{
					foreach(var interfaceType in type.GetOrderedInterfaces())
					{
						result = interfaceType.GetProperty(propertyName);
						if (result != null)
							return result;
					}
				}
				
				return null;
			}
		#endregion
		#region GetInterfaceProperty
			/// <summary>
			/// Gets a property by it's name.
			/// If this type is an interface, search in it's base interfaces.
			/// Throws an exception if no such property is found.
			/// </summary>
			public static PropertyInfo GetInterfaceProperty(this Type type, string propertyName)
			{
				PropertyInfo result = TryGetInterfaceProperty(type, propertyName);
				
				if (result == null)
					throw new ArgumentException("Property \"" + propertyName + "\" was not found in type " + type.FullName + ".");
				
				return 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