Click here to Skip to main content
15,885,985 members
Articles / Web Development / HTML
Tip/Trick

EnumConstrainedDictionary

Rate me:
Please Sign up or sign in to vote.
2.80/5 (2 votes)
5 Jan 2016CPOL3 min read 9.6K   1   8
A Dictionary that is constrained to hold only the defined members of an enumeration

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:

C#
public enum ParseUriResult
{
  Success
,
  Message  
,
  ServerUri
,
  Port        
}

The method can then be defined something like:

C#
public static EnumConstrainedDictionary<ParseUriResult,object>
ParseUri ( string Candidate )
{
  EnumConstrainedDictionary<ParseUriResult,object> result = 
    new EnumConstrainedDictionary<ParseUriResult,object>() ;

  try
  {
    string uri  = null ;
    int    port = 0    ;

    // Do whatever it takes to extract the data

    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:

C#
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:

C#
public sealed class ParseUriResult : EnumConstrainedDictionary<ParseUriResult.Field,object>
{
  public enum Field
  {
    Success
  ,
    Message  
  ,
    ServerUri
  ,
    Port        
  }
}

And then use it like this:

C#
public static ParseUriResult
ParseUri ( string Candidate )
{
  ParseUriResult result = 
    new ParseUriResult() ;

  try
  {
    string uri  = null ;
    int    port = 0    ;

    // Do whatever to extract the data

    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:

C#
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.

C#
public EnumConstrainedDictionary
(
)
{
    /* Instantiate the dictionary */
    this.data = new System.Collections.Generic.Dictionary<Tkey,Tvalue>() ;

    /* Get all the defined members of the enumeration */
    Tkey[] a = (Tkey[]) System.Enum.GetValues ( keytype ) ;

    /* Put all the defined members in the dictionary */
    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.

C#
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:

C#
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

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)
United States United States
BSCS 1992 Wentworth Institute of Technology

Originally from the Boston (MA) area. Lived in SoCal for a while. Now in the Phoenix (AZ) area.

OpenVMS enthusiast, ISO 8601 evangelist, photographer, opinionated SOB, acknowledged pedant and contrarian

---------------

"I would be looking for better tekkies, too. Yours are broken." -- Paul Pedant

"Using fewer technologies is better than using more." -- Rico Mariani

"Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’" -- Steve McConnell

"Every time you write a comment, you should grimace and feel the failure of your ability of expression." -- Unknown

"If you need help knowing what to think, let me know and I'll tell you." -- Jeffrey Snover [MSFT]

"Typing is no substitute for thinking." -- R.W. Hamming

"I find it appalling that you can become a programmer with less training than it takes to become a plumber." -- Bjarne Stroustrup

ZagNut’s Law: Arrogance is inversely proportional to ability.

"Well blow me sideways with a plastic marionette. I've just learned something new - and if I could award you a 100 for that post I would. Way to go you keyboard lovegod you." -- Pete O'Hanlon

"linq'ish" sounds like "inept" in German -- Andreas Gieriet

"Things would be different if I ran the zoo." -- Dr. Seuss

"Wrong is evil, and it must be defeated." –- Jeff Ello

"A good designer must rely on experience, on precise, logical thinking, and on pedantic exactness." -- Nigel Shaw

“It’s always easier to do it the hard way.” -- Blackhart

“If Unix wasn’t so bad that you can’t give it away, Bill Gates would never have succeeded in selling Windows.” -- Blackhart

"Use vertical and horizontal whitespace generously. Generally, all binary operators except '.' and '->' should be separated from their operands by blanks."

"Omit needless local variables." -- Strunk... had he taught programming

Comments and Discussions

 
GeneralMy vote of 3 Pin
Paulo Zemek5-Jan-16 14:11
mvaPaulo Zemek5-Jan-16 14:11 
QuestionHave you considered using a class instead of an enum? Pin
Paulo Zemek5-Jan-16 11:53
mvaPaulo Zemek5-Jan-16 11:53 
GeneralRe: Have you considered using a class instead of an enum? Pin
PIEBALDconsult5-Jan-16 13:36
mvePIEBALDconsult5-Jan-16 13:36 
GeneralRe: Have you considered using a class instead of an enum? Pin
Paulo Zemek5-Jan-16 13:49
mvaPaulo Zemek5-Jan-16 13:49 
GeneralRe: Have you considered using a class instead of an enum? Pin
PIEBALDconsult5-Jan-16 14:04
mvePIEBALDconsult5-Jan-16 14:04 
GeneralRe: Have you considered using a class instead of an enum? Pin
Paulo Zemek5-Jan-16 14:15
mvaPaulo Zemek5-Jan-16 14:15 
GeneralRe: Have you considered using a class instead of an enum? Pin
PIEBALDconsult5-Jan-16 14:42
mvePIEBALDconsult5-Jan-16 14:42 
AnswerRe: Have you considered using a class instead of an enum? Pin
LostTheMarbles6-Jan-16 2:31
professionalLostTheMarbles6-Jan-16 2:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.