Professional Documents
Culture Documents
You can't add methods, properties or other types of member to an enum, though you can add Find us on Facebook
methods to them indirectly using 'extension methods'. See my earlier article (http://www.c-
sharpcorner.com/UploadFile/b942f9/5951/). C# Corner
Like
Enum values often have to be cast back to their underlying types, which can be a nuisance in
some scenarios - for example when they are used to index a collection. 48,996 people like C# Corner.
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 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,
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.
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;
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));
}
return names.ToArray();
}
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;
}
}
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:
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();
Console.WriteLine();
Console.WriteLine();
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.
No, ordinary enums are fine for most purposes and are very lightweight.
However, if you wish to use a non-integral underlying type or to include some other members, then
I hope that you will consider using a generic enum instead.
ArƟcle Extensions
Contents added by Chris on Apr 07, 2013
POST 2013-04-07/8
myFraction2 = Fraction.GetMemberByName("Third");
myDouble = myFraction2.FractionOf(30);
try {
EvilEnum myEvilEnum = EvilEnum.Hacker;
myString = myEvilEnum.ToString();
} catch (Exception ex) {
myString = ex.Message;
}
//Implicite cast
Planet myPlanet = PlanetEnum.Earth;
double myEarthWeight = 80;
double myMass = myEarthWeight / myPlanet.SurfaceGravity;
foreach (PlanetEnum myPlanet2 in PlanetEnum.GetMembers()) {
myString = string.Format("My weight on {0} is {1:F2} kg", myPlanet2, myPlanet2.Value.GetSurfaceWeight(myMass));
}
PlanetEnum myMercury = PlanetEnum.Mercury;
PlanetEnum myEarth = PlanetEnum.Earth;
PlanetEnum myJupiter = PlanetEnum.Jupiter;
myString = string.Format("It is {0} that {1} is closer to the Sun than {2}", myMercury.IsCloserToSunThan(myEarth), myMercury, myE
//True
myString = string.Format("It is {0} that {1} is closer to the Sun than {2}", myJupiter.IsCloserToSunThan(myEarth), myJupiter, myE
//False
I hope that this code may be helpful for you. Enjoy it.
Best regards
Chris
END OF POST
//Constructors
//Public Fields
//**************************************************************************
// INNER CLASS: EvilComparer
//**************************************************************************
ConƟnue to read at
POST 2013-04-07/8
//Constructors
//Public Fields
ConƟnue to read at
POST 2013-04-07/7
//Private Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
//Public Fields
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;
}
}
ConƟnue to read at
POST 2013-04-07/6
//Constructors
//Public Fields
//Private Fields
private readonly double _MassInKg;
private readonly double _RadiusInMeters;
//Public Properties
/// <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; }
}
ConƟnue to read at
POST 2013-04-07/5
//Constructors
//Public Fields
ConƟnue to read at
POST 2013-04-07/4
//Private Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
/// <summary>Called by implementors to create a new instance of TEnum. Important: Seal your class and/or make your constructors private!<
protected CustomEnum(TValue aValue) : this(aValue, null, null)
{
}
/// <summary>Called by implementors to create a new instance of TEnum. Important: Seal your class and/or make your constructors private!<
protected CustomEnum(TValue aValue, bool? caseSensitive, IEqualityComparer<TValue> aValueComparer)
{
//Assign instance value
_Value = aValue;
//Make sure no evil cross subclass is changing our static variable
if ((!typeof(TEnum).IsAssignableFrom(this.GetType()))) {
throw new InvalidOperationException("Internal error in " + this.GetType().Name + "! Change the first type parameter from
}
//Make sure only the first subclass is affecting our static variables
if ((_IsFirstInstance)) {
_IsFirstInstance = false;
//Assign static variables
_CaseSensitive = caseSensitive;
_ValueComparer = aValueComparer;
}
}
//Public Properties
/// <summary>Returns 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)) {
myResult = InitCaseSensitive();
_CaseSensitive = myResult;
}
return myResult.Value;
}
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static Type UnderlyingType {
get { return typeof(TValue); }
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum[] GetMembers()
{
TEnum[] myMembers = Members;
TEnum[] myResult = new TEnum[myMembers.Length];
Array.Copy(myMembers, myResult, myMembers.Length);
return myResult;
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static string[] GetNames()
{
TEnum[] myMembers = Members;
string[] myResult = new string[myMembers.Length];
for (Int32 i = 0; i <= myMembers.Length - 1; i++) {
myResult[i] = myMembers[i]._Name;
}
return myResult;
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TValue[] GetValues()
{
TEnum[] myMembers = Members;
TValue[] myResult = new TValue[myMembers.Length];
for (Int32 i = 0; i <= myMembers.Length - 1; i++) {
myResult[i] = myMembers[i]._Value;
}
return myResult;
}
/// <summary>Returns the member of the given name or null if not found. You can check through property
/// <see cref="CaseSensitive" /> whether <paramref name="aName" /> is treated case-sensitive or not.
/// If aName is null, an ArgumentNullException is thrown. If the subclass is incorrectly implemented
/// and has duplicate names defined, a DuplicateNameException is thrown.</summary>
/// <param name="aName">The name to look up.</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>A full duplicate check is performed the first time this method is called.</remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByName(string aName)
{
//Check the argument
if ((aName == null))
throw new ArgumentNullException("aName");
//Get the members
TEnum[] myMembers = Members;
//Initialize comparer and check whole table for duplicates
IEqualityComparer<string> myComparer = _StringComparer;
if ((myComparer == null)) {
//Determine the comparer
myComparer = CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
//Check for duplicates (happens if the constructor explicitely sets the case-insensitive flag but has two fields that dif
//or if the enum has multiple hierarchical subclasses and the overwritten properties do not hide the parent ones (like th
//in JScript.NET).
if ((HasDuplicateNames(myComparer))) {
throw new DuplicateNameException("Internal error in " + typeof(TEnum).Name + ", the member names are not unique!"
}
//If everything is okay, assign the comparer
_StringComparer = myComparer;
}
//Return the first name found
foreach (TEnum myMember in myMembers) {
if ((myComparer.Equals(myMember._Name, aName)))
return myMember;
}
//Otherwise return null
return null;
}
/// <summary>Returns the member of the given name or null if not found using the given comparer
/// to perform the comparison. If there are no special reasons don't use this method but the
/// one without the comparer overload as it is optimized to perform the duplicate check only
/// once and not every time the method is used.</summary>
/// <param name="aName">The name to look up.</param>
/// <param name="aComparer">The comparer to use for the equality comparison of the strings.</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>The whole list is searched every time this method is called to ensure aName is unique according to the given comparer.</rema
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByName(string aName, IEqualityComparer<string> aComparer)
{
//Check the argument
if ((aName == null))
throw new ArgumentNullException("aName");
//Use optimized method if possible
if ((aComparer == null))
return GetMemberByName(aName);
if ((object.Equals(aComparer, _StringComparer)))
return GetMemberByName(aName);
//Get the members
TEnum[] myMembers = Members;
//Get the first found member but continue looping
TEnum myResult = null;
foreach (TEnum myMember in myMembers) {
if ((aComparer.Equals(myMember._Name, aName))) {
if ((myResult == null)) {
myResult = myMember;
} else {
throw new DuplicateNameException("The name \"" + aName + "\" is not unique according to the given compare
}
}
}
//Return the result
return myResult;
}
/// <summary>Returns the first member of the given value or null if not found. The value to look up
/// may also be null. This function uses the value comparer defined by the enumeration (or Object's
/// equal method if not defined) to perform the comparison.</summary>
/// <param name="aValue">The value to look up (null is valid be null).</param>
/// <returns>The enum entry or null if not found.</returns>
/// <remarks>The duplicate check is performed only the first time if successful.</remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByValue(TValue aValue)
{
return GetMemberByValue(aValue, _ValueComparer);
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static TEnum GetMemberByValue(TValue aValue, IEqualityComparer<TValue> aValueComparer)
{
//Get the members
TEnum[] myMembers = Members;
//Immediately return the member if the values equal
if ((aValueComparer == null)) {
//Using the default comparer
foreach (TEnum myMember in myMembers) {
if ((object.Equals(myMember._Value, aValue)))
return myMember;
}
} else {
//Using the given comparer
foreach (TEnum myMember in myMembers) {
//Immediately return the member if the values equal
if ((aValueComparer.Equals(myMember._Value, aValue)))
return myMember;
}
}
//Return null if not found
return null;
}
/// <summary>Always implicitely allow to cast into the value type (like this is the case with standard
/// .NET enumerations).</summary>
public static implicit operator TValue(CustomEnum<TEnum, TValue> value)
{
if ((value == null))
throw new ArgumentNullException("value");
return value.Value;
}
//Framework Properties
//Framework Methods
[EditorBrowsable(EditorBrowsableState.Advanced)]
public virtual bool Equals(TValue other)
{
//Private Properties
//Private Methods
ConƟnue to read at
POST 2013-04-07/3
POST 2013-04-07/1
I like your idea of generic custom enums very much, but what I did not like of your implementaƟon is that the values are used
instead of the enums themselves.
There seems to be a liƩle informaƟon missing in your knowlegde I'am glad to fill in:
What is executed even before the fields of the subclass are assigned?
-> Lazzy iniƟalized geƩer properƟes.
What allows you not to have everything iniƟalized in the base classes staƟc constructor?
-> Lazzy iniƟalized geƩer properƟes.
Using this knowledge it is very easy to construct a more intuiƟve version of your genericEnum (I call them CustomEnum). Here's
my version, including all the sample enums to prove the concept.
ConƟnue to read at
POST 2013-04-07/2
RELATED ARTICLES
Generic Dal in WCF using VB.NET SorƟng CollecƟon of Custom Type using Generic
Dynamically CreaƟng Generic List and Generic Advance Usage of Generic DicƟonary Using C#
DicƟonary at RunƟme Using C# Generic Data Access Layer for WCF : Part 3
Generic DAL using WCF: Part 6 Generic Data Access Layer for WCF : Part 5
Generic Data Access Layer using ADO.NET EnƟty Generic Data Access Layer using ADO.NET EnƟty
Framework : Part 2 Framework
Compare 2 Objects of Generic Class Type in C#
COMMENTS 2 of 2
Suthish Nair
Can we create dynamic Enums?
0 Like 0 Reply Post Reply Oct 11, 2011
Vulpes
Hi Suthish. If by a dynamic enum, you mean one that can be expanded by adding addiƟonal name/value
pairs at runƟme then, yes, this can easily be done by adding some code to the GenericEnum base class. As it
wasn't worth doing another arƟcle, I've just posted the code in a blog (hƩp://www.c-sharpcorner.com/Blogs
/7081/creaƟng-dynamic-enums-using-C-Sharp.aspx).
0 Like Oct 12, 2011
COMMENT USING
Add a comment...
MVPs MOST VIEWED LEGENDS NOW PRIZES AWARDS REVIEWS SURVEY CERTIFICATIONS DOWNLOADS Hosted By CBeyond Cloud Services
PHOTOS CODE SNIPPETS CONSULTING TRAINING STUDENTS MEMBERS MEDIA KIT ABOUT US LINKS IDEAS