You are on page 1of 17

Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

C# Corner Jabalpur User Group Meet: 19 July 2014

HTML 5 IndexedDB Database

ARTICLE 01 HTML 5 IndexedDB Database

CreaƟng Generic Enums using C# 02 CRUD OperaƟons in MVC 5 Using WebAPI


Posted by Vulpes in ArƟcles | Visual C# on October 11, 2011 With AngularJS
Login Form in 3-Tier Architecture With
An enum variable can then be set to any one of these constants or (in the case of ‘Flags’
enums) to a meaningful combinaƟon of them.
03 Lock in ASP.Net
2 20,419
ASP.Net MVC 4 Crunch
Tweet 0 Share Like 0 20336 2 0
04
Diving Into OOP (Day 5): All About Access
Reader Level:
05 Modifiers in C# (C# Modifiers/Sealed
/Constants/Readonly Fields)
Download Files: genericenum.zip Lambda Expression in 15 Minutes
06
CreaƟng Windows Phone and Window 8.1
What's wrong with the enums we have now? 07 ApplicaƟons Using MicrosoŌ App Studio
As I'm sure everyone reading this will know, enums are simple lightweight types that enable you to Parallel Task in .Net 4.0
define a set of named integral constants. An enum variable can then be set to any one of these 08
constants or (in the case of 'Flags' enums) to a meaningful combination of them.
Master Pages in C# Windows Forms
As such, enums perform a useful role in C# programming. However, they do have a number of
09 ApplicaƟons
shortcomings:
Early Look at Visual Studio 2014 CTP 2
10
Their underlying types are always integers (int, long, byte etc). You can't have an enum with
an underlying type of double, decimal, string or DateTime for example.
View All
You can assign any value of the underlying type to an enum variable, whether it corresponds
to one of the defined constants or not. Follow @csharpcorner 6,051 followers

You can't add methods, properties or other types of member to an enum, though you can add Find us on Facebook
methods to them indirectly using 'extension methods'. See my earlier article (http://www.c-
sharpcorner.com/UploadFile/b942f9/5951/). C# Corner
Like
Enum values often have to be cast back to their underlying types, which can be a nuisance in
some scenarios - for example when they are used to index a collection. 48,996 people like C# Corner.

What can we do about these shortcomings?

In this article, I'd like to demonstrate how we can build a 'generic' enum using C# which:

Can have any underlying type, except the type of the generic enum itself.

Can only be assigned values which are actually defined.


Facebook social plugin

Can have other members apart from the enum constants, though needn't have.

Has values which never have to be cast back to their underlying type, because they are in fact
values of that type already.

All 'ordinary' enums inherit from an abstract class called System.Enum which itself inherits from
System.ValueType. Enums are therefore value types themselves.

The System.Enum class contains a number of static methods which can be applied to any enum,
such as GetNames, GetValues, IsDefined, Parse and TryParse.

Enums also have some support from the C# language itself which conceals the way they are actually
implemented from the programmer. In particular, all enums have a public instance field of their
underlying type called 'value_' which contains their current value but is hidden from view except
when you use reflection:

using System;
using System.Reflection;

enum Colors
{
Red,
Green,

1 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

Blue
}

class Test
{
static void Main()
{
Colors c = Colors.Blue;
Type t = typeof(Colors);
BindingFlags bf = BindingFlags.Instance | BindingFlags.Public;
FieldInfo fi = t.GetField("value__", bf);
int val = (int)fi.GetValue(c);
Console.WriteLine((Colors)val); // Blue
Console.ReadKey();
}
}

Our generic enum will also rely on an abstract class to provide basic functionality and will itself need
to be a class (i.e. a reference type), because it's not possible for custom value types to inherit from
anything other than System.ValueType or System.Enum.

The abstract base class also needs to be a generic class which takes two type parameters: the type
of the 'enum' itself and its underlying type, so that it can use reflection to obtain the names of the
constants and their values.

However, the 'Value' property now needs to be explicit because, of course, our generic enum will no
longer have any language support. There are also explicit 'Name' and 'Index' properties. The latter
records the position of each defined value in the 'names' or 'values' collection and will normally
correspond to the order in which they're defined. The only instance field which a generic enum needs
and has is the _index field (exposed by the Index property) because the other properties are
deducible from this.

As with ordinary enums, it is possible for more than one constant to have the same value. However,
combinations of values are not supported.

How is the abstract base class implemented?

The code for the GenericEnum base class is included in the download which accompanies this article
but, for the benefit of those who don't like downloading anything from the Internet, here it is 'in the
flesh' :-

using System;
using System.Collections.Generic;
using System.Reflection;

public abstract class GenericEnum<T, U> where T : GenericEnum<T, U>, new()


