Question

I have a field in a database (whose schema I can't change) which contains a specific set of values. Let's call them H, M, and L. H stands for High, M for Medium, and L is for Low. In C# I'd like to be able to reference these values in a typesafe way, but one that is also readable in code.

Currently there's a lot of this pattern littering the repository:

public static class Priority
{
    public const string High = "H";
    public const string Medium = "M";
    public const string Low = "L";
}

Which does provide the readability but isn't typesafe and could potentially be dangerous if lowercase values make their way into the database (unlikely but not impossible).

Is there a better way to handle this pattern?

Was it helpful?

Solution

You can implement this as a combination of an enum and a static class encapsulating logic for it, like this:

public enum Priority { High, Medium, Low }

public static class Priorities {
    public static string GetCode(this Priority priority) {
        switch (priority) {
        case Priority.High: return "H";
        case Priority.Medium: return "M";
        case Priority.Low: return "L";
        }
        throw new ArgumentException("priority");
    }
    public static Priority GetPriority(string priorityCode) {
        switch (priorityCode) {
        case "H": return Priority.High;
        case "M": return Priority.Medium;
        case "L": return Priority.Low;
        }
        throw new ArgumentException("priorityCode");
    }
}

Now you can use Priorities.GetPriority(codeFromDatabase) to make an element of Priority enumeration from a DB code, and call

priority.GetCode()

to obtain a code for writing a Priority back to the database.

OTHER TIPS

There are two ways I'd deal with this, depending on the situation.

The first is to use an enum and a Dictionary<TKey, TValue> to map a character to an entry in the enum.

enum Priority : byte
{
    High,
    Medium,
    Low
}
static class Priorities
{
    private static Dictionary<char, Priority> _toPriority = new Dictionary<char, Priority>();
    private static Dictionary<Priority, char> _fromPriority = new Dictionary<Priority, char>();

    static Priorities()
    {
        var priorities = Enum.GetNames(typeof(Priority));
        var values = (Priority[])Enum.GetValues(typeof(Priority));
        for (var i = 0; i < priorities.Length; i++)
        {
            _toPriority.Add(priorities[i][0], values[i]);
            _fromPriority.Add(values[i], priorities[i][0]);
        }
    }

    public static Priority GetPriority(string field)
    {
        Priority res;
        if (!TryGetPriority(field, out res))
            throw new ArgumentException("Invalid priority on field.", "field");
        return res;
    }

    public static bool TryGetPriority(string field, out Priority priority)
    {
        if (field == null || field.Length == 0) { priority = default(Priority); return false; }
        return _toPriority.TryGetValue(field[0], out priority);
    }

    public static char GetCode(Priority priority)
    {
        return _fromPriority[priority];
    }
}

Another way to do this would be to create a struct which creates itself in public static readonly fields.

struct Priority
{
    public static readonly Priority High = new Priority('H');
    public static readonly Priority Medium = new Priority('M');
    public static readonly Priority Low = new Priority('L');

    static Priority()
    {
        register(High);
        register(Medium);
        register(Low);
    }

    public static bool TryGetPriority(char code, out Priority priority)
    {
        return _map.TryGetValue(code, out priority);
    }
    public static Priority GetPriority(char code)
    {
        Priority priority;
        if (!TryGetPriority(code, out priority))
            throw new ArgumentException("Code doesn't represent an existing priority.", "code");
        return priority;
    }

    public override int GetHashCode()
    {
        return _code.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        if (!(obj is Priority)) return false;
        return ((Priority)obj)._code == _code;
    }

    public override string ToString()
    {
        return _code.ToString();
    }

    public static implicit operator char(Priority @this) { return @this._code; }
    public static explicit operator Priority(char code)
    {
        Priority result;
        if (!_map.TryGetValue(code, out result))
            throw new InvalidCastException();
        return result;
    }

    private static readonly Dictionary<char, Priority> _map = new Dictionary<char, Priority>();
    private static void register(Priority p)
    {
        _map.Add(char.ToLowerInvariant(p._code), p);
        _map.Add(char.ToUpperInvariant(p._code), p);
    }

    private readonly char _code;
    private Priority(char code) { _code = code; }
}

Method 1:
Pros: You only have to define the enum the result will automatically update. You can access both the full name (enumInstance.ToString()) and the code.
Cons: You need to explicitly call conversion methods to change between the char and Priority.

Method 2:
Pros: The type will implicitly convert to char, and can be cast from char.
Cons: You have to update both the calls to register and the enum to add or modify entries. You cannot access the full name of the field.

Both cons on method two can be resolved easily. The first can be resolved by using reflection to discover all public fields. The second by either adding it as parameter to the constructor or also through reflection.


Using method 1:

Priority p = Priority.High; // Assign literal
MessageBox.Show(p.ToString()); // High
MessageBox.Show(Priorities.GetCode(p).ToString()); // H

Priority p = Priorities.GetPriority('L'); // Cast from character
MessageBox.Show(p.ToString()); // Low
MessageBox.Show(Priorities.GetCode(p).ToString()); // L

Priority p; // Safe assigning
if (!Priorities.TryGetPriority('M', out p))
    return;
MessageBox.Show(p.ToString()); // Medium
MessageBox.Show(Priorities.GetCode(p).ToString()); // M

Using method 2:

Priority p = Priority.High; // Assign literal
MessageBox.Show(p.ToString()); // H

Priority p = (Priority)'L'; // Cast from character
MessageBox.Show(p.ToString()); // L

Priority p; // Safe assigning
if (!Priority.TryGetPriority('M', out p))
    return; // Handle invalid scenario
MessageBox.Show(p.ToString()); // M

Personally I think this solution is much cleaner than relying on two switches and the definition. Performance wise (it won't really matter unless you have an incredibly large database) it'll perform very similar to the switch statement. A switch statement in the right condition will be compiled an in-code hashmap, just like a Dictionary<TKey, TValue> is a hashmap.

If you want to have multi-character strings just change char to string.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top