Click here to Skip to main content
15,888,521 members
Articles / Programming Languages / C# 4.0

ReflectionHelper

Rate me:
Please Sign up or sign in to vote.
4.93/5 (31 votes)
6 Aug 2012CPOL4 min read 72.5K   2.2K   79  
This class makes getting MemberInfos easy, without the use of magic strings (so it is refactoring friendly) and also allows you to create delegates to do fast accesses to those items, much faster than the normal Invoke, GetValue or SetValue methods.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Pfz.Threading;

namespace Pfz.Caching
{
	/// <summary>
	/// A collection that only keeps weak-references to its items.
	/// Ideal if at some point you only need to do a for-each over all the
	/// non-collected items. Use WeakHashSet if you need to remove the items
	/// or call Contains frequently.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	[Serializable]
	public class WeakCollection<T>:
		ReaderWriterSafeDisposable,
		ISerializable,
		ICollection<T>,
		IGarbageCollectionAware
	where
		T: class
	{
		#region Private fields
			private static readonly Comparison<GCHandle> _SortHandlesDelegate = _SortHandles;
			private GCHandle[] _handles;
		#endregion

		#region Constructors
			/// <summary>
			/// Creates an empty weak-list.
			/// </summary>
			public WeakCollection():
				this(32)
			{
			}
			
			/// <summary>
			/// Creates an empty weak-list using the given minCapacity to it.
			/// </summary>
			/// <param name="initialCapacity">The initialCapacity of the list. The default value is 32.</param>
			public WeakCollection(int initialCapacity)
			{
				if (initialCapacity < 1)
					throw new ArgumentOutOfRangeException("initialCapacity", "The initial accepted capacity value is 1.");
					
				_handles = new GCHandle[initialCapacity];
				GCUtils.RegisterForCollectedNotification(this);
			}
		#endregion
		#region Dispose
			/// <summary>
			/// Releases all handles.
			/// </summary>
			protected override void Dispose(bool disposing)
			{
				// if the dispose was called manullay, we must unregister ourselves from the GCUtils.Collected method.
				// we don't need to do this if this object is being collected, as the Collected event is also we and, so,
				// it was already removed from there.
				if (disposing)
					GCUtils.UnregisterFromCollectedNotification(this);

				// here we will free all allocated handles. After all, even if the objects can be collected, the
				// handles must be freed.
				var handles = _handles;
				if (handles != null)
				{
					int count = handles.Length;
					for (int i=0; i<count; i++)
					{
						var handle = handles[i];
						if (handle.IsAllocated)
							handle.Free();
					}

					_handles = null;
				}

				base.Dispose(disposing);
			}
		#endregion
		#region _Collected
			void IGarbageCollectionAware.OnCollected()
			{
				using(DisposeLock.WriteLock())
				{
					if (WasDisposed)
					{
						GCUtils.UnregisterFromCollectedNotification(this);
						return;
					}
						
					int allocated = 0;
						
					int count = Count;
					for(int i=0; i<count; i++)
					{
						var handle = _handles[i];
							
						if (handle.IsAllocated && handle.Target != null)
							allocated ++;
					}
						
					int minCapacity = count / 2;
					if (minCapacity < 32)
						minCapacity = 32;
							
					if (allocated < minCapacity)
						allocated = minCapacity;
						
					if (allocated != _handles.Length)
					{
						var newHandles = new GCHandle[allocated];
						try
						{
						}
						finally
						{
							int newIndex = 0;
							for(int i=0; i<count; i++)
							{
								var handle = _handles[i];
									
								if (!handle.IsAllocated)
									continue;
									
								var target = handle.Target;
								if (target == null)
									handle.Free();
								else
								{
									newHandles[newIndex] = handle;
									newIndex++;
								}
							}
							for(int i=count; i<_handles.Length; i++)
							{
								var handle = _handles[i];
								if (handle.IsAllocated)
									handle.Free();
							}
								
							Count = newIndex;
							_handles = newHandles;
						}
					}
				}
			}
		#endregion
		
		#region Properties
			#region Count
				/// <summary>
				/// Gets an approximate count of the items added.
				/// </summary>
				public int Count { get; private set; }
			#endregion
		#endregion
		#region Methods
			#region Clear
				/// <summary>
				/// Clears all the items in the list.
				/// </summary>
				public void Clear()
				{
					using(DisposeLock.WriteLock())
					{
						CheckUndisposed();
							
						Count = 0;
					}
				}
			#endregion
			#region Add
				/// <summary>
				/// Adds an item to the list.
				/// </summary>
				public void Add(T item)
				{
					if (item == null)
						throw new ArgumentNullException("item");
					
					using(DisposeLock.WriteLock())
					{
						CheckUndisposed();
							
						int count = Count;
						if (count == _handles.Length)
						{
							bool mustReturn = false;
							try
							{
							}
							finally
							{
								Array.Sort(_handles, _SortHandlesDelegate);
								
								for(int i=0; i<count; i++)
								{
									var handle1 = _handles[i];
									if (handle1.Target == null)
									{
										handle1.Target = item;
										Count = i+1;
										mustReturn = true;
										break;
									}
								}
								
								if (!mustReturn)
									Array.Resize(ref _handles, count * 2);
							}
							
							if (mustReturn)
								return;
						}
							
						var handle = _handles[count];
						if (handle.IsAllocated)
							handle.Target = item;
						else
						{
							try
							{
							}
							finally
							{
								_handles[count] = GCHandle.Alloc(item, GCHandleType.Weak);
							}
						}

						Count++;
					}
				}
			#endregion
			#region ToList
				/// <summary>
				/// Gets a strong-list with all the non-collected items present
				/// in this list.
				/// </summary>
				public List<T> ToList()
				{
					List<T> result = new List<T>();
					
					using(DisposeLock.ReadLock())
					{
						CheckUndisposed();
							
						int count = Count;
						for (int i=0; i<count; i++)
						{
							object target = _handles[i].Target;
								
							if (target != null)
								result.Add((T)target);
						}
					}
						
					return result;
				}
			#endregion
			
			#region Contains
				/// <summary>
				/// Returns true if an item exists in the collection, false otherwise.
				/// </summary>
				public bool Contains(T item)
				{
					if (item == null)
						throw new ArgumentNullException("item");
				
					using(DisposeLock.ReadLock())
						return _IndexOf(item) != -1;
				}
			#endregion
			#region _IndexOf
				private int _IndexOf(T item)
				{
					CheckUndisposed();
						
					int count = Count;
					for (int i=0; i<count; i++)
					{
						GCHandle handle = _handles[i];
						if (handle.Target == item)
							return i;
					}
					
					return -1;
				}
			#endregion
			
			#region Remove
				/// <summary>
				/// Tries to remove an item from this list and returns if it was
				/// found and removed. Note that the Count and the following items 
				/// are not automatically updated, as this will only happen in the next
				/// garbage collection.
				/// </summary>
				public bool Remove(T item)
				{
					using(DisposeLock.WriteLock())
					{
						int index = _IndexOf(item);
						if (index != -1)
						{
							_handles[index].Target = null;
							
							if (index == Count-1)
								Count = index;
								
							return true;
						}
					}
					
					return false;
				}
			#endregion
			#region RemoveLast
				/// <summary>
				/// Removes the last item from the list and return it.
				/// It the list is empty, returns null.
				/// </summary>
				public T RemoveLast()
				{
					using(var upgradeableLock = DisposeLock.UpgradeableLock())
					{
						int count = Count;
						for(int i=count-1; i>=0; i--)
						{
							var handle = _handles[i];
							var target = handle.Target;
							if (target != null)
							{
								upgradeableLock.Upgrade();

								handle.Target = null;
								Count = i;
								return (T)target;
							}
						}
					}

					return null;
				}
			#endregion
			
			#region GetEnumerator
				/// <summary>
				/// Gets an enumerator over the non-collected items of this
				/// list.
				/// </summary>
				public IEnumerator<T> GetEnumerator()
				{
					return ToList().GetEnumerator();
				}
			#endregion
			#region _SortHandles
				private static int _SortHandles(GCHandle a, GCHandle b)
				{
					if (a.Target == null)
					{
						if (b.Target == null)
							return 0;
							
						return 1;
					}
					
					if (b.Target == null)
						return -1;
						
					return 0;
				}
			#endregion
		#endregion
		
		#region Interfaces
			#region ISerializable Members
				/// <summary>
				/// Creates the WeakList from the serialization info.
				/// </summary>
				protected WeakCollection(SerializationInfo info, StreamingContext context):
					this(32)
				{
				}
				
				/// <summary>
				/// Creates serialization info.
				/// </summary>
				protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
				{
				}

				void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
				{
					GetObjectData(info, context);
				}
			#endregion
			#region ICollection<T> Members
				void ICollection<T>.CopyTo(T[] array, int arrayIndex)
				{
					ToList().CopyTo(array, arrayIndex);
				}
				bool ICollection<T>.IsReadOnly
				{
					get
					{
						return false;
					}
				}
			#endregion
			#region IEnumerable Members
				IEnumerator IEnumerable.GetEnumerator()
				{
					return GetEnumerator();
				}
			#endregion
		#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