generic enum
TRANSCRIPT
![Page 1: Generic Enum](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/1.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/2.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/3.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/4.jpg)
}
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/5.jpg)
}
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/6.jpg)
}
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/7.jpg)
}
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/8.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/9.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/10.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/11.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/12.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/13.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/14.jpg)
/// <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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/15.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/16.jpg)
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](https://reader031.vdocuments.site/reader031/viewer/2022020520/577cc6f71a28aba7119faae9/html5/thumbnails/17.jpg)
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