Introduction
This article presents a dictionary that is constrained to hold only those keys which are defined in an enumeration; those keys are automatically pre-filled by the constructor. No keys may be removed, and no keys may be added. Furthermore, the Dictionary may be made read-only so the values can't be changed either.
Background
OK, why would you want to do that?
I am now using this class in my ParsedCommandLine[^] class, where it holds the values of the "switches" from the command line. There, it replaces the use of a more generic ReadOnlyDictionary
.
However, the more immediate impetus for writing this class is this Tip: OperationResult Class in Support of Multiple Return Values from a Method[^] which presents a solution for a similar situation -- that of returning a Dictionary of result data.
Using the code
OK, so just how would this class be used in that situation?
Consider that this class constrains the Dictionary to the members of an enumeration. That enumeration must be accessible by both the calling and called code and it acts as an explicit contract between the sections of code. The called code says that it will return a Dictionary that contains the members of a particular enumeration and it follows through on that promise. The calling code can then confidently access the items in the Dictionary.
With the code in the referenced Tip, the caller has no guarantee of what keys are in the Dictionary.
As an example, I'll use something like that in the referenced Tip. Let's say that we're writing a method that will accept a string and attempt to extract a URI and a port number from it. The result of such an operation may yield the following four values, as defined in an enumeration:
public enum ParseUriResult
{
Success
,
Message
,
ServerUri
,
Port
}
The method can then be defined something like:
public static EnumConstrainedDictionary<ParseUriResult,object>
ParseUri ( string Candidate )
{
EnumConstrainedDictionary<ParseUriResult,object> result =
new EnumConstrainedDictionary<ParseUriResult,object>() ;
try
{
string uri = null ;
int port = 0 ;
result [ ParseUriResult.Success ] = true ;
result [ ParseUriResult.ServerUri ] = uri ;
result [ ParseUriResult.Port ] = port ;
}
catch ( System.Exception ex )
{
result [ ParseUriResult.Success ] = false ;
result [ ParseUriResult.Message ] = ex.Message ;
}
result.IsReadOnly = true ;
return ( result ) ;
}
And the caller can access the returned data something like:
EnumConstrainedDictionary<ParseUriResult,object> uri = ParseUri ( foo ) ;
if ( (bool) uri [ ParseUriResult.Success ] )
{
connect ( (string) uri [ ParseUriResult.ServerUri ] , (int) uri [ ParseUriResult.Port ] ) ;
}
else
{
log ( uri [ ParseUriResult.Message ] ) ;
}
That's rather ugly, but it's OK for something you don't use very frequently, and which is burried deep within a library. Just for exercise, let's try to clean it up a little bit.
A more mature technique
Define a class that derives from EnumConstrainedDictionary
and which contains the enumeration:
public sealed class ParseUriResult : EnumConstrainedDictionary<ParseUriResult.Field,object>
{
public enum Field
{
Success
,
Message
,
ServerUri
,
Port
}
}
And then use it like this:
public static ParseUriResult
ParseUri ( string Candidate )
{
ParseUriResult result =
new ParseUriResult() ;
try
{
string uri = null ;
int port = 0 ;
result [ ParseUriResult.Field.Success ] = true ;
result [ ParseUriResult.Field.ServerUri ] = uri ;
result [ ParseUriResult.Field.Port ] = port ;
}
catch ( System.Exception ex )
{
result [ ParseUriResult.Field.Success ] = false ;
result [ ParseUriResult.Field.Message ] = ex.Message ;
}
result.IsReadOnly = true ;
return ( result ) ;
}
And like this:
public static void
ConnectUri
(
System.Net.Sockets.Socket Socket
,
string Uri
)
{
ParseUriResult uri = ParseUri ( Uri ) ;
if ( (bool) uri [ ParseUriResult.Field.Success ] )
{
Socket.Connect ( (string) uri [ ParseUriResult.Field.ServerUri ] , (int) uri [ ParseUriResult.Field.Port ] ) ;
}
else
{
System.Console.WriteLine ( uri [ ParseUriResult.Field.Message ] ) ;
}
}
That seems much cleaner to me.
EnumConstrainedDictionary
The class wraps a System.Collections.Generic.Dictionary<Tkey,Tvalue>
and implements System.Collections.Generic.IDictionary<Tkey,Tvalue>
.
Most of the members are simply passed through to the Dictionary -- e.g. Count .
Several are simply marked Obsolete and will throw a System.InvalidOperationException
if they're ever called -- e.g. Add and Remove .
I won't bore you with those details.
Constructor
The constructor gets the defined values from the enumeration and adds them to the Dictionary.
public EnumConstrainedDictionary
(
)
{
this.data = new System.Collections.Generic.Dictionary<Tkey,Tvalue>() ;
Tkey[] a = (Tkey[]) System.Enum.GetValues ( keytype ) ;
for ( int i = 0 ; i < a.Length ; i++ )
{
this.data [ a [ i ] ] = default(Tvalue) ;
}
return ;
}
IsReadOnly
Is used to get and set the locked
state of the instance. Once you've set locked
to true, you can't change the values in the Dictionary.
private bool locked = false ;
public virtual bool
IsReadOnly
{
get
{
return ( this.locked ) ;
}
set
{
if ( this.locked && !value )
{
throw ( new System.InvalidOperationException ( "This instance is read-only" ) ) ;
}
this.locked = value ;
return ;
}
}
Indexer
The indexer is implemented like this:
public virtual Tvalue
this
[
Tkey Key
]
{
get
{
if ( !this.data.ContainsKey ( Key ) )
{
throw ( new System.ArgumentOutOfRangeException ( "Key" , Key , "That Key does not exist" ) ) ;
}
return ( this.data [ Key ] ) ;
}
set
{
if ( !this.data.ContainsKey ( Key ) )
{
throw ( new System.ArgumentOutOfRangeException ( "Key" , Key , "That Key does not exist" ) ) ;
}
if ( this.locked )
{
throw ( new System.InvalidOperationException ( "This instance is read-only" ) ) ;
}
this.data [ Key ] = value ;
return ;
}
}
Conclusion
This class probably won't be used much, but I think it fits a rather small niche very nicely.
This has not been extensively tested. My ParsedCommandLine class uses a PIEBALD.Type.EnumConstrainedDictionary<T,string>
which doen't really stress the code.
History
2016-01-04 First submitted