Click here to Skip to main content
15,895,667 members
Articles / Programming Languages / Visual Basic 10

Having fun with custom collections!

Rate me:
Please Sign up or sign in to vote.
4.91/5 (71 votes)
14 Oct 2011CPOL44 min read 187.4K   2.9K   121  
Creating custom collections from IEnumerable(T) to IDictionary(T) and everything in between!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MagicStove
{
	/// <summary>
	/// Represents a collection of ingredients where the (type of) an ingredient is the key
	/// and the amount of that ingredient the value.
	/// </summary>
	public class Ingredients : IDictionary<IIngredient, int>
	{

		private const String ErrorMessage = "Adding or removing the specified amount of ingredients will result in a negative. Ingredients not added.";

		// The innerDictionary has a ChangeableDictionaryValue(Of Integer) as value
		// because the value of a KeyValuePair cannot be changed once it is assigned.
		private Dictionary<IIngredient, ChangeableDictionaryValue<int>> _innerDictionary;

		/// <summary>
		/// Instantiates a new cult with no members.
		/// </summary>
		public Ingredients()
			: base()
		{
			_innerDictionary = new Dictionary<IIngredient, ChangeableDictionaryValue<int>>();
		}

		/// <summary>
		/// Returns a KeyValuePair of the _innerDictionary given a key.
		/// The KeyValuePair is selected by type of the key rather than its spot in memory.
		/// </summary>
		/// <param name="key">An ingredient to look for.</param>
		/// <returns>A KeyValuePair where the type of the key is the same as the type of the key parameter.</returns>
		private KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> GetPairByKeyType(IIngredient key)
		{
			KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> pair = _innerDictionary.Where(p => p.Key.GetType() == key.GetType()).FirstOrDefault();
			if (pair.Key == null)
			{
				throw new KeyNotFoundException();
			}
			else
			{
				return pair;
			}
		}

		/// <summary>
		/// Adds the specified amount of the ingredient.
		/// </summary>
		/// <param name="key">The ingredient to add.</param>
		/// <param name="value">The amount to add of the specified ingredient.</param>
		public virtual void Add(IIngredient key, int value)
		{
			// Cast to call the explicitly implemented Add Method.
			(this as ICollection<KeyValuePair<IIngredient, int>>).Add(new KeyValuePair<IIngredient, int>(key, value));
		}

		/// <summary>
		/// Adds the KeyValuePair to the _innerDictionary.
		/// </summary>
		/// <param name="item">The KeyValuePair to add.</param>
		void ICollection<KeyValuePair<IIngredient, int>>.Add(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
		{
			// If the key already exists then do not add a new KeyValuePair.
			// Add the specified amount to the already existing KeyValuePair instead.
			if (this.ContainsKey(item.Key))
			{
				if (this[item.Key] + item.Value < 0)
				{
					throw new InvalidOperationException(ErrorMessage);
				}
				else
				{
					this[item.Key] += item.Value;
				}
			}
			else
			{
				if (item.Value < 0)
				{
					throw new InvalidOperationException(ErrorMessage);
				}
				else
				{
					_innerDictionary.Add(item.Key, new ChangeableDictionaryValue<int>(item.Value));
				}
			}
		}

		/// <summary>
		/// Removes an ingredient.
		/// </summary>
		/// <param name="key">The ingredient to remove.</param>
		/// <returns>True if the key was found and successfully removed.</returns>
		public virtual bool Remove(IIngredient key)
		{
			if (this.ContainsKey(key))
			{
				return _innerDictionary.Remove(GetPairByKeyType(key).Key);
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Removes the specified value from the ingredient of the key.
		/// Removes by checking the type of the key rather than pointer.
		/// </summary>
		/// <param name="item">The KeyValuePair to remove.</param>
		/// <returns>True if the key was found and successfully removed.</returns>
		bool ICollection<KeyValuePair<IIngredient, int>>.Remove(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
		{
			this.Add(item.Key, -item.Value);
			return true;
		}

		/// <summary>
		/// Clears the dictionary of ingredients.
		/// </summary>
		public void Clear()
		{
			_innerDictionary.Clear();
		}

		/// <summary>
		/// Checks if the specified amount of the key ingredient is present in the dictionary.
		/// </summary>
		/// <param name="ingredient">The key ingredient to check for presence.</param>
		/// <param name="amount">The amount of the ingredient to check for presence.</param>
		/// <returns>True if the specified amount of the given key ingredient is present in the dictionary.</returns>
		public bool Contains(IIngredient ingredient, int amount)
		{
			return this.Contains(new KeyValuePair<IIngredient, int>(ingredient, amount));
		}

		/// <summary>
		/// Checks if the specified KeyValuePair is present in the dictionary.
		/// Does not check pointer, but if the amount of the specified key type is present.
		/// </summary>
		/// <param name="item">The KeyValuePair containing the key ingredient and amount to check.</param>
		/// <returns>True if the specified amount of the given key ingredient is present in the dictionary.</returns>
		bool ICollection<KeyValuePair<IIngredient, int>>.Contains(System.Collections.Generic.KeyValuePair<IIngredient, int> item)
		{
			if (this.ContainsKey(item.Key))
			{
				return GetPairByKeyType(item.Key).Value.Value >= item.Value;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Checks if an ingredient with the same type as the specified key is already present in the dictionary.
		/// </summary>
		/// <param name="key">The key ingredient to check for presence.</param>
		/// <returns>True if a type of the specified ingredient is present in the dictionary.</returns>
		public bool ContainsKey(IIngredient key)
		{
			KeyValuePair<IIngredient, ChangeableDictionaryValue<int>> pair = _innerDictionary.Where(p => p.Key.GetType() == key.GetType()).FirstOrDefault();
			if (pair.Key == null)
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		/// <summary>
		/// Gets the amount of an ingredient if the ingredient exists.
		/// </summary>
		/// <param name="key">The ingredient to get the amount of.</param>
		/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter.</param>
		/// <returns>True if the key exists and the value was found.</returns>
		public bool TryGetValue(IIngredient key, out int value)
		{
			if (this.ContainsKey(key))
			{
				value = GetPairByKeyType(key).Value.Value;
				return true;
			}
			else
			{
				value = default(int);
				return false;
			}
		}

		/// <summary>
		/// Gets or sets the amount associated with the specified ingredient.
		/// </summary>
		/// <param name="key">An ingredient to get or set the amount of.</param>
		/// <value>The amount of the ingredient to set.</value>
		/// <returns>The amount of the specified ingredient.</returns>
		public virtual int this[IIngredient key]
		{
			get { return GetPairByKeyType(key).Value.Value; }
			set
			{
				if (value < 0)
				{
					throw new InvalidOperationException(ErrorMessage);
				}
				else
				{
					GetPairByKeyType(key).Value.Value = value;
				}
			}
		}

		/// <summary>
		/// Gets a collection containing all ingredients in the dictionary.
		/// </summary>
		/// <returns>A collection of ingredients.</returns>
		public System.Collections.Generic.ICollection<IIngredient> Keys
		{
			get { return _innerDictionary.Keys; }
		}

		/// <summary>
		/// Gets a collection containing all amounts of ingredients in the dictionary.
		/// </summary>
		/// <returns>A collection of integers.</returns>
		public System.Collections.Generic.ICollection<int> Values
		{
			get
			{
				return InnerDictToIntValue().Values;
			}
		}

		/// <summary>
		/// Gets the amount of unique ingredients in the dictionary.
		/// </summary>
		/// <returns>The amount of unique ingredients in the dictionary.</returns>
		public int Count
		{
			get { return _innerDictionary.Count; }
		}

		/// <summary>
		/// Returns a value that specifies wether the dictionary is read only.
		/// </summary>
		/// <returns>True if the dictionary is read only.</returns>
		public bool IsReadOnly
		{
			get { return false; }
		}

		/// <summary>
		/// Converts the _innerDictionary to a new dictionary which values cannot be changed.
		/// </summary>
		/// <returns>A new dictionary containing all keys and values of the _innerDictionary.</returns>
		private Dictionary<IIngredient, int> InnerDictToIntValue()
		{
			Dictionary<IIngredient, int> dict = new Dictionary<IIngredient, int>();
			// Put all KeyValuePairs of the _innerDictionary in the new dictionary using a one-line LINQ query.
			_innerDictionary.ToList().ForEach(pair => dict.Add(pair.Key, pair.Value.Value));
			return dict;
		}

		/// <summary>
		/// Copies the elements of the Dictionary to an Array, starting at a particular Array index.
		/// </summary>
		/// <param name="array">The one-dimensional Array that is the destination of the elements copied from the Dictionary. The Array must have zero-based indexing.</param>
		/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
		void ICollection<KeyValuePair<IIngredient, int>>.CopyTo(KeyValuePair<IIngredient, int>[] array, int arrayIndex)
		{
			// We do not want to implement this Method ourselves. The _innerDictionary already has it implemented.
			// This is a private Method of the _innerDictionary though, so we first have to cast it to an IDictionary.
			// Remember that the _innerDictionary has a ChangeableValue while we Implement an IDictionary with an Integer as value.
			// First convert the _innerDictionary to a Dictionary with an Integer as value.
			((IDictionary<IIngredient, int>)InnerDictToIntValue()).CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// Returns an Enumerator that iterates through the ingredients and their amounts.
		/// </summary>
		/// <returns>An Enumerator for ingredients.</returns>
		public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<IIngredient, int>> GetEnumerator()
		{
			// First convert the _innerDictionary to a Dictionary having an Integer as value.
			return InnerDictToIntValue().GetEnumerator();
		}

		/// <summary>
		/// Returns an Enumerator that iterates through the ingredients and their amounts.
		/// </summary>
		/// <returns>An Enumerator for ingredients.</returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

	}
}

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
CEO JUUN Software
Netherlands Netherlands
Sander Rossel is a Microsoft certified professional developer with experience and expertise in .NET and .NET Core (C#, ASP.NET, and Entity Framework), SQL Server, Azure, Azure DevOps, JavaScript, MongoDB, and other technologies.

He is the owner of JUUN Software, a company specializing in custom software. JUUN Software uses modern, but proven technologies, such as .NET Core, Azure and Azure DevOps.

You can't miss his books on Amazon and his free e-books on Syncfusion!

He wrote a JavaScript LINQ library, arrgh.js (works in IE8+, Edge, Firefox, Chrome, and probably everything else).

Check out his prize-winning articles on CodeProject as well!

Comments and Discussions