Handling alphabetic enumerated codes in a database field - c#

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?

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.

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.

Related

Is there a way to use the 'in' keyword with delegates, Func<>s or IComparers?

I apologize if the question is poorly-worded, this has been a really hard question to phrase or to search for an answer for and it doesn't help that the 'in' keyword is used for contravarient delegates. This is the best way that I can describe it:
The setup:
public delegate int TComparer(in T left, in T right);
// an instance of the delegate, initialized elsewhere
private TComparer _comparer;
// an array of T, initialized elsewhere
private T[] data;
The 'in' keyword means that these operations throw conversion errors:
Array.Sort(data, _comparer); // CS1503
Func<T,T,int> _func = _comparer; // CS1503
Because a function where a parameter uses the 'in' keyword is different from a function that doesn't, it seems to me that there isn't any way to use delegates (or IComparer, etc.) with the keyword in this way. Is there something in the library designed to handle these cases, or is there another way to gain the same efficiency* in a case more specifically like this?
(* Note: In this context T may be a large immutable struct existing in great quantities and that is being accessed so frequently that it would be really useful to avoid defensive copying.)
Edit: Here's a more complete look at the code:
public readonly struct QuadData {
public readonly VertexPositionColorTexture TL; // 6 floats in a struct
public readonly VertexPositionColorTexture TR;
public readonly VertexPositionColorTexture BL;
public readonly VertexPositionColorTexture BR;
public readonly int Depth;
public readonly int TextureCode;
// constructors...
}
public class SpriteBatcher {
public SpriteBatcher() {
_quadBuffer = Array.Empty<QuadData>();
_quadComparer = CompareByDepthDescendingThenTexture;
}
public delegate int QuadDataComparer(in QuadData left, in QuadData right);
private QuadDataComparer _quadDataComparer; // initialized in constructor
private QuadData[] _quadBuffer; // initialized in constructor
public void Update() {
Array.Sort(_quadBuffer, _quadDataComparer); // CS1503
}
}
Some additional stuff that I don't think informs the question itself:
private void OnSetSortingMode(SpriteSortOption sortOption) {
if (sortOption == _sortOption) return;
switch (sortOption) {
case SpriteSortOption.None:
break;
case SpriteSortOption.Texture:
_quadDataComparer = CompareByTexture;
break;
case SpriteSortOption.DepthAscending:
_quadDataComparer = CompareByDepthAscendingThenTexture;
break;
case SpriteSortOption.DepthDescending:
_quadDataComparer = CompareByDepthDescendingThenTexture;
break;
default:
throw new ArgumentException($"Unimplemented SpriteBatcher.SortOptions enumerated value {sortOption} passed to SpriteBatcher.SetSortingMode()");
}
if (sortOption != SpriteSortOption.None) {
_flags |= SpriteBatcherFlags.Sort;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CompareByTexture(in QuadData x, in QuadData y) {
return y.TextureHashCode - x.TextureHashCode; // int.CompareTo(int) is not needed because we don't actually care if a HashCode is larger or smaller, only different.
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CompareByDepthAscendingThenTexture(in QuadData x, in QuadData y) {
if (x.Depth < y.Depth) return 1;
if (x.Depth > y.Depth) return -1;
return y.TextureHashCode - x.TextureHashCode; // int.CompareTo(int) is not needed because we don't actually care if a HashCode is larger or smaller, only different.
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CompareByDepthDescendingThenTexture(in QuadData x, in QuadData y) {
if (x.Depth > y.Depth) return 1;
if (x.Depth < y.Depth) return -1;
return y.TextureHashCode - x.TextureHashCode; // int.CompareTo(int) is not needed because we don't actually care if a HashCode is larger or smaller, only different.
}
public void SetCustomSortOption(QuadDataComparer comparison) {
this._quadDataComparer = comparison;
this._sortOption = SpriteSortOption.Custom;
}

Dictionary with class as Key

I am studying electronic engineering, and I am a beginner in C#. I have measured data and I would like to store it in a 2 dimensional way. I thought I could make a Dictionary like this:
Dictionary<Key, string> dic = new Dictionary<Key, string>();
"Key" here is my a own class with two int variables. Now I want to store the data in this Dictionary but it doesn't work so far. If I want to read the data with the special Key, the error report says, that the Key is not available in the Dictionary.
Here is the class Key:
public partial class Key
{
public Key(int Bahn, int Zeile) {
myBahn = Bahn;
myZeile = Zeile;
}
public int getBahn()
{
return myBahn;
}
public int getZeile()
{
return myZeile;
}
private int myBahn;
private int myZeile;
}
for testing it I made something like this:
Getting elements in:
Key KE = new Key(1,1);
dic.Add(KE, "hans");
...
Getting elements out:
Key KE = new Key(1,1);
monitor.Text = dic[KE];
Has someone an idea?
You need to override methods GetHashCode and Equals in your own class to use it as a key.
class Foo
{
public string Name { get; set;}
public int FooID {get; set;}
public override int GetHashCode()
{
return FooID;
}
public override bool Equals(object obj)
{
return Equals(obj as Foo);
}
public bool Equals(Foo obj)
{
return obj != null && obj.FooID == this.FooID;
}
}
Though you could use a class as key by implementing your own Equals and GetHashCode, I would not advise to do it if you're not yet familiar with C#.
These methods will be invoked by C# internal libraries, which expect them to work exactly as per specification, handling all edge cases gracefully. If you put a bug in them, you might be in for an unpleasant head scratching session.
In my opinion, it would be no less efficient and way simpler to create a key on the spot using tried, true and tested existing types that already support hashing and comparison.
From your angular coordinates, e.g.:
int Bahn = 15;
int Zeile = 30;
You could use a string (e.g. "15,30"):
String Key (int Bahn, int Zeile) { return $"{Bahn},{Zeile}"; }
var myDict = new Dictionary<string, string>();
myDict.Add (Key(Bahn,Zeile), myString);
or a two elements tuple (e.g. <15,30>) if you need something more efficient:
Tuple<int,int> Key (int Bahn, int Zeile) { return Tuple.Create(Bahn,Zeile); }
var myDict = new Dictionary<Tuple<int, int>, string>();
myDict.Add (Key(Bahn,Zeile), myString);
or a mere combination of your two angles if the range is small enough to fit into an int (e.g. 15+30*360) if you need something even more efficient:
int Key (int Bahn, int Zeile) { return Bahn+360*Zeile; }
var myDict = new Dictionary<int, string>();
myDict.Add (Key(Bahn,Zeile), myString);
That seems a lot less cumbersome than:
class Key {
// truckloads of code to implement the class,
// not to mention testing it thourougly, including edge cases
}
var myDict = new Dictionary<Key, string>();
myDict.Add (new Key(Bahn,Zeile), myString);
Mutability of the key
Also, note that your keys must be immutable as long as they are used to index an entry.
If you change the value of Bahn or Ziel after the key has been used to add an element, you will mess up your dictionary something bad.
The behaviour is undefined, but you will most likely lose random entries, cause memory leaks and possibly crash with an exception if the internal libraries end up detecting an inconsistent state (like several entries indexed by the same key).
For instance:
var myKey = new Key(15, 30);
for (String data in row_of_data_sampled_every_10_degrees)
{
myDict.Add (myKey, data); // myKey must remain constant until the entry is removed
myKey.Bahn += 10; // changing it now spells the death of your dictionary
}
A side note on hashing angular coordinates
Now the catch is, the generic hashing functions provided for ints, strings and tuples are unlikely to produce optimal results for your specific set of data.
I would advise to start with a simple solution and only resort to specialized code if you run into actual performance issues. In which case you would probably be better off using a data structure more suited to spatial indexing (typically a quadtree in case of polar coordinates, or an octree if you want to reconstruct a 3D model from your scanner data).
For the sake of an alternate opinion and not to be disparaging of Mr. Kuroi's solution (which is a good one) here is a simple class that can be used as a key in a map as well as other uses. We use a complex class as a key because we want to know track other things. In this example, we arrive at a vertex in a graph and we want to know if we have visited it before.
<code>
public class Vertex : IComparable<Vertex>, IEquatable<Vertex>, IComparable
{
String m_strVertexName = String.Empty;
bool m_bHasVisited = false;
public Vertex()
{
}
public Vertex(String strVertexName) : this()
{
m_strVertexName = strVertexName;
}
public override string ToString()
{
return m_strVertexName;
}
public string VertexName
{
get { return m_strVertexName; }
set
{
if (!String.IsNullOrEmpty(value))
m_strVertexName = value;
}
}
public bool HasVisited
{
get { return m_bHasVisited; }
set { m_bHasVisited = value; }
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public int CompareTo(Vertex rhs)
{
if (Equals(rhs))
return 0;
return ToString().CompareTo(rhs.ToString());
}
int IComparable.CompareTo(object rhs)
{
if (!(rhs is Vertex))
throw new InvalidOperationException("CompareTo: Not a Vertex");
return CompareTo((Vertex)rhs);
}
public static bool operator < (Vertex lhs, Vertex rhs) => lhs.CompareTo(rhs) < 0;
public static bool operator > (Vertex lhs, Vertex rhs) => lhs.CompareTo(rhs) > 0;
public bool Equals (Vertex rhs) => ToString() == rhs.ToString();
public override bool Equals(object rhs)
{
if (!(rhs is Vertex))
return false;
return Equals((Vertex)rhs);
}
public static bool operator == (Vertex lhs, Vertex rhs) => lhs.Equals(rhs);
public static bool operator != (Vertex lhs, Vertex rhs) => !(lhs == rhs);
}
</code>

Problem in implementing my own switch class

I am trying to implement a custom switch case just for fun..
The approach is that I have created a class that inherits a dictionary object
public class MySwitch<T> : Dictionary<string, Func<T>>
{
public T Execute(string key)
{
if (this.ContainsKey(key)) return this[key]();
else return default(T);
}
}
And I am using as under
new MySwitch<int>
{
{ "case 1", ()=> MessageBox.Show("From1") },
{ "case 2..10", ()=>MessageBox.Show("From 2 to 10") },
}.Execute("case 2..10");
But if I specify "case 2" it gives a default value as the key is not in the dictionary.
The whole purpose of making "case 2..10 " is that if the user enters anything between case 2 to case 10, it will execute the same value.
Could anyone please help me in solving this?
Thanks
The string "case 2..10" is stored in the dictionary object and the only way contains key returns ture is if you supply exactly that string. meaning you would have to pass the exact string "case 2..10" to containskey to return true.
My adice would be to look at the specification pattern.
You can have each function have one or more specification attached to it and then execute the relevant function (or functions if you want).
In short:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T item);
}
And a specific implementation:
public class IntegerRangeSpecification : ISpecification<int>
{
private readonly int min;
private readonly int max;
public IntegerRangeSpecification(int min, int max)
{
this.min = min;
this.max = max;
}
public bool IsSatisfiedBy(int item)
{
return (item >= min) && (item <= max);
}
}
Of course you would rather have RangeSpecification<T> : ISpecification<T> but that requires some more effort/design (such as where T : IComparable).
Anyway, I hope that gets you on the right track.
First you probably want to encapsulate a dictionary rather than extend a dictionary as I doubt you want all the methods of a dictionary in your switch class.
Second you will need to parse your case strings apart and add your own keys to the dictionary. The approach of using strings as keys strikes me as not the best of ideas. By using strings as your keys you are requiring that the keys match exactly. If you made your dictionary use integer keys and you changed how it was initialized. Keeping with the the spirit of your switch you could do something like this:
public class Switch<T>
{
private Dictionary<int, Func<T>> _switch = new Dictionary<int, Func<T>>();
private Func<T> _defaultFunc;
public Switch(Func<T> defaultFunc)
{
_defaultFunc = defaultFunc;
}
public void AddCase(Func<T> func, params int[] cases)
{
foreach (int caseID in cases)
{
_switch[caseID] = func;
}
}
public void AddCase( int beginRange, int endRange, Func<T> func)
{
for (int i = beginRange; i <= endRange; i++)
{
_switch[i] = func;
}
}
public T Execute(int caseID)
{
Func<T> func;
if(_switch.TryGetValue(caseID, out func)){
return func();
}else{
return _defaultFunc();
}
}
}
Which could be used like this:
Switch<int> mySwitch = new Switch<int>(() => {Console.WriteLine("Default"); return 0;});
mySwitch.AddCase(() => { Console.WriteLine("1"); return 1; }, 1);
mySwitch.AddCase(2, 10, () => { Console.WriteLine("2 through 10"); return 1; });
mySwitch.AddCase(() => { Console.WriteLine("11, 15, 17"); return 2; }, 11, 15, 17);
mySwitch.Execute(1);
mySwitch.Execute(2);
mySwitch.Execute(3);
mySwitch.Execute(4);
mySwitch.Execute(11);
mySwitch.Execute(12);
mySwitch.Execute(15);
There are a bunch of different ways you can accomplish what you want. Hope this helps some

Extending Enums, Overkill?

I have an object that needs to be serialized to an EDI format. For this example we'll say it's a car. A car might not be the best example b/c options change over time, but for the real object the Enums will never change.
I have many Enums like the following with custom attributes applied.
public enum RoofStyle
{
[DisplayText("Glass Top")]
[StringValue("GTR")]
Glass,
[DisplayText("Convertible Soft Top")]
[StringValue("CST")]
ConvertibleSoft,
[DisplayText("Hard Top")]
[StringValue("HT ")]
HardTop,
[DisplayText("Targa Top")]
[StringValue("TT ")]
Targa,
}
The Attributes are accessed via Extension methods:
public static string GetStringValue(this Enum value)
{
// Get the type
Type type = value.GetType();
// Get fieldinfo for this type
FieldInfo fieldInfo = type.GetField(value.ToString());
// Get the stringvalue attributes
StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(StringValueAttribute), false) as StringValueAttribute[];
// Return the first if there was a match.
return attribs.Length > 0 ? attribs[0].StringValue : null;
}
public static string GetDisplayText(this Enum value)
{
// Get the type
Type type = value.GetType();
// Get fieldinfo for this type
FieldInfo fieldInfo = type.GetField(value.ToString());
// Get the DisplayText attributes
DisplayTextAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(DisplayTextAttribute), false) as DisplayTextAttribute[];
// Return the first if there was a match.
return attribs.Length > 0 ? attribs[0].DisplayText : value.ToString();
}
There is a custom EDI serializer that serializes based on the StringValue attributes like so:
StringBuilder sb = new StringBuilder();
sb.Append(car.RoofStyle.GetStringValue());
sb.Append(car.TireSize.GetStringValue());
sb.Append(car.Model.GetStringValue());
...
There is another method that can get Enum Value from StringValue for Deserialization:
car.RoofStyle = Enums.GetCode<RoofStyle>(EDIString.Substring(4, 3))
Defined as:
public static class Enums
{
public static T GetCode<T>(string value)
{
foreach (object o in System.Enum.GetValues(typeof(T)))
{
if (((Enum)o).GetStringValue() == value.ToUpper())
return (T)o;
}
throw new ArgumentException("No code exists for type " + typeof(T).ToString() + " corresponding to value of " + value);
}
}
And Finally, for the UI, the GetDisplayText() is used to show the user friendly text.
What do you think? Overkill? Is there a better way? or Goldie Locks (just right)?
Just want to get feedback before I intergrate it into my personal framework permanently. Thanks.
The part you're using to serialize is fine. The deserialization part is awkwardly written. The main problem is that you're using ToUpper() to compare strings, which is easily broken (think globalization). Such comparisons should be done with string.Compare instead, or the string.Equals overload that takes a StringComparison.
The other thing is that performing these lookups again and again during deserialization is going to pretty slow. If you're serializing a lot of data, this could actually be quite noticeable. In that case, you'd want to build a map from the StringValue to the enum itself - throw it into a static Dictionary<string, RoofStyle> and use it as a lookup for the round-trip. In other words:
public static class Enums
{
private static Dictionary<string, RoofStyle> roofStyles =
new Dictionary<string, RoofStyle>()
{
{ "GTR", RoofStyle.Glass },
{ "CST", RoofStyle.ConvertibleSoft },
{ "HT ", RoofStyle.HardTop },
{ "TT ", RoofStyle.TargaTop }
}
public static RoofStyle GetRoofStyle(string code)
{
RoofStyle result;
if (roofStyles.TryGetValue(code, out result))
return result;
throw new ArgumentException(...);
}
}
It's not as "generic" but it's way more efficient. If you don't like the duplication of string values then extract the codes as constants in a separate class.
If you really need it to be totally generic and work for any enum, you can always lazy-load the dictionary of values (generate it using the extension methods you've written) the first time a conversion is done. It's very simple to do that:
static Dictionary<string, T> CreateEnumLookup<T>()
{
return Enum.GetValues(typeof(T)).ToDictionary(o => ((Enum)o).GetStringValue(),
o => (T)o);
}
P.S. Minor detail but you might want to consider using Attribute.GetCustomAttribute instead of MemberInfo.GetCustomAttributes if you only expect there to be one attribute. There's no reason for all the array fiddling when you only need one item.
Personally, I think you are abusing the language and trying to use enums in a way they were never intended. I would create a static class RoofStyle, and create a simple struct RoofType, and use an instance for each of your enum values.
Why don't you create a type with static members such as mikerobi said
Example...
public class RoofStyle
{
private RoofStyle() { }
public string Display { get; private set; }
public string Value { get; private set; }
public readonly static RoofStyle Glass = new RoofStyle
{
Display = "Glass Top", Value = "GTR",
};
public readonly static RoofStyle ConvertibleSoft = new RoofStyle
{
Display = "Convertible Soft Top", Value = "CST",
};
public readonly static RoofStyle HardTop = new RoofStyle
{
Display = "Hard Top", Value = "HT ",
};
public readonly static RoofStyle Targa = new RoofStyle
{
Display = "Targa Top", Value = "TT ",
};
}
BTW...
When compiled into IL an Enum is very similar to this class structure.
... Enum backing fields ...
.field public specialname rtspecialname int32 value__
.field public static literal valuetype A.ERoofStyle Glass = int32(0x00)
.field public static literal valuetype A.ERoofStyle ConvertibleSoft = int32(0x01)
.field public static literal valuetype A.ERoofStyle HardTop = int32(0x02)
.field public static literal valuetype A.ERoofStyle Targa = int32(0x03)
... Class backing fields ...
.field public static initonly class A.RoofStyle Glass
.field public static initonly class A.RoofStyle ConvertibleSoft
.field public static initonly class A.RoofStyle HardTop
.field public static initonly class A.RoofStyle Targa
Here is a base class I use for enumeration classes:
public abstract class Enumeration<T, TId> : IEquatable<T> where T : Enumeration<T, TId>
{
public static bool operator ==(Enumeration<T, TId> x, T y)
{
return Object.ReferenceEquals(x, y) || (!Object.ReferenceEquals(x, null) && x.Equals(y));
}
public static bool operator !=(Enumeration<T, TId> first, T second)
{
return !(first == second);
}
public static T FromId(TId id)
{
return AllValues.Where(value => value.Id.Equals(id)).FirstOrDefault();
}
public static readonly ReadOnlyCollection<T> AllValues = FindValues();
private static ReadOnlyCollection<T> FindValues()
{
var values =
(from staticField in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public)
where staticField.FieldType == typeof(T)
select (T) staticField.GetValue(null))
.ToList()
.AsReadOnly();
var duplicateIds =
(from value in values
group value by value.Id into valuesById
where valuesById.Skip(1).Any()
select valuesById.Key)
.Take(1)
.ToList();
if(duplicateIds.Count > 0)
{
throw new DuplicateEnumerationIdException("Duplicate ID: " + duplicateIds.Single());
}
return values;
}
protected Enumeration(TId id, string name)
{
Contract.Requires(((object) id) != null);
Contract.Requires(!String.IsNullOrEmpty(name));
this.Id = id;
this.Name = name;
}
protected Enumeration()
{}
public override bool Equals(object obj)
{
return Equals(obj as T);
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
public override string ToString()
{
return this.Name;
}
#region IEquatable
public virtual bool Equals(T other)
{
return other != null && this.IdComparer.Equals(this.Id, other.Id);
}
#endregion
public virtual TId Id { get; private set; }
public virtual string Name { get; private set; }
protected virtual IEqualityComparer<TId> IdComparer
{
get { return EqualityComparer<TId>.Default; }
}
}
An implementation would look like:
public sealed class RoofStyle : Enumeration<RoofStyle, int>
{
public static readonly RoofStyle Glass = new RoofStyle(0, "Glass Top", "GTR");
public static readonly RoofStyle ConvertibleSoft = new RoofStyle(1, "Convertible Soft Top", "CST");
public static readonly RoofStyle HardTop = new RoofStyle(2, "Hard Top", "HT ");
public static readonly RoofStyle Targa = new RoofStyle(3, "Targa Top", "TT ");
public static RoofStyle FromStringValue(string stringValue)
{
return AllValues.FirstOrDefault(value => value.StringValue == stringValue);
}
private RoofStyle(int id, string name, string stringValue) : base(id, name)
{
StringValue = stringValue;
}
public string StringValue { get; private set; }
}
You would use it during serialization like this:
var builder = new StringBuilder();
builder.Append(car.RoofStyle.StringValue);
...
To deserialize:
car.RoofStyle = RoofStyle.FromStringValue(EDIString.Substring(4, 3));
I don't see a problem with it - actually, I do the same. By this, I achieve verbosity with the enum, and can define how the enum is to be translated when I use it to request data, eg. RequestTarget.Character will result in "char".
Can't say I've ever seen it done that way but the consumer code is relatively simple so I'd probably enjoy using it.
The only thing that sticks out for me is the potential for consumers having to deal with nulls - which might be able to be removed. If you have control over the attributes (which you do, from the sounds of it), then there should never be a case where GetDisplayText or GetStringValue return null so you can remove
return attribs.Length > 0 ? attribs[0].StringValue : null;
and replace it with
return attribs[0].StringValue;
in order to simplify the interface for consumer code.
IMHO, the design is solid, and will work.
However, reflection tends to be a litle slow, so if those methods are used in tight loops, it might slow down the whole application.
You could try caching the the return values into a Dictionary<RoofStyle, string> so they are only reflected once, and then fetched from cache.
Something like this:
private static Dictionary<Enum, string> stringValues
= new Dictionary<Enum,string>();
public static string GetStringValue(this Enum value)
{
if (!stringValues.ContainsKey(value))
{
Type type = value.GetType();
FieldInfo fieldInfo = type.GetField(value.ToString());
StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(StringValueAttribute), false) as StringValueAttribute[];
stringValues.Add(value, attribs.Length > 0 ? attribs[0].StringValue : null);
}
return stringValues[value];
}
I know this question has already been answered, but while ago I posted the following code fragment on my personal blog, which demonstrates faking Java style enums using extension methods. You might find this method works for you, especially as it overcomes the overhead of accessing Attributes via reflection.
using System;
using System.Collections.Generic;
namespace ScratchPad
{
internal class Program
{
private static void Main(string[] args)
{
var p = new Program();
p.Run();
}
private void Run()
{
double earthWeight = 175;
double mass = earthWeight / Planet.Earth.SurfaceGravity();
foreach (Planet planet in Enum.GetValues(typeof(Planet))) {
Console.WriteLine("Your weight on {0} is {1}", planet, planet.SurfaceWeight(mass));
}
}
}
public enum Planet
{
Mercury,
Venus,
Earth,
Mars,
Jupiter,
Saturn,
Uranus,
Neptune
}
public static class PlanetExtensions
{
private static readonly Dictionary<Planet, PlanetData> planetMap = new Dictionary<Planet, PlanetData>
{
{Planet.Mercury, new PlanetData(3.303e+23, 2.4397e6)},
{Planet.Venus, new PlanetData(4.869e+24, 6.0518e6)},
{Planet.Earth, new PlanetData(5.976e+24, 6.37814e6)},
{Planet.Mars, new PlanetData(6.421e+23, 3.3972e6)},
{Planet.Jupiter, new PlanetData(1.9e+27, 7.1492e7)},
{Planet.Saturn, new PlanetData(5.688e+26, 6.0268e7)},
{Planet.Uranus, new PlanetData(8.686e+25, 2.5559e7)},
{Planet.Neptune, new PlanetData(1.024e+26, 2.4746e7)}
};
private const double G = 6.67300E-11;
public static double Mass(this Planet planet)
{
return GetPlanetData(planet).Mass;
}
public static double Radius(this Planet planet)
{
return GetPlanetData(planet).Radius;
}
public static double SurfaceGravity(this Planet planet)
{
PlanetData planetData = GetPlanetData(planet);
return G * planetData.Mass / (planetData.Radius * planetData.Radius);
}
public static double SurfaceWeight(this Planet planet, double mass)
{
return mass * SurfaceGravity(planet);
}
private static PlanetData GetPlanetData(Planet planet)
{
if (!planetMap.ContainsKey(planet))
throw new ArgumentOutOfRangeException("planet", "Unknown Planet");
return planetMap[planet];
}
#region Nested type: PlanetData
public class PlanetData
{
public PlanetData(double mass, double radius)
{
Mass = mass;
Radius = radius;
}
public double Mass { get; private set; }
public double Radius { get; private set; }
}
#endregion
}
}

Using an enum as an array index in C#

I want to do the same as in this question, that is:
enum DaysOfTheWeek {Sunday=0, Monday, Tuesday...};
string[] message_array = new string[number_of_items_at_enum];
...
Console.Write(custom_array[(int)DaysOfTheWeek.Sunday]);
however, I would rather have something integral to so, rather than write this error prone code. Is there a built in module in C# that does just this?
If the values of your enum items are contigious, the array method works pretty well. However, in any case, you could use Dictionary<DayOfTheWeek, string> (which is less performant, by the way).
Since C# 7.3 it has been possible to use System.Enum as a constraint on type parameters. So the nasty hacks in the some of the other answers are no longer required.
Here's a very simple ArrayByEum class that does exactly what the question asked.
Note that it will waste space if the enum values are non-contiguous, and won't cope with enum values that are too large for an int. I did say this example was very simple.
/// <summary>An array indexed by an Enum</summary>
/// <typeparam name="T">Type stored in array</typeparam>
/// <typeparam name="U">Indexer Enum type</typeparam>
public class ArrayByEnum<T,U> : IEnumerable where U : Enum // requires C# 7.3 or later
{
private readonly T[] _array;
private readonly int _lower;
public ArrayByEnum()
{
_lower = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Min());
int upper = Convert.ToInt32(Enum.GetValues(typeof(U)).Cast<U>().Max());
_array = new T[1 + upper - _lower];
}
public T this[U key]
{
get { return _array[Convert.ToInt32(key) - _lower]; }
set { _array[Convert.ToInt32(key) - _lower] = value; }
}
public IEnumerator GetEnumerator()
{
return Enum.GetValues(typeof(U)).Cast<U>().Select(i => this[i]).GetEnumerator();
}
}
Usage:
ArrayByEnum<string,MyEnum> myArray = new ArrayByEnum<string,MyEnum>();
myArray[MyEnum.First] = "Hello";
myArray[YourEnum.Other] = "World"; // compiler error
You could make a class or struct that could do the work for you
public class Caster
{
public enum DayOfWeek
{
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
public Caster() {}
public Caster(string[] data) { this.Data = data; }
public string this[DayOfWeek dow]{
get { return this.Data[(int)dow]; }
}
public string[] Data { get; set; }
public static implicit operator string[](Caster caster) { return caster.Data; }
public static implicit operator Caster(string[] data) { return new Caster(data); }
}
class Program
{
static void Main(string[] args)
{
Caster message_array = new string[7];
Console.Write(message_array[Caster.DayOfWeek.Sunday]);
}
}
EDIT
For lack of a better place to put this, I am posting a generic version of the Caster class below. Unfortunately, it relies on runtime checks to enforce TKey as an enum.
public enum DayOfWeek
{
Weekend,
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
public class TypeNotSupportedException : ApplicationException
{
public TypeNotSupportedException(Type type)
: base(string.Format("The type \"{0}\" is not supported in this context.", type.Name))
{
}
}
public class CannotBeIndexerException : ApplicationException
{
public CannotBeIndexerException(Type enumUnderlyingType, Type indexerType)
: base(
string.Format("The base type of the enum (\"{0}\") cannot be safely cast to \"{1}\".",
enumUnderlyingType.Name, indexerType)
)
{
}
}
public class Caster<TKey, TValue>
{
private readonly Type baseEnumType;
public Caster()
{
baseEnumType = typeof(TKey);
if (!baseEnumType.IsEnum)
throw new TypeNotSupportedException(baseEnumType);
}
public Caster(TValue[] data)
: this()
{
Data = data;
}
public TValue this[TKey key]
{
get
{
var enumUnderlyingType = Enum.GetUnderlyingType(baseEnumType);
var intType = typeof(int);
if (!enumUnderlyingType.IsAssignableFrom(intType))
throw new CannotBeIndexerException(enumUnderlyingType, intType);
var index = (int) Enum.Parse(baseEnumType, key.ToString());
return Data[index];
}
}
public TValue[] Data { get; set; }
public static implicit operator TValue[](Caster<TKey, TValue> caster)
{
return caster.Data;
}
public static implicit operator Caster<TKey, TValue>(TValue[] data)
{
return new Caster<TKey, TValue>(data);
}
}
// declaring and using it.
Caster<DayOfWeek, string> messageArray =
new[]
{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
Console.WriteLine(messageArray[DayOfWeek.Sunday]);
Console.WriteLine(messageArray[DayOfWeek.Monday]);
Console.WriteLine(messageArray[DayOfWeek.Tuesday]);
Console.WriteLine(messageArray[DayOfWeek.Wednesday]);
Console.WriteLine(messageArray[DayOfWeek.Thursday]);
Console.WriteLine(messageArray[DayOfWeek.Friday]);
Console.WriteLine(messageArray[DayOfWeek.Saturday]);
Here you go:
string[] message_array = Enum.GetNames(typeof(DaysOfTheWeek));
If you really need the length, then just take the .Length on the result :)
You can get values with:
string[] message_array = Enum.GetValues(typeof(DaysOfTheWeek));
Compact form of enum used as index and assigning whatever type to a Dictionary
and strongly typed. In this case float values are returned but values could be complex Class instances having properties and methods and more:
enum opacityLevel { Min, Default, Max }
private static readonly Dictionary<opacityLevel, float> _oLevels = new Dictionary<opacityLevel, float>
{
{ opacityLevel.Max, 40.0 },
{ opacityLevel.Default, 50.0 },
{ opacityLevel.Min, 100.0 }
};
//Access float value like this
var x = _oLevels[opacitylevel.Default];
If all you need is essentially a map, but don't want to incur performance overhead associated with dictionary lookups, this might work:
public class EnumIndexedArray<TKey, T> : IEnumerable<KeyValuePair<TKey, T>> where TKey : struct
{
public EnumIndexedArray()
{
if (!typeof (TKey).IsEnum) throw new InvalidOperationException("Generic type argument is not an Enum");
var size = Convert.ToInt32(Keys.Max()) + 1;
Values = new T[size];
}
protected T[] Values;
public static IEnumerable<TKey> Keys
{
get { return Enum.GetValues(typeof (TKey)).OfType<TKey>(); }
}
public T this[TKey index]
{
get { return Values[Convert.ToInt32(index)]; }
set { Values[Convert.ToInt32(index)] = value; }
}
private IEnumerable<KeyValuePair<TKey, T>> CreateEnumerable()
{
return Keys.Select(key => new KeyValuePair<TKey, T>(key, Values[Convert.ToInt32(key)]));
}
public IEnumerator<KeyValuePair<TKey, T>> GetEnumerator()
{
return CreateEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
So in your case you could derive:
class DaysOfWeekToStringsMap:EnumIndexedArray<DayOfWeek,string>{};
Usage:
var map = new DaysOfWeekToStringsMap();
//using the Keys static property
foreach(var day in DaysOfWeekToStringsMap.Keys){
map[day] = day.ToString();
}
foreach(var day in DaysOfWeekToStringsMap.Keys){
Console.WriteLine("map[{0}]={1}",day, map[day]);
}
// using iterator
foreach(var value in map){
Console.WriteLine("map[{0}]={1}",value.Key, value.Value);
}
Obviously this implementation is backed by an array, so non-contiguous enums like this:
enum
{
Ok = 1,
NotOk = 1000000
}
would result in excessive memory usage.
If you require maximum possible performance you might want to make it less generic and loose all generic enum handling code I had to use to get it to compile and work. I didn't benchmark this though, so maybe it's no big deal.
Caching the Keys static property might also help.
I realize this is an old question, but there have been a number of comments about the fact that all solutions so far have run-time checks to ensure the data type is an enum. Here is a complete solution (with some examples) of a solution with compile time checks (as well as some comments and discussions from my fellow developers)
//There is no good way to constrain a generic class parameter to an Enum. The hack below does work at compile time,
// though it is convoluted. For examples of how to use the two classes EnumIndexedArray and ObjEnumIndexedArray,
// see AssetClassArray below. Or, e.g.
// EConstraint.EnumIndexedArray<int, YourEnum> x = new EConstraint.EnumIndexedArray<int, YourEnum>();
// See this post
// http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum/29581813#29581813
// and the answer/comments by Julien Lebosquain
public class EConstraint : HackForCompileTimeConstraintOfTEnumToAnEnum<System.Enum> { }//THIS MUST BE THE ONLY IMPLEMENTATION OF THE ABSTRACT HackForCompileTimeConstraintOfTEnumToAnEnum
public abstract class HackForCompileTimeConstraintOfTEnumToAnEnum<SystemEnum> where SystemEnum : class
{
//For object types T, users should use EnumIndexedObjectArray below.
public class EnumIndexedArray<T, TEnum>
where TEnum : struct, SystemEnum
{
//Needs to be public so that we can easily do things like intIndexedArray.data.sum()
// - just not worth writing up all the equivalent methods, and we can't inherit from T[] and guarantee proper initialization.
//Also, note that we cannot use Length here for initialization, even if Length were defined the same as GetNumEnums up to
// static qualification, because we cannot use a non-static for initialization here.
// Since we want Length to be non-static, in keeping with other definitions of the Length property, we define the separate static
// GetNumEnums, and then define the non-static Length in terms of the actual size of the data array, just for clarity,
// safety and certainty (in case someone does something stupid like resizing data).
public T[] data = new T[GetNumEnums()];
//First, a couple of statics allowing easy use of the enums themselves.
public static TEnum[] GetEnums()
{
return (TEnum[])Enum.GetValues(typeof(TEnum));
}
public TEnum[] getEnums()
{
return GetEnums();
}
//Provide a static method of getting the number of enums. The Length property also returns this, but it is not static and cannot be use in many circumstances.
public static int GetNumEnums()
{
return GetEnums().Length;
}
//This should always return the same as GetNumEnums, but is not static and does it in a way that guarantees consistency with the member array.
public int Length { get { return data.Length; } }
//public int Count { get { return data.Length; } }
public EnumIndexedArray() { }
// [WDS 2015-04-17] Remove. This can be dangerous. Just force people to use EnumIndexedArray(T[] inputArray).
// [DIM 2015-04-18] Actually, if you think about it, EnumIndexedArray(T[] inputArray) is just as dangerous:
// For value types, both are fine. For object types, the latter causes each object in the input array to be referenced twice,
// while the former causes the single object t to be multiply referenced. Two references to each of many is no less dangerous
// than 3 or more references to one. So all of these are dangerous for object types.
// We could remove all these ctors from this base class, and create a separate
// EnumIndexedValueArray<T, TEnum> : EnumIndexedArray<T, TEnum> where T: struct ...
// but then specializing to TEnum = AssetClass would have to be done twice below, once for value types and once
// for object types, with a repetition of all the property definitions. Violating the DRY principle that much
// just to protect against stupid usage, clearly documented as dangerous, is not worth it IMHO.
public EnumIndexedArray(T t)
{
int i = Length;
while (--i >= 0)
{
this[i] = t;
}
}
public EnumIndexedArray(T[] inputArray)
{
if (inputArray.Length > Length)
{
throw new Exception(string.Format("Length of enum-indexed array ({0}) to big. Can't be more than {1}.", inputArray.Length, Length));
}
Array.Copy(inputArray, data, inputArray.Length);
}
public EnumIndexedArray(EnumIndexedArray<T, TEnum> inputArray)
{
Array.Copy(inputArray.data, data, data.Length);
}
//Clean data access
public T this[int ac] { get { return data[ac]; } set { data[ac] = value; } }
public T this[TEnum ac] { get { return data[Convert.ToInt32(ac)]; } set { data[Convert.ToInt32(ac)] = value; } }
}
public class EnumIndexedObjectArray<T, TEnum> : EnumIndexedArray<T, TEnum>
where TEnum : struct, SystemEnum
where T : new()
{
public EnumIndexedObjectArray(bool doInitializeWithNewObjects = true)
{
if (doInitializeWithNewObjects)
{
for (int i = Length; i > 0; this[--i] = new T()) ;
}
}
// The other ctor's are dangerous for object arrays
}
public class EnumIndexedArrayComparator<T, TEnum> : EqualityComparer<EnumIndexedArray<T, TEnum>>
where TEnum : struct, SystemEnum
{
private readonly EqualityComparer<T> elementComparer = EqualityComparer<T>.Default;
public override bool Equals(EnumIndexedArray<T, TEnum> lhs, EnumIndexedArray<T, TEnum> rhs)
{
if (lhs == rhs)
return true;
if (lhs == null || rhs == null)
return false;
//These cases should not be possible because of the way these classes are constructed.
// HOWEVER, the data member is public, so somebody _could_ do something stupid and make
// data=null, or make lhs.data == rhs.data, even though lhs!=rhs (above check)
//On the other hand, these are just optimizations, so it won't be an issue if we reomve them anyway,
// Unless someone does something really dumb like setting .data to null or resizing to an incorrect size,
// in which case things will crash, but any developer who does this deserves to have it crash painfully...
//if (lhs.data == rhs.data)
// return true;
//if (lhs.data == null || rhs.data == null)
// return false;
int i = lhs.Length;
//if (rhs.Length != i)
// return false;
while (--i >= 0)
{
if (!elementComparer.Equals(lhs[i], rhs[i]))
return false;
}
return true;
}
public override int GetHashCode(EnumIndexedArray<T, TEnum> enumIndexedArray)
{
//This doesn't work: for two arrays ar1 and ar2, ar1.GetHashCode() != ar2.GetHashCode() even when ar1[i]==ar2[i] for all i (unless of course they are the exact same array object)
//return engineArray.GetHashCode();
//Code taken from comment by Jon Skeet - of course - in http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array
//31 and 17 are used commonly elsewhere, but maybe because everyone is using Skeet's post.
//On the other hand, this is really not very critical.
unchecked
{
int hash = 17;
int i = enumIndexedArray.Length;
while (--i >= 0)
{
hash = hash * 31 + elementComparer.GetHashCode(enumIndexedArray[i]);
}
return hash;
}
}
}
}
//Because of the above hack, this fails at compile time - as it should. It would, otherwise, only fail at run time.
//public class ThisShouldNotCompile : EConstraint.EnumIndexedArray<int, bool>
//{
//}
//An example
public enum AssetClass { Ir, FxFwd, Cm, Eq, FxOpt, Cr };
public class AssetClassArrayComparator<T> : EConstraint.EnumIndexedArrayComparator<T, AssetClass> { }
public class AssetClassIndexedArray<T> : EConstraint.EnumIndexedArray<T, AssetClass>
{
public AssetClassIndexedArray()
{
}
public AssetClassIndexedArray(T t) : base(t)
{
}
public AssetClassIndexedArray(T[] inputArray) : base(inputArray)
{
}
public AssetClassIndexedArray(EConstraint.EnumIndexedArray<T, AssetClass> inputArray) : base(inputArray)
{
}
public T Cm { get { return this[AssetClass.Cm ]; } set { this[AssetClass.Cm ] = value; } }
public T FxFwd { get { return this[AssetClass.FxFwd]; } set { this[AssetClass.FxFwd] = value; } }
public T Ir { get { return this[AssetClass.Ir ]; } set { this[AssetClass.Ir ] = value; } }
public T Eq { get { return this[AssetClass.Eq ]; } set { this[AssetClass.Eq ] = value; } }
public T FxOpt { get { return this[AssetClass.FxOpt]; } set { this[AssetClass.FxOpt] = value; } }
public T Cr { get { return this[AssetClass.Cr ]; } set { this[AssetClass.Cr ] = value; } }
}
//Inherit from AssetClassArray<T>, not EnumIndexedObjectArray<T, AssetClass>, so we get the benefit of the public access getters and setters above
public class AssetClassIndexedObjectArray<T> : AssetClassIndexedArray<T> where T : new()
{
public AssetClassIndexedObjectArray(bool bInitializeWithNewObjects = true)
{
if (bInitializeWithNewObjects)
{
for (int i = Length; i > 0; this[--i] = new T()) ;
}
}
}
EDIT:
If you are using C# 7.3 or later, PLEASE don't use this ugly solution. See Ian Goldby's answer from 2018.
You can always do some extra mapping to get an array index of an enum value in a consistent and defined way:
int ArrayIndexFromDaysOfTheWeekEnum(DaysOfWeek day)
{
switch (day)
{
case DaysOfWeek.Sunday: return 0;
case DaysOfWeek.Monday: return 1;
...
default: throw ...;
}
}
Be as specific as you can. One day someone will modify your enum and the code will fail because the enum's value was (mis)used as an array index.
For future reference the above problem can be summarized as follows:
I come from Delphi where you can define an array as follows:
type
{$SCOPEDENUMS ON}
TDaysOfTheWeek = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TDaysOfTheWeekStrings = array[TDaysOfTheWeek];
Then you can iterate through the array using Min and Max:
for Dow := Min(TDaysOfTheWeek) to Max(TDaysOfTheWeek)
DaysOfTheWeekStrings[Dow] := '';
Though this is quite a contrived example, when you are dealing with array positions later in the code I can just type DaysOfTheWeekStrings[TDaysOfTheWeek.Monday]. This has the advantage of the fact that I should the TDaysOfTheWeek increase in size then I do not have to remember the new size of the array etc..... However back to the C# world. I have found this example C# Enum Array Example.
It was a very good answer by #ian-goldby, but it didn't address the issue raised by #zar-shardan, which is an issue I hit myself. Below is my take on a solution, with a an extension class for converting an IEnumerable, and a test class below that:
/// <summary>
/// An array indexed by an enumerated type instead of an integer
/// </summary>
public class ArrayIndexedByEnum<TKey, TElement> : IEnumerable<TElement> where TKey : Enum
{
private readonly Array _array;
private readonly Dictionary<TKey, TElement> _dictionary;
/// <summary>
/// Creates the initial array, populated with the defaults for TElement
/// </summary>
public ArrayIndexedByEnum()
{
var min = Convert.ToInt64(Enum.GetValues(typeof(TKey)).Cast<TKey>().Min());
var max = Convert.ToInt64(Enum.GetValues(typeof(TKey)).Cast<TKey>().Max());
var size = max - min + 1;
// Check that we aren't creating a ridiculously big array, if we are,
// then use a dictionary instead
if (min >= Int32.MinValue &&
max <= Int32.MaxValue &&
size < Enum.GetValues(typeof(TKey)).Length * 3L)
{
var lowerBound = Convert.ToInt32(min);
var upperBound = Convert.ToInt32(max);
_array = Array.CreateInstance(typeof(TElement), new int[] {(int)size }, new int[] { lowerBound });
}
else
{
_dictionary = new Dictionary<TKey, TElement>();
foreach (var value in Enum.GetValues(typeof(TKey)).Cast<TKey>())
{
_dictionary[value] = default(TElement);
}
}
}
/// <summary>
/// Gets the element by enumerated type
/// </summary>
public TElement this[TKey key]
{
get => (TElement)(_array?.GetValue(Convert.ToInt32(key)) ?? _dictionary[key]);
set
{
if (_array != null)
{
_array.SetValue(value, Convert.ToInt32(key));
}
else
{
_dictionary[key] = value;
}
}
}
/// <summary>
/// Gets a generic enumerator
/// </summary>
public IEnumerator<TElement> GetEnumerator()
{
return Enum.GetValues(typeof(TKey)).Cast<TKey>().Select(k => this[k]).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Here's the extension class:
/// <summary>
/// Extensions for converting IEnumerable<TElement> to ArrayIndexedByEnum
/// </summary>
public static class ArrayIndexedByEnumExtensions
{
/// <summary>
/// Creates a ArrayIndexedByEnumExtensions from an System.Collections.Generic.IEnumerable
/// according to specified key selector and element selector functions.
/// </summary>
public static ArrayIndexedByEnum<TKey, TElement> ToArrayIndexedByEnum<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) where TKey : Enum
{
var array = new ArrayIndexedByEnum<TKey, TElement>();
foreach(var item in source)
{
array[keySelector(item)] = elementSelector(item);
}
return array;
}
/// <summary>
/// Creates a ArrayIndexedByEnum from an System.Collections.Generic.IEnumerable
/// according to a specified key selector function.
/// </summary>
public static ArrayIndexedByEnum<TKey, TSource> ToArrayIndexedByEnum<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) where TKey : Enum
{
return source.ToArrayIndexedByEnum(keySelector, i => i);
}
}
And here are my tests:
[TestClass]
public class ArrayIndexedByEnumUnitTest
{
private enum OddNumbersEnum : UInt16
{
One = 1,
Three = 3,
Five = 5,
Seven = 7,
Nine = 9
}
private enum PowersOf2 : Int64
{
TwoP0 = 1,
TwoP1 = 2,
TwoP2 = 4,
TwoP3 = 8,
TwoP4 = 16,
TwoP5 = 32,
TwoP6 = 64,
TwoP7 = 128,
TwoP8 = 256,
TwoP9 = 512,
TwoP10 = 1_024,
TwoP11 = 2_048,
TwoP12 = 4_096,
TwoP13 = 8_192,
TwoP14 = 16_384,
TwoP15 = 32_768,
TwoP16 = 65_536,
TwoP17 = 131_072,
TwoP18 = 262_144,
TwoP19 = 524_288,
TwoP20 = 1_048_576,
TwoP21 = 2_097_152,
TwoP22 = 4_194_304,
TwoP23 = 8_388_608,
TwoP24 = 16_777_216,
TwoP25 = 33_554_432,
TwoP26 = 67_108_864,
TwoP27 = 134_217_728,
TwoP28 = 268_435_456,
TwoP29 = 536_870_912,
TwoP30 = 1_073_741_824,
TwoP31 = 2_147_483_648,
TwoP32 = 4_294_967_296,
TwoP33 = 8_589_934_592,
TwoP34 = 17_179_869_184,
TwoP35 = 34_359_738_368,
TwoP36 = 68_719_476_736,
TwoP37 = 137_438_953_472,
TwoP38 = 274_877_906_944,
TwoP39 = 549_755_813_888,
TwoP40 = 1_099_511_627_776,
TwoP41 = 2_199_023_255_552,
TwoP42 = 4_398_046_511_104,
TwoP43 = 8_796_093_022_208,
TwoP44 = 17_592_186_044_416,
TwoP45 = 35_184_372_088_832,
TwoP46 = 70_368_744_177_664,
TwoP47 = 140_737_488_355_328,
TwoP48 = 281_474_976_710_656,
TwoP49 = 562_949_953_421_312,
TwoP50 = 1_125_899_906_842_620,
TwoP51 = 2_251_799_813_685_250,
TwoP52 = 4_503_599_627_370_500,
TwoP53 = 9_007_199_254_740_990,
TwoP54 = 18_014_398_509_482_000,
TwoP55 = 36_028_797_018_964_000,
TwoP56 = 72_057_594_037_927_900,
TwoP57 = 144_115_188_075_856_000,
TwoP58 = 288_230_376_151_712_000,
TwoP59 = 576_460_752_303_423_000,
TwoP60 = 1_152_921_504_606_850_000,
}
[TestMethod]
public void TestSimpleArray()
{
var array = new ArrayIndexedByEnum<OddNumbersEnum, string>();
var odds = Enum.GetValues(typeof(OddNumbersEnum)).Cast<OddNumbersEnum>().ToList();
// Store all the values
foreach (var odd in odds)
{
array[odd] = odd.ToString();
}
// Check the retrieved values are the same as what was stored
foreach (var odd in odds)
{
Assert.AreEqual(odd.ToString(), array[odd]);
}
}
[TestMethod]
public void TestPossiblyHugeArray()
{
var array = new ArrayIndexedByEnum<PowersOf2, string>();
var powersOf2s = Enum.GetValues(typeof(PowersOf2)).Cast<PowersOf2>().ToList();
// Store all the values
foreach (var powerOf2 in powersOf2s)
{
array[powerOf2] = powerOf2.ToString();
}
// Check the retrieved values are the same as what was stored
foreach (var powerOf2 in powersOf2s)
{
Assert.AreEqual(powerOf2.ToString(), array[powerOf2]);
}
}
}

Categories