{
static readonly List<string> names;

static readonly List<U> values;

static bool allowInstanceExceptions;

static GenericEnum()
{
Type t = typeof(T);
Type u = typeof(U);
if (t == u) throw new InvalidOperationException(String.Format("{0} and its underlying type
cannot be the same", t.Name));
BindingFlags bf = BindingFlags.Static | BindingFlags.Public;
FieldInfo[] fia = t.GetFields(bf);
names = new List<string>();
values = new List<U>();
for (int i = 0; i < fia.Length; i++)
{
if (fia[i].FieldType == u && (fia[i].IsLiteral || fia[i].IsInitOnly))
{
names.Add(fia[i].Name);
values.Add((U)fia[i].GetValue(null));
}
}
if (names.Count == 0) throw new InvalidOperationException(String.Format("{0} has no
suitable fields", t.Name));
}

public static bool AllowInstanceExceptions


{
get { return allowInstanceExceptions; }
set { allowInstanceExceptions = value; }
}

public static string[] GetNames()


{

2 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

return names.ToArray();
}

public static string[] GetNames(U value)


{
List<string> nameList = new List<string>();
for (int i = 0; i < values.Count; i++)
{
if (values[i].Equals(value)) nameList.Add(names[i]);
}
return nameList.ToArray();
}

public static U[] GetValues()


{
return values.ToArray();
}

public static int[] GetIndices(U value)


{
List<int> indexList = new List<int>();
for (int i = 0; i < values.Count; i++)
{
if (values[i].Equals(value)) indexList.Add(i);
}
return indexList.ToArray();
}

public static int IndexOf(string name)


{
return names.IndexOf(name);
}

public static U ValueOf(string name)


{
int index = names.IndexOf(name);
if (index >= 0)
{
return values[index];
}
throw new ArgumentException(String.Format("'{0}' is not a defined name of {1}", name,
typeof(T).Name));
}

public static string FirstNameWith(U value)


{
int index = values.IndexOf(value);
if (index >= 0)
{
return names[index];
}
throw new ArgumentException(String.Format("'{0}' is not a defined value of {1}", value,
typeof(T).Name));
}

public static int FirstIndexWith(U value)


{
int index = values.IndexOf(value);
if (index >= 0)
{
return index;
}
throw new ArgumentException(String.Format("'{0}' is not a defined value of {1}", value,
typeof(T).Name));
}

public static string NameAt(int index)


{
if (index >= 0 && index < Count)
{
return names[index];
}
throw new IndexOutOfRangeException(String.Format("Index must be between 0 and {0}",
Count - 1));
}

public static U ValueAt(int index)


{
if (index >= 0 && index < Count)
{
return values[index];
}
throw new IndexOutOfRangeException(String.Format("Index must be between 0 and {0}",
Count - 1));

3 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

public static Type UnderlyingType


{
get { return typeof(U); }
}

public static int Count


{
get { return names.Count; }
}

public static bool IsDefinedName(string name)


{
if (names.IndexOf(name) >= 0) return true;
return false;
}

public static bool IsDefinedValue(U value)


{
if (values.IndexOf(value) >= 0) return true;
return false;
}

public static bool IsDefinedIndex(int index)


{
if (index >= 0 && index < Count) return true;
return false;
}

public static T ByName(string name)


{
if (!IsDefinedName(name))
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("'{0}' is not a
defined name of {1}", name, typeof(T).Name));
return null;
}
T t = new T();
t._index = names.IndexOf(name);
return t;
}

public static T ByValue(U value)


{
if (!IsDefinedValue(value))
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("'{0}' is not a
defined value of {1}", value, typeof(T).Name));
return null;
}
T t = new T();
t._index = values.IndexOf(value);
return t;
}

public static T ByIndex(int index)


{
if (index < 0 || index >= Count)
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("Index must be
between 0 and {0}", Count - 1));
return null;
}
T t = new T();
t._index = index;
return t;
}

protected int _index;

public int Index


{
get { return _index; }
set
{
if (value < 0 || value >= Count)
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("Index must
be between 0 and {0}", Count - 1));
return;
}
_index = value;
}

4 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

public string Name


{
get { return names[_index]; }
set
{
int index = names.IndexOf(value);
if (index == -1)
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("'{0}' is not a
defined name of {1}", value, typeof(T).Name));
return;
}
_index = index;
}
}

public U Value
{
get { return values[_index]; }
set
{
int index = values.IndexOf(value);
if (index == -1)
{
if (allowInstanceExceptions) throw new ArgumentException(String.Format("'{0}' is not a
defined value of {1}", value, typeof(T).Name));
return;
}
_index = index;
}
}

public override string ToString()


{
return names[_index];
}
}

How can I use this base class to create and instantiate a generic enum?

You first need to declare your 'enum' class and specify that it inherits from the GenericEnum base
class. For example:

class Fractions : GenericEnum<Fractions, double>


{
public static readonly double Sixth = 1.0 / 6.0;
public static readonly double Fifth = 0.2;
public static readonly double Quarter = 0.25;
public static readonly double Third = 1.0 / 3.0;
public static readonly double Half = 0.5;

public double FractionOf(double amount)


{
return this.Value * amount;
}
}

class Seasons : GenericEnum<Seasons, DateTime>


{
public static readonly DateTime Spring = new DateTime(2011, 3, 1);
public static readonly DateTime Summer = new DateTime(2011, 6, 1);
public static readonly DateTime Autumn = new DateTime(2011, 9, 1);
public static readonly DateTime Winter = new DateTime(2011, 12, 1);
}

public class Planets : GenericEnum<Planets, Planet>


{
public static readonly Planet Mercury = new Planet(3.303e+23, 2.4397e6);
public static readonly Planet Venus = new Planet(4.869e+24, 6.0518e6);
public static readonly Planet Earth = new Planet(5.976e+24, 6.37814e6);
public static readonly Planet Mars = new Planet(6.421e+23, 3.3972e6);
public static readonly Planet Jupiter = new Planet(1.9e+27, 7.1492e7);
public static readonly Planet Saturn = new Planet(5.688e+26, 6.0268e7);
public static readonly Planet Uranus = new Planet(8.686e+25, 2.5559e7);
public static readonly Planet Neptune = new Planet(1.024e+26, 2.4746e7);

public bool IsCloserToSunThan(Planets p)


{
if (this.Index < p.Index) return true;
return false;
}

5 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

public class Planet


{
public double Mass { get; private set; } // in kilograms
public double Radius { get; private set; } // in meters

public Planet(double mass, double radius)


{
Mass = mass;
Radius = radius;
}

// universal gravitational constant (m^3 kg^-1 s^-2)


public static double G = 6.67300E-11;

public double SurfaceGravity()


{
return G * Mass / (Radius * Radius);
}

public double SurfaceWeight(double otherMass)


{
return otherMass * SurfaceGravity();
}
}

Incidentally, the code for the Planets and Planet classes follows closely the code for a well known
example of an enum in the Java programming language.

For the defined values, you can use either compile time constants (for those types which support
them) or static readonly fields which are not evaluated until runtime and then remain constant.
Either will be acceptable to the base class though it's recommended that you use one or the other
(but not both!) when defining a particular class. All such constants must be public.

You can instantiate a generic enum either by name, index or value. As I dislike throwing exceptions
when there is a reasonably graceful alternative, if you try to instantiate a generic enum with an
invalid parameter, then null is returned. Similarly, if you try to change the value of a generic enum
to an invalid value, then the existing value is left unchanged. Anyone who would prefer to throw
exceptions in these cases, can do so by setting the generic enum's static property,
AllowInstanceExceptions, to true; it is false by default.

You can also use the constructor to instantiate an enum in which case an enum instance is created
with an initial value corresponding to an index of zero.

Here's an example which illustrates these points and also shows how some of the static members in
the base class are used:

class Test
{
static void Main()
{
Console.Clear();
Fractions f = new Fractions();
Console.WriteLine(f); // Sixth
f.Value = Fractions.Fifth;
Console.WriteLine(f.Index); // 1
Fractions f2 = Fractions.ByName("Third");
Console.WriteLine(f2.FractionOf(30)); // 10
f2.Index = 4;
Console.WriteLine(f2); // Half
string name = Fractions.FirstNameWith(0.25);
Console.WriteLine(name); // Quarter
Fractions f3 = Fractions.ByName("Tenth"); // no exception by default
Console.WriteLine(f3 == null); // true
f3 = Fractions.ByValue(1.0 / 3.0);
Console.WriteLine(f3); // Third

Console.WriteLine();

foreach (string season in Seasons.GetNames())


{
Console.WriteLine("{0} starts on {1}", season, Seasons.ValueOf(season).ToString("d
MMMM"));
}

Console.WriteLine();

double earthWeight = 80;


double mass = earthWeight / Planets.Earth.SurfaceGravity();
foreach (Planet p in Planets.GetValues())
{
Console.WriteLine("Weight on {0} is {1:F2} kg", Planets.FirstNameWith(p),
p.SurfaceWeight(mass));

6 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

Console.WriteLine();

Planets mercury = Planets.ByName("Mercury");


Planets earth = Planets.ByIndex(2);
Planets jupiter = new Planets();
jupiter.Value = Planets.Jupiter;
Console.WriteLine("It is {0} that Mercury is closer to the Sun than the Earth",
mercury.IsCloserToSunThan(earth)); // True
Console.WriteLine("It is {0} that Jupiter is closer to the Sun than the Earth",
jupiter.IsCloserToSunThan(earth)); // False
Console.ReadKey();
}
}

Do generic enums have any shortcomings or limitations?

Inevitably, they do though I don't regard any of them as being particularly serious:

Although they have a small memory footprint (4 bytes for the _index field), they have to be
reference types rather than value types for the reasons already discussed. This means that
they will require heap memory, including overhead, of 12 bytes (32 bit system) or 20 bytes
(64 bit system) and will need to be tracked by the garbage collector. Memory will also be
needed for the static fields in the abstract base class.

Due to the lack of built-in language support, instantiation is more awkward than one would
like; if you instantiate by name, you have to use a string.

There is no 'Flags' enum support where the underlying type is integral, as I concluded that
trying to replicate this would be too messy, given that it would be meaningless for
non-integral types.

It's not possible to use the type of the generic enum itself as its underlying type due to
initialization problems. The base class's static constructor inevitably runs before the enum's
static readonly members have been initialized which means that they are all still null when
they are being reflected upon.

Even though any other type can be used as the underlying type, in practice you might want to
restrict this to structs and immutable reference types. If you use a mutable reference type,
then the user could read the 'constant value' and change the state of the object itself.

Any kind of member can be added to a generic enum except, of course, for public constants or
static readonly fields of the underlying type.

Should I always prefer generic enums to ordinary enums in future?

No, ordinary enums are fine for most purposes and are very lightweight.

However, if you wish to use a non-integral underlying type or to include some other members, then
I hope that you will consider using a generic enum instead.

ArƟcle Extensions
Contents added by Chris on Apr 07, 2013
POST 2013-04-07/8

The sample applicaƟon demonstraƟng the usage:

static class StartUp


{

static internal void Main(string[] args)


{
Fraction myFraction = Fraction.Fifth;
bool myBoolean = Fraction.CaseSensitive;
Int32 myIndex = myFraction.Index;
Fraction myFraction2 = Fraction.GetMemberByName("fIfTh");
HourFormat myHourFormat = HourFormat.hh;
HourFormat myHourFormat2 = HourFormat.GetMemberByName("HH");
HourFormat myHourFormat3 = HourFormat.GetMemberByName("hh");
myBoolean = HourFormat.CaseSensitive;

double myDouble = myFraction.FractionOf(25.0);


string myString = null;
myString = myFraction.ToString();
myBoolean = object.ReferenceEquals(myFraction, myFraction2);

myFraction2 = Fraction.GetMemberByName("Third");
myDouble = myFraction2.FractionOf(30);

try {
EvilEnum myEvilEnum = EvilEnum.Hacker;
myString = myEvilEnum.ToString();
} catch (Exception ex) {

7 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

myString = ex.Message;
}

foreach (Season mySeason in Season.GetMembers()) {


myString = string.Format("{0} starts on {1}", mySeason.Name, mySeason.Value);
}

//Implicite cast
Planet myPlanet = PlanetEnum.Earth;
double myEarthWeight = 80;
double myMass = myEarthWeight / myPlanet.SurfaceGravity;
foreach (PlanetEnum myPlanet2 in PlanetEnum.GetMembers()) {
myString = string.Format("My weight on {0} is {1:F2} kg", myPlanet2, myPlanet2.Value.GetSurfaceWeight(myMass));
}
PlanetEnum myMercury = PlanetEnum.Mercury;
PlanetEnum myEarth = PlanetEnum.Earth;
PlanetEnum myJupiter = PlanetEnum.Jupiter;
myString = string.Format("It is {0} that {1} is closer to the Sun than {2}", myMercury.IsCloserToSunThan(myEarth), myMercury, myE
//True
myString = string.Format("It is {0} that {1} is closer to the Sun than {2}", myJupiter.IsCloserToSunThan(myEarth), myJupiter, myE
//False

I hope that this code may be helpful for you. Enjoy it.

Best regards
Chris

END OF POST

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/7

The sample enum EvilEnum (new):


(to prove that an enum is not allowed to corrupt another enum)

public class EvilEnum : CustomEnum<Season, DateTime>


{

//Constructors

private EvilEnum(DateTime aValue) : base(aValue, false, new EvilComparer())


{
}

//Public Fields

public static readonly EvilEnum Hacker = new EvilEnum(DateTime.Now);

public static readonly EvilEnum CopyPasteError = new EvilEnum(DateTime.Now.AddMonths(-1));

//**************************************************************************
// INNER CLASS: EvilComparer
//**************************************************************************

private class EvilComparer : IEqualityComparer<System.DateTime>


{

public bool Equals1(System.DateTime x, System.DateTime y)


{
return ((DateTime.Now.Second % 2) == 0);
}
bool System.Collections.Generic.IEqualityComparer<System.DateTime>.Equals(System.DateTime x, System.DateTime y)
{
return Equals1(x, y);
}

public int GetHashCode1(System.DateTime obj)


{
return DateTime.Now.Second;
}
int System.Collections.Generic.IEqualityComparer<System.DateTime>.GetHashCode(System.DateTime obj)
{
return GetHashCode1(obj);
}

ConƟnue to read at

8 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

POST 2013-04-07/8

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/6

The sample enum HourFormat (new):


(to prove the case-sensiƟve/case-insensiƟve automaƟsm)

public class HourFormat : CustomEnum<HourFormat, string>


{

//Constructors

private HourFormat(string aValue) : base(aValue)


{
}

//Public Fields

public static readonly HourFormat hh = new HourFormat("hh");


public static readonly HourFormat HH = new HourFormat("HH");
public static readonly HourFormat h = new HourFormat("h");
public static readonly HourFormat H = new HourFormat("H");

ConƟnue to read at
POST 2013-04-07/7

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/5

The sample enum Season (seasons):


(note the fiŌh season is a geƩer property)

public class Season : CustomEnum<Season, DateTime>


{

//Private Fields

[DebuggerBrowsable(DebuggerBrowsableState.Never)]

private static Season _FirstAccessSeason;


//Constructors

private Season(Int32 aDay, Int32 aMonth) : base(new DateTime(DateTime.Today.Year, aMonth, aDay))


{
}

private Season(DateTime aDate) : base(aDate)


{
}

//Public Fields

public static readonly Season Spring = new Season(1, 3);


public static readonly Season Summer = new Season(1, 6);
public static readonly Season Autumn = new Season(1, 9);

public static readonly Season Winter = new Season(1, 12);


/// <summary>It's not very common but possible to declare a custom enum value as read-only property.</summary>
public static Season ProgramInstallationSeason {
get {
Season myResult = _FirstAccessSeason;
if ((myResult == null)) {
DateTime myDate = QueryRegistryForInstallationDate();
switch (myDate.Month) {
case 12:
case 1:
case 2:
myResult = new Season(Winter);
break;
case 3:
case 4:
case 5:
myResult = new Season(Spring);
break;
case 6:
case 7:
case 8:
myResult = new Season(Summer);
break;
case 9:

9 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

case 10:
case 11:
myResult = new Season(Autumn);
break;
default:
throw new NotSupportedException("What kind of strange month is that, are you using java?");
}
_FirstAccessSeason = myResult;
}
return myResult;
}
}

private static DateTime QueryRegistryForInstallationDate()


{
//Fake
return DateTime.Today.AddDays(-100);
}

ConƟnue to read at
POST 2013-04-07/6

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/4

The sample enum PlanetEnum (planets):

public sealed class PlanetEnum : CustomEnum<PlanetEnum, Planet>


{

//Constructors

private PlanetEnum(double massInKg, double radiusInMeters) : base(new Planet(massInKg, radiusInMeters))


{
}

//Public Fields

public static readonly PlanetEnum Mercury = new PlanetEnum(3.303E+23, 2439700.0);


public static readonly PlanetEnum Venus = new PlanetEnum(4.869E+24, 6051800.0);
public static readonly PlanetEnum Earth = new PlanetEnum(5.976E+24, 6378140.0);
public static readonly PlanetEnum Mars = new PlanetEnum(6.421E+23, 3397200.0);
public static readonly PlanetEnum Jupiter = new PlanetEnum(1.9E+27, 71492000.0);
public static readonly PlanetEnum Saturn = new PlanetEnum(5.688E+26, 60268000.0);
public static readonly PlanetEnum Uranus = new PlanetEnum(8.686E+25, 25559000.0);

public static readonly PlanetEnum Neptune = new PlanetEnum(1.024E+26, 24746000.0);


//Public Methods

public bool IsCloserToSunThan(PlanetEnum planet)


{
return (this.Index < planet.Index);
}

The helper class Planet:

public class Planet


{

//Private Fields
private readonly double _MassInKg;
private readonly double _RadiusInMeters;

private const double _G = 6.673E-11;


//Constructors

public Planet(double massInKg, double radiusInMeters)


{
_MassInKg = massInKg;
_RadiusInMeters = radiusInMeters;
}

//Public Properties

/// <summary>The mass of the planet in kilograms.</summary>


public double MassInKg {
get { return _MassInKg; }
}

10 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

/// <summary>The average equatorial radius of the planet in meters.</summary>


public double RadiusInMeters {
get { return _RadiusInMeters; }
}

/// <summary>Universal gravitational constant that currently in our univers is 6.67300E-11 (m^3 kg^-1 s^-2).
/// But of course this class is not limited to only our univers...</summary>
public virtual double G {
get { return _G; }
}

public double SurfaceGravity {


get { return (G * MassInKg) / (RadiusInMeters * RadiusInMeters); }
}

public double GetSurfaceWeight(double otherMass) {


return (otherMass * SurfaceGravity);
}

ConƟnue to read at
POST 2013-04-07/5

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/3

The sample enum FracƟon (fracƟons):

public sealed class Fraction : CustomEnum<Fraction, double>


{

//Constructors

private Fraction(double aValue) : base(aValue)


{
}

//Public Fields

public static readonly Fraction Sixth = new Fraction(1.0 / 6.0);


public static readonly Fraction Fifth = new Fraction(0.2);
public static readonly Fraction Quarter = new Fraction(0.25);
public static readonly Fraction Third = new Fraction(1.0 / 3.0);

public static readonly Fraction Half = new Fraction(0.5);


//Public Methods

public double FractionOf(double amount)


{
return (this.Value * amount);
}

ConƟnue to read at
POST 2013-04-07/4

Contents added by Chris on Apr 07, 2013


POST 2013-04-07/2

But yeah, I know, all you want is code, here it is:

The base class CustomEnum (genericEnum):

/// <summary>Base-class for custom enumerations.</summary>


/// <typeparam name="TEnum">The type of your subclass</typeparam>
/// <typeparam name="TValue">The type of the value</typeparam>
/// <remarks>Hints for implementors: You must ensure that only one instance of each enum-value exists. This is easily reached by
/// declaring the constructor(s) private and/or sealing the class and exposing the enum-values as static fields. If you are
/// implementing them through static getter properties, make sure lazzy initialization is used and that not a new instances
/// is returned with every call.</remarks>
[DebuggerDisplay("{_Name} ({_Value})")]
public abstract class CustomEnum<TEnum, TValue> : IEquatable<TValue> where TEnum : CustomEnum<TEnum, TValue>
{

//Private Fields

[DebuggerBrowsable(DebuggerBrowsableState.Never)]

11 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

private static TEnum[] _Members;


[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static bool? _CaseSensitive;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private static IEqualityComparer<TValue> _ValueComparer;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
//Hint: assign only in GetMemberByName(..)!
private static IEqualityComparer<string> _StringComparer;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string _Name;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private TValue _Value;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Int32 _Index = -1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]

private static bool _IsFirstInstance = true;


//Constructors

/// <summary>Called by implementors to create a new instance of TEnum. Important: Seal your class and/or make your constructors private!<
protected CustomEnum(TValue aValue) : this(aValue, null, null)
{
}

/// <summary>Called by implementors to create a new instance of TEnum. Important: Seal your class and/or make your constructors private!<
protected CustomEnum(TValue aValue, bool? caseSensitive, IEqualityComparer<TValue> aValueComparer)
{
//Assign instance value
_Value = aValue;
//Make sure no evil cross subclass is changing our static variable
if ((!typeof(TEnum).IsAssignableFrom(this.GetType()))) {
throw new InvalidOperationException("Internal error in " + this.GetType().Name + "! Change the first type parameter from
}
//Make sure only the first subclass is affecting our static variables
if ((_IsFirstInstance)) {
_IsFirstInstance = false;
//Assign static variables
_CaseSensitive = caseSensitive;
_ValueComparer = aValueComparer;
}
}

//Public Properties

/// <summary>Returns the name of this CustomEnum-value.</summary>


public string Name {
get {
//Check whether the name is already assigned
string myResult = _Name;
if ((myResult == null)) {
InitMembers();
myResult = _Name;
}
//Return the name
return myResult;
}
}

/// <summary>Returns the value of this CustomEnum-value.</summary>


public TValue Value {
get { return _Value; }
}

/// <summary>Returns the index position of this CustomEnum-value.</summary>


public Int32 Index {
get {
//Check whether the index is already assigned
Int32 myResult = _Index;
if ((myResult < 0)) {
InitMembers();
myResult = _Index;
}
//Return the index
return myResult;
}
}

//Public Class Properties

/// <summary>Returns whether the names passed to function <see cref="GetMemberByName(string)" /> are treated case sensitive
/// or not (using <see cref="StringComparer.Ordinal" /> resp. <see cref="StringComparer.OrdinalIgnoreCase" />).
/// The default behavior is that they are case-insensitive except there would be two or more entries that would
/// cause an ambiguity.</summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static bool CaseSensitive {
get {
bool? myResult = _CaseSensitive;
if ((myResult == null)) {

12 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

myResult = InitCaseSensitive();
_CaseSensitive = myResult;
}
return myResult.Value;
}
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static Type UnderlyingType {
get { return typeof(TValue); }
}

//Public Class Functions

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum[] GetMembers()
{
TEnum[] myMembers = Members;
TEnum[] myResult = new TEnum[myMembers.Length];
Array.Copy(myMembers, myResult, myMembers.Length);
return myResult;
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static string[] GetNames()
{
TEnum[] myMembers = Members;
string[] myResult = new string[myMembers.Length];
for (Int32 i = 0; i <= myMembers.Length - 1; i++) {
myResult[i] = myMembers[i]._Name;
}
return myResult;
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TValue[] GetValues()
{
TEnum[] myMembers = Members;
TValue[] myResult = new TValue[myMembers.Length];
for (Int32 i = 0; i <= myMembers.Length - 1; i++) {
myResult[i] = myMembers[i]._Value;
}
return myResult;
}

/// <summary>Returns the member of the given name or null if not found. You can check through property
/// <see cref="CaseSensitive" /> whether <paramref name="aName" /> is treated case-sensitive or not.
/// If aName is null, an ArgumentNullException is thrown. If the subclass is incorrectly implemented
/// and has duplicate names defined, a DuplicateNameException is thrown.</summary>
/// <param name="aName">The name to look up.</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>A full duplicate check is performed the first time this method is called.</remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByName(string aName)
{
//Check the argument
if ((aName == null))
throw new ArgumentNullException("aName");
//Get the members
TEnum[] myMembers = Members;
//Initialize comparer and check whole table for duplicates
IEqualityComparer<string> myComparer = _StringComparer;
if ((myComparer == null)) {
//Determine the comparer
myComparer = CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
//Check for duplicates (happens if the constructor explicitely sets the case-insensitive flag but has two fields that dif
//or if the enum has multiple hierarchical subclasses and the overwritten properties do not hide the parent ones (like th
//in JScript.NET).
if ((HasDuplicateNames(myComparer))) {
throw new DuplicateNameException("Internal error in " + typeof(TEnum).Name + ", the member names are not unique!"
}
//If everything is okay, assign the comparer
_StringComparer = myComparer;
}
//Return the first name found
foreach (TEnum myMember in myMembers) {
if ((myComparer.Equals(myMember._Name, aName)))
return myMember;
}
//Otherwise return null
return null;
}

/// <summary>Returns the member of the given name or null if not found using the given comparer
/// to perform the comparison. If there are no special reasons don't use this method but the
/// one without the comparer overload as it is optimized to perform the duplicate check only
/// once and not every time the method is used.</summary>
/// <param name="aName">The name to look up.</param>

13 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

/// <param name="aComparer">The comparer to use for the equality comparison of the strings.</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>The whole list is searched every time this method is called to ensure aName is unique according to the given comparer.</rema
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByName(string aName, IEqualityComparer<string> aComparer)
{
//Check the argument
if ((aName == null))
throw new ArgumentNullException("aName");
//Use optimized method if possible
if ((aComparer == null))
return GetMemberByName(aName);
if ((object.Equals(aComparer, _StringComparer)))
return GetMemberByName(aName);
//Get the members
TEnum[] myMembers = Members;
//Get the first found member but continue looping
TEnum myResult = null;
foreach (TEnum myMember in myMembers) {
if ((aComparer.Equals(myMember._Name, aName))) {
if ((myResult == null)) {
myResult = myMember;
} else {
throw new DuplicateNameException("The name \"" + aName + "\" is not unique according to the given compare
}
}
}
//Return the result
return myResult;
}

/// <summary>Returns the first member of the given value or null if not found. The value to look up
/// may also be null. This function uses the value comparer defined by the enumeration (or Object's
/// equal method if not defined) to perform the comparison.</summary>
/// <param name="aValue">The value to look up (null is valid be null).</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>The duplicate check is performed only the first time if successful.</remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByValue(TValue aValue)
{
return GetMemberByValue(aValue, _ValueComparer);
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByValue(TValue aValue, IEqualityComparer<TValue> aValueComparer)
{
//Get the members
TEnum[] myMembers = Members;
//Immediately return the member if the values equal
if ((aValueComparer == null)) {
//Using the default comparer
foreach (TEnum myMember in myMembers) {
if ((object.Equals(myMember._Value, aValue)))
return myMember;
}
} else {
//Using the given comparer
foreach (TEnum myMember in myMembers) {
//Immediately return the member if the values equal
if ((aValueComparer.Equals(myMember._Value, aValue)))
return myMember;
}
}
//Return null if not found
return null;
}

/// <summary>Always implicitely allow to cast into the value type (like this is the case with standard
/// .NET enumerations).</summary>
public static implicit operator TValue(CustomEnum<TEnum, TValue> value)
{
if ((value == null))
throw new ArgumentNullException("value");
return value.Value;
}

//Framework Properties

public override string ToString()


{
return Name;
}

//Framework Methods

[EditorBrowsable(EditorBrowsableState.Advanced)]
public virtual bool Equals(TValue other)
{

14 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

IEqualityComparer<TValue> myComparer = _ValueComparer;


if ((myComparer == null)) {
return object.Equals(this._Value, other);
}
return myComparer.Equals(this._Value, other);
}

//Private Properties

private static TEnum[] Members {


get {
TEnum[] myResult = _Members;
if ((myResult == null)) {
myResult = PrivateGetMembers();
_Members = myResult;
}
return myResult;
}
}

//Private Methods

private static void InitMembers()


{
TEnum[] myDummy = Members;
}

private static TEnum[] PrivateGetMembers()


{
List<TEnum> myList = new List<TEnum>();
AddFields(myList);
AddGetters(myList);
return myList.ToArray();
}

private static void AddFields(List<TEnum> aList)


{
BindingFlags myFlags = (BindingFlags.Static | BindingFlags.Public);
FieldInfo[] myFields = typeof(TEnum).GetFields(myFlags);
foreach (FieldInfo myField in myFields) {
if ((object.ReferenceEquals(myField.FieldType, typeof(TEnum))) && (myField.IsLiteral || myField.IsInitOnly)) {
TEnum myEntry = (TEnum)myField.GetValue(null);
AddEntry(myEntry, myField.Name, aList);
}
}
}

private static void AddGetters(List<TEnum> aList)


{
BindingFlags myFlags = (BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty);
PropertyInfo[] myProperties = typeof(TEnum).GetProperties(myFlags);
foreach (PropertyInfo myProperty in myProperties) {
if ((object.ReferenceEquals(myProperty.PropertyType, typeof(TEnum))) && (myProperty.CanRead) && (!myProperty.CanWrite) &&
//Invoke the property twice and check whether the same instance is returned (it is a requirement)
TEnum myEntry = (TEnum)myProperty.GetValue(null, null);
TEnum myEntry2 = (TEnum)myProperty.GetValue(null, null);
if ((!object.ReferenceEquals(myEntry, myEntry2))) {
throw new InvalidOperationException("Internal error in " + typeof(TEnum).Name + "! Property " + myPropert
}
//Add the entry
AddEntry(myEntry, myProperty.Name, aList);
}
}
}

private static void AddEntry(TEnum aValue, string aName, List<TEnum> aList)


{
//Check for instance conflicts
if ((aValue._Name != null)) {
throw new InvalidOperationException("Internal error in " + typeof(TEnum).Name + "! It's invalid to assign the same instan
}
//Set the name and index
aValue._Name = aName;
aValue._Index = aList.Count;
//Add to the list
aList.Add(aValue);
}

private static bool InitCaseSensitive()


{
return HasDuplicateNames(StringComparer.OrdinalIgnoreCase);
}

private static bool HasDuplicateNames(IEqualityComparer<string> aComparer)


{
TEnum[] myMembers = Members;
Dictionary<string, TEnum> myDict = new Dictionary<string, TEnum>(aComparer);
try {

15 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

foreach (TEnum myEntry in myMembers) {


myDict.Add(myEntry._Name, myEntry);
}
} catch {
return true;
}
return false;
}

ConƟnue to read at
POST 2013-04-07/3

Contents added by Chris on Apr 07, 2013


(This is now the third Ɵme I'm trying to make my post as the c-sharpcorner seems to have some limitaƟons in input length but
does not find it necessary to inform us in the UI nor to provide a speaking error message, and of course every thing is lost aŌer
pressing the buƩon, no possibility to shorten it a bit. Shame on them. So if you think I sound rude, simply ignore it - I am mad
but not at you...)

POST 2013-04-07/1

Hello Vulpes, hello community

I like your idea of generic custom enums very much, but what I did not like of your implementaƟon is that the values are used
instead of the enums themselves.

There seems to be a liƩle informaƟon missing in your knowlegde I'am glad to fill in:

What is executed even before the fields of the subclass are assigned?
-> Lazzy iniƟalized geƩer properƟes.

What allows you not to have everything iniƟalized in the base classes staƟc constructor?
-> Lazzy iniƟalized geƩer properƟes.

Using this knowledge it is very easy to construct a more intuiƟve version of your genericEnum (I call them CustomEnum). Here's
my version, including all the sample enums to prove the concept.

This implementaƟon has the following advantages:


- Speed and memory consumpƟon are excellent as there is only one instance of each enum value throughout the AppDomain.
- The enum automaƟcally detects whether a case-sensiƟve or a case-insensiƟve search is needed to find the enum by name
(overridable in the enum)
- The enum allows to also iniƟalize a enum value through staƟc geƩer property
- The enum performs different checks to ensure the implementor implemented the paƩern right (check for cross enum
instanƟaƟon (eg. public class EvilEnum : CustomEnum<Season, DateTime>), check for uncached geƩer results (an excepƟon is
thrown when the second Ɵme the geƩer is invoked another instance is returned than the first Ɵme)
- The enum can provide a ValueComparer to determine equality of the value
- The enum implements IEquatable (Enum -> Value)
- It has debugger and editor aƩributes applied for beƩer comfort

ConƟnue to read at
POST 2013-04-07/2

RELATED ARTICLES

Generic Dal in WCF using VB.NET SorƟng CollecƟon of Custom Type using Generic
Dynamically CreaƟng Generic List and Generic Advance Usage of Generic DicƟonary Using C#
DicƟonary at RunƟme Using C# Generic Data Access Layer for WCF : Part 3
Generic DAL using WCF: Part 6 Generic Data Access Layer for WCF : Part 5
Generic Data Access Layer using ADO.NET EnƟty Generic Data Access Layer using ADO.NET EnƟty
Framework : Part 2 Framework
Compare 2 Objects of Generic Class Type in C#

COMMENTS 2 of 2

Suthish Nair
Can we create dynamic Enums?
0 Like 0 Reply Post Reply Oct 11, 2011

Vulpes
Hi Suthish. If by a dynamic enum, you mean one that can be expanded by adding addiƟonal name/value
pairs at runƟme then, yes, this can easily be done by adding some code to the GenericEnum base class. As it
wasn't worth doing another arƟcle, I've just posted the code in a blog (hƩp://www.c-sharpcorner.com/Blogs
/7081/creaƟng-dynamic-enums-using-C-Sharp.aspx).
0 Like Oct 12, 2011

Type your comment here and press Enter Key....

16 trong 17 14/07/2014 08:44 SA


Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums...

COMMENT USING

Add a comment...

Also post on Facebook Posting as Xuanthu Saigon (Not you?)

Facebook social plugin

MVPs MOST VIEWED LEGENDS NOW PRIZES AWARDS REVIEWS SURVEY CERTIFICATIONS DOWNLOADS Hosted By CBeyond Cloud Services

PHOTOS CODE SNIPPETS CONSULTING TRAINING STUDENTS MEMBERS MEDIA KIT ABOUT US LINKS IDEAS

CONTACT US PRIVACY POLICY TERMS & CONDITIONS SITEMAP REPORT ABUSE

©2014 C# Corner. All contents are copyright of their authors.

17 trong 17 14/07/2014 08:44 SA

You might also like