Thursday, February 17, 2011

Creating a constant Dictionary in C#

What is the most efficient way to create a constant (never changes at runtime) mapping of strings to ints?

I've tried using a const Dictionary, but that didn't work out.

I could implement a immutable wrapper with appropriate semantics, but that still doesn't seem totally right.


For those who have asked, I'm implementing IDataErrorInfo in a generated class and am looking for a way to make the columnName lookup into my array of descriptors.

I wasn't aware (typo when testing! d'oh!) that switch accepts strings, so that's what I'm gonna use. Thanks!

From stackoverflow
  • There does not seem to be any standard immutable interface for dictionaries, so creating a wrapper seems like the only reasonable option, unfortunately.

    Edit: Marc Gravell found the ILookup that I missed - that will allow you to at least avoid creating a new wrapper, although you still need to transform the Dictionary with .ToLookup().

    If this is a need constrained to a specific scenario, you might be better off with a more business-logic-oriented interface:

    interface IActiveUserCountProvider
    {
        int GetMaxForServer(string serverName);
    }
    
  • There are precious few immutable collections in the current framework. I can think of one relatively pain-free option in .NET 3.5:

    Use Enumerable.ToLookup() - the Lookup<,> class is immutable (but multi-valued on the rhs); you can do this from a Dictionary<,> quite easily:

        Dictionary<string, int> ids = new Dictionary<string, int> {
          {"abc",1}, {"def",2}, {"ghi",3}
        };
        ILookup<string, int> lookup = ids.ToLookup(x => x.Key, x => x.Value);
        int i = lookup["def"].Single();
    
  • What's about public static readonly?

    Sander : This would make the Dictionary **reference** read-only, not the contents themselves.
    Tamir : Actually, I was failed to find real scenario for mapping strings to ints, except messagebag. However, if it's about Pyton-based programming, I'll vote for using enum constant instead of all those :)
  • enum Constants
    {
        Abc = 1,
        Def = 2,
        Ghi = 3
    }
    
    ...
    
    int i = (int)Enum.Parse(typeof(Constants), "Def");
    
    David Schmitt : Interesting idea! I'm wondering how good the Parse() call performs. I fear only a profiler can answer that one.
  • Creating a truly compile-time generated constant dictionary in C# is not really a straightforward task. Actually, none of the answers here really achieve that.

    There is one solution though which meets your requirements, although not necessarily a nice one; remember that according to the C# specification, switch-case tables are compiled to constant hash jump tables. That is, they are constant dictionaries, not a series of if-else statements. So consider a switch-case statement like this:

    switch (myString)
    {
       case "cat": return 0;
       case "dog": return 1;
       case "elephant": return 3;
    }
    

    This is exactly what you want. And yes, I know, it's ugly.

    mackenir : Hey, I just learned something.
    David Schmitt : It's generated code anyways, has good performance characteristics, can be compile-time computed, and has other nice properties for my use case too. I'll be doing it like this!
    Tom Anderson : just be careful that returning non-value types, even like this, will break your classes immutability.
  • This is the closest thing you can get to a "CONST Dictionary":

    public static int GetValueByName(string name)
    {
        switch (name)
        {
            case "bob": return 1;
            case "billy": return 2;
            default: return -1;
        }
    }
    

    The compiler will be smart enough to build the code as clean as possible.

  • what if we want to populate the dictionary from database?

    David Schmitt : Good question! Populating the dict at run-time doesn't allow the dict to be compile-time optimized like the switch() statement. I'd go for a ToDictionary() or ToLookup() Linq call there. See e.g. http://msdn.microsoft.com/en-us/library/system.linq.enumerable.tolookup.aspx
  • Why not:

    public class MyClass
    {
        private Dictionary<string, int> _myCollection = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };
    
        public IEnumerable<KeyValuePair<string,int>> MyCollection
        {
            get { return _myCollection.AsEnumerable<KeyValuePair<string, int>>(); }
        }
    }
    David Schmitt : Because this is quite expensive compared to the available alternatives under the specified conditions.

0 comments:

Post a Comment