Click here to Skip to main content
15,897,891 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.8K   2.9K   121  
Creating custom collections from IEnumerable(T) to IDictionary(T) and everything in between!
''' <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
	Implements IDictionary(Of IIngredient, Integer)

	Private Const ErrorMessage As String = "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 _innerDictionary As Dictionary(Of IIngredient, ChangeableDictionaryValue(Of Integer))

	''' <summary>
	''' Instantiates a new cult with no members.
	''' </summary>
	Public Sub New()
		MyBase.New()
		_innerDictionary = New Dictionary(Of IIngredient, ChangeableDictionaryValue(Of Integer))
	End Sub

	''' <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 Function GetPairByKeyType(ByVal key As IIngredient) As KeyValuePair(Of IIngredient, ChangeableDictionaryValue(Of Integer))
		Dim pair As KeyValuePair(Of IIngredient, ChangeableDictionaryValue(Of Integer)) = _innerDictionary.Where(Function(p) p.Key.GetType = key.GetType).FirstOrDefault
		If pair.Key Is Nothing Then
			Throw New KeyNotFoundException
		Else
			Return pair
		End If
	End Function

	''' <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 Overridable Sub Add(ByVal key As IIngredient, ByVal value As Integer) Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).Add
		Add(New KeyValuePair(Of IIngredient, Integer)(key, value))
	End Sub

	''' <summary>
	''' Adds the KeyValuePair to the _innerDictionary.
	''' </summary>
	''' <param name="item">The KeyValuePair to add.</param>
	Private Sub Add(ByVal item As System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).Add
		' If the key already exists then do not add a new KeyValuePair.
		' Add the specified amount to the already existing KeyValuePair instead.
		If Me.ContainsKey(item.Key) Then
			If Me(item.Key) + item.Value < 0 Then
				Throw New InvalidOperationException(ErrorMessage)
			Else
				Me(item.Key) += item.Value
			End If
		Else
			If item.Value < 0 Then
				Throw New InvalidOperationException(ErrorMessage)
			Else
				_innerDictionary.Add(item.Key, New ChangeableDictionaryValue(Of Integer)(item.Value))
			End If
		End If
	End Sub

	''' <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 Overridable Function Remove(ByVal key As IIngredient) As Boolean Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).Remove
		If Me.ContainsKey(key) Then
			Return _innerDictionary.Remove(GetPairByKeyType(key).Key)
		Else
			Return False
		End If
	End Function

	''' <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>
	Private Function Remove(ByVal item As System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).Remove
		Me.Add(item.Key, -item.Value)
		Return True
	End Function

	''' <summary>
	''' Clears the dictionary of ingredients.
	''' </summary>
	Public Sub Clear() Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).Clear
		_innerDictionary.Clear()
	End Sub

	''' <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 Function Contains(ByVal ingredient As IIngredient, ByVal amount As Integer) As Boolean
		Return Me.Contains(New KeyValuePair(Of IIngredient, Integer)(ingredient, amount))
	End Function

	''' <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>
	Private Function Contains(ByVal item As System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)) As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).Contains
		If Me.ContainsKey(item.Key) Then
			Return GetPairByKeyType(item.Key).Value.Value >= item.Value
		Else
			Return False
		End If
	End Function

	''' <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 Function ContainsKey(ByVal key As IIngredient) As Boolean Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).ContainsKey
		Dim pair As KeyValuePair(Of IIngredient, ChangeableDictionaryValue(Of Integer)) = _innerDictionary.Where(Function(p) p.Key.GetType = key.GetType).FirstOrDefault
		If pair.Key Is Nothing Then
			Return False
		Else
			Return True
		End If
	End Function

	''' <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 Function TryGetValue(ByVal key As IIngredient, ByRef value As Integer) As Boolean Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).TryGetValue
		If Me.ContainsKey(key) Then
			value = GetPairByKeyType(key).Value.Value
			Return True
		Else
			value = Nothing
			Return False
		End If
	End Function

	''' <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>
	Default Public Overridable Property Item(ByVal key As IIngredient) As Integer Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).Item
		Get
			Return GetPairByKeyType(key).Value.Value
		End Get
		Set(ByVal value As Integer)
			If value < 0 Then
				Throw New InvalidOperationException(ErrorMessage)
			Else
				GetPairByKeyType(key).Value.Value = value
			End If
		End Set
	End Property

	''' <summary>
	''' Gets a collection containing all ingredients in the dictionary.
	''' </summary>
	''' <returns>A collection of ingredients.</returns>
	Public ReadOnly Property Keys As System.Collections.Generic.ICollection(Of IIngredient) Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).Keys
		Get
			Return _innerDictionary.Keys
		End Get
	End Property

	''' <summary>
	''' Gets a collection containing all amounts of ingredients in the dictionary.
	''' </summary>
	''' <returns>A collection of integers.</returns>
	Public ReadOnly Property Values As System.Collections.Generic.ICollection(Of Integer) Implements System.Collections.Generic.IDictionary(Of IIngredient, Integer).Values
		Get
			Return InnerDictToIntValue.Values
		End Get
	End Property

	''' <summary>
	''' Gets the amount of unique ingredients in the dictionary.
	''' </summary>
	''' <returns>The amount of unique ingredients in the dictionary.</returns>
	Public ReadOnly Property Count As Integer Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).Count
		Get
			Return _innerDictionary.Count
		End Get
	End Property

	''' <summary>
	''' Returns a value that specifies wether the dictionary is read only.
	''' </summary>
	''' <returns>True if the dictionary is read only.</returns>
	Public ReadOnly Property IsReadOnly As Boolean Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).IsReadOnly
		Get
			Return False
		End Get
	End Property

	''' <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 Function InnerDictToIntValue() As Dictionary(Of IIngredient, Integer)
		Dim dict As New Dictionary(Of IIngredient, Integer)
		' Put all KeyValuePairs of the _innerDictionary in the new dictionary using a one-line LINQ query.
		_innerDictionary.ToList.ForEach(Sub(pair) dict.Add(pair.Key, pair.Value.Value))
		Return dict
	End Function

	''' <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>
	Private Sub CopyTo(ByVal array() As System.Collections.Generic.KeyValuePair(Of IIngredient, Integer), ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).CopyTo
		' 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.
		DirectCast(InnerDictToIntValue(), IDictionary(Of IIngredient, Integer)).CopyTo(array, arrayIndex)
	End Sub

	''' <summary>
	''' Returns an Enumerator that iterates through the ingredients and their amounts.
	''' </summary>
	''' <returns>An Enumerator for ingredients.</returns>
	Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)) Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of IIngredient, Integer)).GetEnumerator
		' First convert the _innerDictionary to a Dictionary having an Integer as value.
		Return InnerDictToIntValue.GetEnumerator
	End Function

	''' <summary>
	''' Returns an Enumerator that iterates through the ingredients and their amounts.
	''' </summary>
	''' <returns>An Enumerator for ingredients.</returns>
	Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
		Return Me.GetEnumerator
	End Function

End Class

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