Should I use Enum, static Class, Dictionary or Struct to represent these "labeled floats" in C#?
Andrew Mclaughlin
I have a constant data structure that represents the relative height of each human vertebra, normalized in relation to total spine height. This is derived from anthropometric studies, etc.
I have implemented it in Python as a tuple of tuples, each tuple containing a (string)Name and (double)Value, like this:
vertebral_heights = (
("C7", 0.0000000),
("T1", 0.0391914),
("T2", 0.0785479),
("T3", 0.1183993),
("T4", 0.1590759),
("T5", 0.2009076),
("T6", 0.2442244),
("T7", 0.2893564),
("T8", 0.3366337),
("T9", 0.3863861),
("T10", 0.4389439),
("T11", 0.4946370),
("T12", 0.5537954),
("L1", 0.6167492),
("L2", 0.6838284),
("L3", 0.7553630),
("L4", 0.8316832),
("L5", 0.9131188),
("S1", 1.0000000))My first thought was to create a Dictionary, but that would need a class to be used as a container. Then the idea of an Enum came to mind, but I have read "enums are for ints", and I have doubles. Then there are Class and Struct, but to this point I am utterly confused, and I believe my current understanding of the best practices of doing this stuff in C# is not enough, yet.
My intended use is to have a "map" between the application model (the numeric part of the elements) and the user model (the named, domain-related part of the elements).
Any suggestion?
37 Answers
It really depends on how you want to access the values.
Constants
If you will always use variable names, for example:
double x = C7;then you can just use a class full of constants like so:
public class VertebralHeights
{ public const double C7 = 0.0000000d;
}Dictionary
However, if you want to access them dynamically, for example:
string id = "C7";
double x = VertebralHeights[id];then you will be better off with a Dictionary which you can define like so:
Dictionary<string, double> VertebralHeights = new Dictionary<string, double>()
{ { "C7", 0.0000000d }, { "T1", 0.0391914d}
}Having both ways together.
If you want both strongly-typed and dynamic access to the value you can extend either of the above methods...
For constants (method 1) add a function that takes a string:
public double GetValue(string s)
{ switch(s) { case "C7": return C7; case "T7": return T7; //...and so on... default: return 0;//or an alternate default }
}(note: you could do this with reflection instead, which would be easier with a massive list, but isn't really worth the extra performance hit here)
For the Dictionary approach (method 2), you could add a collection of getters:
public double C7 { get { return VertebralHeights["C7"]; } } 0 Here's my take on this - work with a singleton class which is a Dictionary:
public class Vertebrae : Dictionary<string, double>
{ private Vertebrae() : base() { } private static Vertebrae _heights = new Vertebrae() { { "C7", 0.0 }, { "T1", 0.0391914 }, { "T2", 0.0785479 }, }; public static Vertebrae Heights { get { return _heights; } } public static double C7 { get { return Heights["C7"]; } } public static double T1 { get { return Heights["T1"]; } } public static double T2 { get { return Heights["T2"]; } } public static IEnumerable<double> All { get { return new List<double>() { C7, T1, T2 }; } }
}To access your Vertebrae by string name, you do:
double c7 = Vertebrae.Heights["C7"];To access your Vertebrae by symbolic name, you do:
double c7 = Vertebrae.C7;To enumerate your Vertebrae you do:
foreach (double v in Vertebrae.All) { /* ... */ }For the enumerator you could have a single static List initialized as in the enumerator, but I wasn't sure which would get initialized first, the static list or the static dictionary...
3Do it as an enum, and write the black-box plumbing code up front. You will not regret it! Here's what I would do:
Write a custom attribute so that you can associate the double value to each enum:
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
internal sealed class VertebralHeightAsDoubleAttribute : Attribute
{ public double HeightValue { get; private set; } public VertebralHeightAsDoubleAttribute(double heightValue_) { HeightValue = heightValue_; }
} Some extension methods to make life easier:
public static class VHAttribExtensions
{ public static string ToNameString(this VertebralHeight target) { return Enum.GetName(typeof(VertebralHeight), target); } public static double ToHeightValue(this VertebralHeight target) { var fi = target.GetType().GetField(target.ToString()); var attributes = (VertebralHeightAsDoubleAttribute[])fi.GetCustomAttributes( typeof(VertebralHeightAsDoubleAttribute), false); return attributes.Length > 0 ? attributes[0].HeightValue : double.NaN; }
}Define your enum using the custom attribute:
public enum VertebralHeight
{ [VertebralHeightAsDouble(0.0000000)] C7, [VertebralHeightAsDouble(0.0391914)] T1, [VertebralHeightAsDouble(0.0785479)] T2, [VertebralHeightAsDouble(0.1183993)] T3, [VertebralHeightAsDouble(0.1590759)] T4, [VertebralHeightAsDouble(0.2009076)] T5, [VertebralHeightAsDouble(0.2442244)] T6, [VertebralHeightAsDouble(0.2893564)] T7, [VertebralHeightAsDouble(0.3366337)] T8, [VertebralHeightAsDouble(0.3863861)] T9, [VertebralHeightAsDouble(0.4389439)] T10, [VertebralHeightAsDouble(0.4946370)] T11, [VertebralHeightAsDouble(0.5537954)] T12, [VertebralHeightAsDouble(0.6167492)] L1, [VertebralHeightAsDouble(0.6838284)] L2, [VertebralHeightAsDouble(0.7553630)] L3, [VertebralHeightAsDouble(0.8316832)] L4, [VertebralHeightAsDouble(0.9131188)] L5, [VertebralHeightAsDouble(1.0000000)] S1
}Test it:
static void Main(string[] args)
{ var list = Enum.GetValues(typeof(VertebralHeight)).OfType<VertebralHeight>(); foreach (var vh in list) { Console.WriteLine("{0} : {1}", vh.ToNameString(), vh.ToHeightValue()); } Console.ReadLine();
} 5 You could create a class:
public static class VertebralHeights
{ public const double C7 = 0.0000000; public const double T1 = 0.0391914; //...
}Access: double c7 = VertebralHeights.C7;
Depends on how you are using those mappings. If any lookups by name (string) are involved, then Dictionary is right choice. But if you only need those numbers to have friendly names, I would go for constants in a class (perhaps static).
It is also easy to enumerate both keys and values in a dictionary:
var dict = new Dictionary<string, double>();
foreach (var key in dict.Keys)
{
}
foreach (var value in dict.Values)
{
} 4 To enumerate them, find them by string, and sort them by order, you need to store three pieces of data. The way you access them changes the way that's best to store them.
Listof tuples containing name and value.- This has the primary advantage of ordering.
- In order to get a specific item by name, you'd need a linq query to retrieve it.
Dictionarywith key of name, and value of tuples containing order and value- This has the primary advantage of finding items quickly by name
- In order to get all the items in a specific order you'd need a linq query to order them.
- If you just want to loop over all the items without having a specific order,
Dictionarywill allow that.
- Keep one dictionary and one list of values. - Kind of messy, might be an over-optimization.
- Make a custom collection that inherits from one of the above implementations and provided the missing functionality hiding the actual implementation. Linq isn't so slow as to be worrisome with a short list.
You could use a simple class in a generic collection. Then LINQ makes it easy map the values to one another.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{ class Vertebra { public string name { get; set; } public double height { get; set; } } class Program { static void Main(string[] args) { List<Vertebra> Vertebrae = new List<Vertebra>() { new Vertebra() {name = "C7", height = 0.0000000}, new Vertebra() {name = "T1", height = 0.0391914} //etc }; //find height by name: double H = Vertebrae.Single(v => v.name == "C7").height; //find name by height: string N = Vertebrae.Single(v => v.height == 0.0391914).name; } }
} 1