generic enum

17
ARTICLE 2 20,419 An enum variable can then be set to any one of these constants or (in the case of ‘Flags’ enums) to a meaningful combinaon of them. Creang Generic Enums using C# Posted by Vulpes in Arcles | Visual C# on October 11, 2011 Tweet 0 20336 2 0 Reader Level: Download Files: genericenum.zip What's wrong with the enums we have now? As I'm sure everyone reading this will know, enums are simple lightweight types that enable you to define a set of named integral constants. An enum variable can then be set to any one of these constants or (in the case of 'Flags' enums) to a meaningful combination of them. As such, enums perform a useful role in C# programming. However, they do have a number of shortcomings: 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. You can assign any value of the underlying type to an enum variable, whether it corresponds to one of the defined constants or not. You can't add methods, properties or other types of member to an enum, though you can add methods to them indirectly using 'extension methods'. See my earlier article (http://www.c- sharpcorner.com/UploadFile/b942f9/5951/). 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. 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. 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, 01 HTML 5 IndexedDB Database 02 CRUD Operaons in MVC 5 Using WebAPI With AngularJS 03 Login Form in 3-Tier Architecture With Lock in ASP.Net 04 ASP.Net MVC 4 Crunch 05 Diving Into OOP (Day 5): All About Access Modifiers in C# (C# Modifiers/Sealed /Constants/Readonly Fields) 06 Lambda Expression in 15 Minutes 07 Creang Windows Phone and Window 8.1 Applicaons Using Microso App Studio 08 Parallel Task in .Net 4.0 09 Master Pages in C# Windows Forms Applicaons 10 Early Look at Visual Studio 2014 CTP 2 View All Follow @csharpcorner 6,051 followers C# Corner Jabalpur User Group Meet: 19 July 2014 HTML 5 IndexedDB Database Share Share 0 Like Like Find us on Facebook C# Corner 48,996 people like C# Corner . Facebook social plugin Like Like Creating Generic Enums using C# http://www.c-sharpcorner.com/uploadfile/b942f9/creating-generic-enums... 1 trong 17 14/07/2014 08:44 SA

Upload: xuanthu-saigon

Post on 20-Jul-2016

3 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Generic Enum

ARTICLE

2 20,419

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.

Crea&ng Generic Enums using C#Posted by Vulpes in Ar&cles | Visual C# on October 11, 2011

Tweet 0 20336 2 0

Reader Level:

Download Files: genericenum.zip

What's wrong with the enums we have now?

As I'm sure everyone reading this will know, enums are simple lightweight types that enable you to

define a set of named integral constants. An enum variable can then be set to any one of these

constants or (in the case of 'Flags' enums) to a meaningful combination of them.

As such, enums perform a useful role in C# programming. However, they do have a number of

shortcomings:

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.

You can assign any value of the underlying type to an enum variable, whether it corresponds

to one of the defined constants or not.

You can't add methods, properties or other types of member to an enum, though you can add

methods to them indirectly using 'extension methods'. See my earlier article (http://www.c-

sharpcorner.com/UploadFile/b942f9/5951/).

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.

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.

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,

01HTML 5 IndexedDB Database

02CRUD Opera&ons in MVC 5 Using WebAPI

With AngularJS

03Login Form in 3-Tier Architecture With

Lock in ASP.Net

04ASP.Net MVC 4 Crunch

05Diving Into OOP (Day 5): All About Access

Modifiers in C# (C# Modifiers/Sealed

/Constants/Readonly Fields)

06Lambda Expression in 15 Minutes

07Crea&ng Windows Phone and Window 8.1

Applica&ons Using MicrosoD App Studio

08Parallel Task in .Net 4.0

09Master Pages in C# Windows Forms

Applica&ons

10Early Look at Visual Studio 2014 CTP 2

View All

Follow @csharpcorner 6,051 followers

C# Corner Jabalpur User Group Meet: 19 July 2014

HTML 5 IndexedDB Database

ShareShare 0LikeLike

Find us on Facebook

C# Corner

48,996 people like C# Corner.

Facebook social plugin

LikeLike

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

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

Page 2: Generic Enum

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()

{

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

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

Page 3: Generic Enum

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));

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

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

Page 4: Generic Enum

}

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;

}

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

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

Page 5: Generic Enum

}

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;

}

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

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

Page 6: Generic Enum

}

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));

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

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

Page 7: Generic Enum

}

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.

Contents added by Chris on Apr 07, 2013

Ar&cle Extensions

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) {

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

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

Page 8: Generic Enum

Contents added by Chris on Apr 07, 2013

myString = ex.Message;}

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

}

//Implicite castPlanet 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, myEarth);//True myString = string.Format("It is {0} that {1} is closer to the Sun than {2}", myJupiter.IsCloserToSunThan(myEarth), myJupiter, myEarth);//False

}

}

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

Best regards

Chris

END OF POST

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

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

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

Page 9: Generic Enum

Contents added by Chris on Apr 07, 2013

Contents added by Chris on Apr 07, 2013

POST 2013-04-07/8

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

POST 2013-04-07/5

The sample enum Season (seasons):

(note the fiDh season is a geGer 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:

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

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

Page 10: Generic Enum

Contents added by Chris on Apr 07, 2013

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(){

//Fakereturn DateTime.Today.AddDays(-100);

}

}

