tags:

views:

126

answers:

9

I am populating a "Dictionary" with the results of an sp call. The key is the field name and the value is whatever value the sp returns for the field. This is all well and good but I'd like developers to have a predefined list of keys to access this list, for safety and documentation reasons. What I'd like to do is have something like an enum as a key for the dictionary so developers can safely access the list, but still have the ability to access the dictionary with a string key value.

I am hoping to have a list of string values that I can access with an enum key AND a string key. Please make sure any suggestions are simple to implement, this is not the kind of thing I'm willing to build a lot of overhead to implement.

NOTE: I'd like there to be one key that drives this not one enum declaration and one List declaration in that I'd be maintaining two lists of keys, just in slightly different formats.

UPDATE: Let me clarify the reason I'd like a string key, I am using this list of stuff in once case to do replacements in a larger string that has something like ##keyName##. So i need to be able get values out of my dictionary based on what's in the ####. I don't know how to do that if I'm using an enum as my key. if I could map the string "keyName" to enum.keyName then my problems are solved.

+1  A: 

Any enum can be converted to a string with ToString. Therefore, you can write an indexer that takes any object and calls ToString on it. For a string, this is a no-op.

edit

To steal an idea from meiscooldude:

public static class EnumDictExt
{
    public static TValue Lookup<TValue, TEnum>(this IDictionary<string, TValue> dict, TEnum e)
            where TEnum : struct, IComparable, IFormattable, IConvertible
        { return dict[e.ToString()]; }
}
Steven Sudit
In response to OP's request, this method does not involve both a List and an enum. Rather, the enum names *are* the column names.
Steven Sudit
A: 

You can wrap a Dictionary in a custom class and add two indexers to that class one for string access and the other one for enum access

Itay
A: 

Override Dictionary<YourEnum> and provide an indexer that takes a string, converts it to an enum, and returns this[enum value]. Edit: Alternatively, override Dictionary<string> and add an indexer that takes an enum, and returns this[enum.ToString()].

Flynn1179
Oh, you mean like this? http://msdn.microsoft.com/en-us/library/dd783499.aspx
Steven Sudit
A: 

You could try something like the following:

public enum MyKeys
{
    FirstKey,
    SecondKey,
    ThirdKey
}

And then elsewhere in your code:

var dict = new Dictionary<MyKeys, string>();

dict.Add(MyKeys.FirstKey, "something");
dict.Add(MyKeys.SecondKey, "something else");
dict.Add(MyKeys.ThirdKey, "something else again");

// And then to access them, you can use
var stringVal = dict[MyKeys.FirstKey];
var secondVal = dict[(MyKeys)Enum.Parse(typeof(MyKeys), "secondkey", true)];
Justin Niessner
If I understand correctly, this is extensible in the wrong direction. OP wants to be able to use enums for known values, strings for unknown ones.
Steven Sudit
This may not be the best solution, but it is the one that works best in the code base I have. Its concise and easy to understand.Thank you
Agile Noob
@Agile: Do you need to deal with unexpected columns? On a side note, the awkward Enum.Parse code can be made prettier with an extension method.
Steven Sudit
+1  A: 

If your not looking for a lot of overhead, then using Extension methods is a possibility.

Meiscooldude
Good idea, so I'll steal it. I've edited my answer to include an example.
Steven Sudit
Haha, thanks for letting me know :)
Meiscooldude
I steal code, not credit. This makes me a genteel thief.
Steven Sudit
A: 

Like others said, you can create two indexers. But to encourage the enum and to discourage to string, make the enum the indexer and name the string api something like "GetTheValueOfThisElementWhichCanBeUnsafe" or similar (you get the idea...).

Lazy as I am, if both are indexers I could not care less but use the string version all the time. But if the API barks at me in a certain way, I at least see the implications in the code.

Arne
+1  A: 

Sounds like you are trying to replicate functionality already present in typed datasets, LINQ to SQL or even plain old DTOs (perhaps with some help from Automapper). Developers will find it easier to work with already familiar constructs like datatables or object properties

Panagiotis Kanavos
+3  A: 

I would just create a class containing constant strings instead of enums.

class Keys
{
    public const string Key1 = "Key1";
    public const string Key2 = "Key2";
}

This way you don't have to do anything with the dictionary, but still have your "suggested keys".

Aaron Smith
The `Symbol` idea proposed by Qwertie in a comment to the question is an extension of this idea, with some nice features.
Steven Sudit
A: 

If I understand you correctly, I recently did something similar with packets I received from/sent to a server. The packets were in form Key: Value\n and defined by a special key/value pair denoting its packet type (Action: Login, in this case below. See the Asterisk API for more reference.) I parsed the packet type out, generated classes based on the expected keys, as such:

public abstract class Packet
{
    private Dictionary<string, string> _dictionary =
        new Dictionary<string, string>();

    public string this[string key]
    {
        get
        {
            if (_dictionary.ContainsKey(key))
            {
                return _dictionary[key];
            }
            else { return ""; }
        }
        set { _dictionary[key] = value; }
    }

    public abstract PacketType Type { get; }
}

public class LoginPacket : Packet
{
    public override PacketType Type
    { get { return PacketType.Action; } }

    public string Username
    {
        get { return this["Username"]; }
        set { this["Username"] = value; }
    }
    public string Password
    {
        get { return this["Password"]; }
        set { this["Password"] = value; }
    }
    public int Status // Example: Not an actual key in Login in the Asterisk API
    {
        get { return int.Parse(this["Status"]); }
        set { this["Status"] = value.ToString(); }
    }
}

This creates a sort of strongly-typed dictionary, and you can do:

LoginPacket packet = new LoginPacket
{
    Username = "User",
    Password = "user",
    Status = 123
};
Console.WriteLine(packet.Username); // Is equal to:
Console.WriteLine(packet["Username"]);

Then handle packet like any other dictionary-like class, You could extend this so that Packet implements IDictionary, or go the other direction and make this[string key] get or set protected so that only valid keys are used.

Daniel Rasmussen
I'd recommend using TryGet in your indexer instead of Contains/Indexer.
Steven Sudit
A good point. Also, I'm kicking myself for using the wrong "its." I don't know what's happening to me... >.<
Daniel Rasmussen