Con&nue to read at

POST 2013-04-07/6

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 Fieldsprivate 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; }}

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

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

Page 11: Generic Enum

Contents added by Chris on Apr 07, 2013

Contents added by Chris on Apr 07, 2013

/// <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

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

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)]

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

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

Page 12: Generic Enum

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!</summaryprotected 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!</summaryprotected CustomEnum(TValue aValue, bool? caseSensitive, IEqualityComparer<TValue> aValueComparer){

//Assign instance value_Value = aValue;//Make sure no evil cross subclass is changing our static variableif ((!typeof(TEnum).IsAssignableFrom(this.GetType()))) {

throw new InvalidOperationException("Internal error in " + this.GetType().Name + "! Change the first type parameter from \"" + ty}//Make sure only the first subclass is affecting our static variablesif ((_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 assignedstring myResult = _Name;if ((myResult == null)) {

InitMembers();myResult = _Name;

}//Return the namereturn 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 assignedInt32 myResult = _Index;if ((myResult < 0)) {

InitMembers();myResult = _Index;

}//Return the indexreturn 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)) {

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

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

Page 13: Generic Enum

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 argumentif ((aName == null))

throw new ArgumentNullException("aName");//Get the membersTEnum[] myMembers = Members;//Initialize comparer and check whole table for duplicatesIEqualityComparer<string> myComparer = _StringComparer;if ((myComparer == null)) {

//Determine the comparermyComparer = CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;//Check for duplicates (happens if the constructor explicitely sets the case-insensitive flag but has two fields that differ only//or if the enum has multiple hierarchical subclasses and the overwritten properties do not hide the parent ones (like this is 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 foundforeach (TEnum myMember in myMembers) {

if ((myComparer.Equals(myMember._Name, aName)))return myMember;

}//Otherwise return nullreturn 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>

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

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

Page 14: Generic Enum

/// <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.</remarks>[EditorBrowsable(EditorBrowsableState.Advanced)]public static TEnum GetMemberByName(string aName, IEqualityComparer<string> aComparer){

//Check the argumentif ((aName == null))

throw new ArgumentNullException("aName");//Use optimized method if possibleif ((aComparer == null))

return GetMemberByName(aName);if ((object.Equals(aComparer, _StringComparer)))

return GetMemberByName(aName);//Get the membersTEnum[] myMembers = Members;//Get the first found member but continue loopingTEnum 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 comparer!");}

}}//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 membersTEnum[] myMembers = Members;//Immediately return the member if the values equalif ((aValueComparer == null)) {

//Using the default comparerforeach (TEnum myMember in myMembers) {

if ((object.Equals(myMember._Value, aValue)))return myMember;

}} else {

//Using the given comparerforeach (TEnum myMember in myMembers) {

//Immediately return the member if the values equalif ((aValueComparer.Equals(myMember._Value, aValue)))

return myMember;}

}//Return null if not foundreturn 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){

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

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

Page 15: Generic Enum

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) && (myProp//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 " + myProperty.Name +}//Add the entryAddEntry(myEntry, myProperty.Name, aList);

}}

}

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

//Check for instance conflictsif ((aValue._Name != null)) {

throw new InvalidOperationException("Internal error in " + typeof(TEnum).Name + "! It's invalid to assign the same instance to tw}//Set the name and indexaValue._Name = aName;aValue._Index = aList.Count;//Add to the listaList.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 {

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

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

Page 16: Generic Enum

Contents added by Chris on Apr 07, 2013

RELATED ARTICLES

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

(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 aDer

pressing the buGon, 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 liGle 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 geGer proper&es.

What allows you not to have everything ini&alized in the base classes sta&c constructor?

-> Lazzy ini&alized geGer 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 geGer property

- The enum performs different checks to ensure the implementor implemented the paGern right (check for cross enum

instan&a&on (eg. public class EvilEnum : CustomEnum<Season, DateTime>), check for uncached geGer results (an excep&on is

thrown when the second &me the geGer 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 aGributes applied for beGer comfort

Con&nue to read at

POST 2013-04-07/2

Generic Dal in WCF using VB.NET Sor&ng Collec&on of Custom Type using Generic

Dynamically Crea&ng Generic List and Generic

Dic&onary at Run&me Using C#

Advance Usage of Generic Dic&onary 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

Framework : Part 2

Generic Data Access Layer using ADO.NET En&ty

Framework

Compare 2 Objects of Generic Class Type in C#

COMMENTS 2of2

Oct 11, 20110 Like 0 Reply Post Reply

Suthish Nair

Can we create dynamic Enums?

Oct 12, 20110 Like

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 (hGp://www.c-sharpcorner.com/Blogs

/7081/crea&ng-dynamic-enums-using-C-Sharp.aspx).

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

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

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

Page 17: Generic Enum

Hosted By CBeyond Cloud Services

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

COMMENT USING

Facebook social plugin

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

Add a comment...

MVPs MOST VIEWED LEGENDS NOW PRIZES AWARDS REVIEWS SURVEY CERTIFICATIONS DOWNLOADS

CONTACT US PRIVACY POLICY TERMS & CONDITIONS SITEMAP REPORT ABUSE

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

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

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