 |
|
 |
Hi, I have used this same technique because I really liked the way Java deals with enums. I did have a couple comments though. First, your base class forces an integer key type. It would be better if this could be any type. Second, you are not doing anything to enforce uniqueness on the key or value.
I have implemented the same sort of thing using a couple levels.
First, I have an EnumType<T> class that does what you have above but does not require a key. The the class is a reference type, the objects reference essencially becomes its key. The only thing the constuctor takes is a string called name (same as your value). The names are required to be unique. This class does not implement GetBaseByKey since there is no key.
Second, I have a ComparableEnumType<T, ValueType> : where ValueType : IComparable<ValueType>. The provides a key / value pair and the ability to get the enum instance from the ValueType key.
I also have a skeleton for a EquatableEnumType<T, ValueType> which is not yet fully implemented but would extend things further.
The code below has some of the problems that you address in your article, which I am going to be incorperating. So thanks.
<code> /// <summary> /// Base class for implementing enums as objects. /// </summary> /// <typeparam name="T">The type of the enum. /// Must extend EnumType(<i>T</i></typeparam> public abstract class EnumType<T> where T : EnumType<T> {
#region Static Fields ///////////////////////////////////////////////////////////////// // ------------------------ Static Fields --------------------- /////////////////////////////////////////////////////////////////
/// <summary>Maintains the list of enum objects.</summary> private static List<T> values = new List<T>();
/// <summary>Maintains the map of enum objects refereneced by their names.</summary> private static Dictionary<String, T> nameList = new Dictionary<string, T>();
#endregion
#region Static Methods ///////////////////////////////////////////////////////////////// // ------------------------ Static Methods -------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// Gets a read-only collection of the enum objects in the type. /// </summary> /// <returns>a read-only collection of the enum objects in the type</returns> public static ReadOnlyCollection<T> Values {
get { return values.AsReadOnly(); } }
/// <summary> /// Gets the enum object associated with the name passed. /// </summary> /// <param name="name">the name of the object to return</param> /// <returns>the object referenced by the name passed, /// null if no such object exists.</returns> public static T ValueOf(String name) {
try { return nameList[name]; } catch (KeyNotFoundException) { return null; } }
#endregion
#region Non-Public Fields ///////////////////////////////////////////////////////////////// // ------------------------ Non-Public Fields ----------------- /////////////////////////////////////////////////////////////////
private String name;
#endregion
#region Properties ///////////////////////////////////////////////////////////////// // ------------------------ Properties ------------------------ /////////////////////////////////////////////////////////////////
/// <summary> /// The String representation of the enum value. /// </summary> public String Name { get { return name; } }
#endregion
#region Constructors ///////////////////////////////////////////////////////////////// // ------------------------ Constructors ---------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// Default constructor. /// </summary> /// <param name="name">the name of the enum object</param> protected EnumType(String name) {
if (nameList.ContainsKey(name)) { throw new ArgumentException ( String.Format("Class {0} already contains an element with name \"{1}\".", typeof(T).Name, name)); }
this.name = name;
values.Add((T)this); nameList.Add(name, (T)this); }
#endregion
#region Class Base Overrides ///////////////////////////////////////////////////////////////// // ------------------------ Class Overrides ------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// Gets the string representation of the enum object. This is /// the same as the object's name. /// </summary> /// <returns>the string representation of the enum object</returns> public override string ToString() { return name; }
#endregion
#region Public Methods ///////////////////////////////////////////////////////////////// // ------------------------ Public Methods -------------------- /////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////// /// <summary> /// Executes a switch statement. /// </summary> /// <param name="cases">the cases to check</param> ///////////////////////////////////////////////////////////////// public void Switch(EnumSwitchCase[] cases) {
foreach (EnumSwitchCase currentCase in cases) {
if (currentCase.caseValue == null || currentCase.caseValue.Equals(this)) {
currentCase.executable(); break; } } }
#endregion }
/// <summary> /// Implements a base class for enum objects where each object is /// associated with a comparable value. This allows for the conversion /// from an instance of a value to the appropriate enum object. /// </summary> /// <typeparam name="T">The type of the enum. /// Must extend ComparableEnumType(<i>T</i>, <i>ValueType</i> </typeparam> /// <typeparam name="ValueType">The type of the comparable objects /// that are associated with each enum object. /// Must implement IComparable(<i>ValueType</i> </typeparam> public abstract class ComparableEnumType<T, ValueType> : EnumType<T>, IComparable<T> where T : ComparableEnumType<T, ValueType> where ValueType : IComparable<ValueType> { #region Inner Classes / Types ///////////////////////////////////////////////////////////////// // ------------------------ Inner Classes --------------------- /////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////// /// <summary> /// A method that may be called to test to test if two values /// are equal. /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns>if the values passed are equal.</returns> ///////////////////////////////////////////////////////////////// public delegate bool EqualsTesterDelegate(ValueType a, ValueType b);
#endregion
#region Static Fields ///////////////////////////////////////////////////////////////// // ------------------------ Static Fields --------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// Method that is called to compare values. If null, the standard /// comparable method will be called. /// </summary> protected static EqualsTesterDelegate AreEqual = null;
/// <summary> /// the enumeration with the highest comparable value. /// </summary> private static T max = null;
/// <summary> /// Gets the enumeration with the highest comparable value. /// </summary> public static T Max { get { return max; } }
/// <summary> /// the enumeration with the lowest comparable value. /// </summary> private static T min = null;
/// <summary> /// Gets the enumeration with the lowest comparable value. /// </summary> public static T Min { get { return min; } }
#endregion
#region Static Methods ///////////////////////////////////////////////////////////////// // ------------------------ Static Methods -------------------- /////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////// /// <summary> /// Gets the instance associated with the <i>value</i> passed. If /// an instance is not found to match the value passed, an attempt /// to get the default value for the type is made. If a default /// instance is found, that is returned, otherwise null is returned. /// </summary> /// <param name="value">the value to be assessed</param> /// <returns>the instance associated with the <i>value</i> passed, /// null or default instance if none found.</returns> ///////////////////////////////////////////////////////////////// public static T Convert(ValueType value) {
foreach (T result in Values) {
if (AreEqual != null) { if (AreEqual(value, result.Value)) {
return result; } } else if (result.Value != null && result.Value.CompareTo(value) == 0) {
return result; } }
return GetDefault(); }
///////////////////////////////////////////////////////////////// /// <summary> /// Gets the instance associated with the <i>value</i> passed. If /// an instance is not found to match the value passed, <i>defaultVal</i> /// is returned. /// </summary> /// <param name="value">the value to be assessed</param> /// <param name="defaultVal">the instance to return if there is /// no match made</param> /// <returns>the instance associated with the <i>value</i> passed, /// <i>defaultVal</i> if none found.</returns> ///////////////////////////////////////////////////////////////// public static T Convert(ValueType value, T defaultVal) {
foreach (T result in Values) {
if (AreEqual(value, result.Value)) {
return result; } }
return defaultVal; }
///////////////////////////////////////////////////////////////// /// <summary> /// Gets the default value of the derived class if defined. /// Otherwise returns null. /// </summary> /// <returns>the default value of the derived class if defined, /// Otherwise returns null</returns> /// <remarks>For a derived type to have a default value that will /// be returned by this method, the type must implement a static /// methods named <b>"GetDefault"</b> which takes no arguments /// and returns the default member of the derived type.</remarks> ///////////////////////////////////////////////////////////////// public static T GetDefault() {
// Get type of derived class Type t = typeof(T);
// get all public static methods MethodInfo[] methods = t.GetMethods(BindingFlags.Static | BindingFlags.Public);
try { // See if GetDefaultValue exists foreach (MethodInfo method in methods) {
if (method.Name.Equals("GetDefault")) {
// get the default value return (T)method.Invoke(null, null); } } } catch (Exception) {
}
// No GetDefault method found, returning null return null; }
#endregion
#region Public Fields ///////////////////////////////////////////////////////////////// // ------------------------ Public Fields --------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// The value associated with this object. /// </summary> public readonly ValueType Value;
#endregion
#region Constructors ///////////////////////////////////////////////////////////////// // ------------------------ Constructors ---------------------- /////////////////////////////////////////////////////////////////
/// <summary> /// Default constructor. /// </summary> /// <param name="name">the name of the enum object</param> /// <param name="value">the value associated with this object, /// may be null</param> protected ComparableEnumType(String name, ValueType value) : base (name){
Value = value;
if (max == null || max.Value == null || max.Value.CompareTo(value) < 0) { max = (T)this; }
if (min == null || min.Value == null || min.Value.CompareTo(value) > 0) { min = (T)this; } }
#endregion
#region IComparable<T> Members
///////////////////////////////////////////////////////////////// /// <summary> /// Compares the current object with another object of the same type. /// </summary> /// <param name="other">An object to compare with this object</param> /// <returns>A 32-bit signed integer that indicates the relative /// order of the objects being compared</returns> ///////////////////////////////////////////////////////////////// public int CompareTo(T other) {
return this.Value.CompareTo(other.Value); }
#endregion } </code>
John Butler
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi John,
Your method looks interesting. It would be nice if you wrote an article or a blog post about it.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi,
I guess you missed the point. Iterating through the values of an enum is just one example of what we could do if enums were extensible as regular classes, just like it is in Java.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I have very little knowledge of Java (although I've read several books about refactoring and design patterns based on the Java language and I know that most of our Nxxxx goodies are borrowed from the Java domain).
I have missed enum extensibility a few times myself, but I have sort of accepted that enums are not extensible, and I have always been able to find a workaround in those rare cases. Of course what you do is ok to do if it's appropriate. I understand enums as simple value types and as such they don't really need any kind of extension.
You can iterate through System.Enum.GetNames(typeof(MyEnum)) or System.Enum.GetValues(typeof(MyEnum)) so I think that your example is an example where you should stick to just using enums and keep the benefit of communicating to other developers that they are dealing with simple value types.
I personally try to stick to the Design guidelines for developing class libraries[^] as much as possible. Of course it's a matter of taste whether to use those conventions or not, but it works fine for me. According to those guidelines your suggestion is an example of incorrect design (Enumeration Design[^]).
I would like to know what other extensibility you miss in the case of enumerations.
A completely other thing is that you are dealing with a kind of "borderline case" where you have to decide whether the matter you're expressing as a type could be regarded as either an enum or a class. The term "borderline case" is my own and covers decisions of whether to express a given matter as enum, struct or class, decisions about whether an attribute should be a method or a property and whether some functionality should be a method or a class (like Kent Beck's method object and the GoF command pattern ).
I find those borderline cases interesting (from a semiotically point of view), and I guess that you are confronted with more of those cases since you use both Java and C#.
Maybe you can use the new VS2008 extension method-functionality to achieve what you want by making a few extensions on System.Enum (works for dotnet 2.0 too as long as you use VS2008 as IDE). That could mean that you would be able to achieve the behavior you want and keep using the enum type as well 
-- modified at 1:50 Monday 15th October, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
Yes, extension method is something that would come in handy here and I hadn’t thought about that when writing this article.
In no way I am saying we shouldn’t use enums anymore. But, as you mentioned, there are a few times when we miss enum extensibility and we have to come up with workarounds. Well, this is a workaround for the lack of enums extensibility. As I said in the article, it is not a perfect solution because of the lack of support in the language itself.
Let me bring up again the Java DaysOfWeek example:
enum DaysOfWeek{ SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
public boolean isWeekEnd(){ return (this == SUNDAY || this == SATURDAY); } }
I don’t know about you, but I’d rather have the type that defines the days of the week telling me if it’s a weekend or not, than having a helper class just to do that. For me that would be an anti-pattern. While I may be breaking a c# design guideline, I am not breaking some more basic concepts like The Open Closed Principle or the Principle of Single Responsibility.
But there are many ways to achieve the same result and this is just another one that I think not many people are aware of. So I thought it'd be worth sharing.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I am glad you did choose to share it, and those guidelines are only a set of guidelines between many sets of principles, not the law.
I am interested in understanding those cases where stuff can be expressed in different ways, and the title of your article caught my eye.
Beleive me, if I thought your article was nonsense I wouldn't have bothered to make any comment.
Keep sharing 
Regards, Anders Haahr
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It is possible to do a lot with enums by using the static methods on the Enum class, i.e. iterate them ...
Regards, Thomas Lykke Petersen (MCP)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, this is good idea ( not new at all ), but the implementation is not so good
1) It should be struct, not class 2) The static list "enumValues" is not required. 3) The static values can be modified with reflection ( potentialy dangerous ).
This implementation is IMHO cleaner:<code> /// <summary> /// Common interface for strongly-typed enums. /// </summary> public interface IStronglyTypedEnum { string GetName(); object GetValue(); } /// <summary> /// Generic interface for strongly-typed enums. /// </summary> /// <typeparam name="T">Type of value of enum item.</typeparam> public interface IStronglyTypedEnum<T> : IStronglyTypedEnum { new T GetValue(); }
/// <summary> /// Enumeration of commonly used colors. /// </summary> public struct Colors : IStronglyTypedEnum<System.Drawing.Color> { public static Colors Background { get { return new Colors( "Background", System.Drawing.Color.White ); } } public static Colors Text { get { return new Colors( "Text", System.Drawing.Color.Black ); } } public static Colors SelectedText { get { return new Colors( "SelectedText", System.Drawing.Color.Blue ); } }
private string name; public string GetName() { return this.name; } private System.Drawing.Color value; public System.Drawing.Color GetValue() { return this.value; } object IStronglyTypedEnum.GetValue() { return this.value; }
public Colors( string name, System.Drawing.Color value ) { this.name = name; this.value = value; }
/// <summary> /// Return collection with enum's items. /// </summary> public static ICollection<Colors> GetEnumItems() { Colors[] items = new Colors[]{ Background, Text, SelectedText };
return items; } }</code>
Developer must write more code, but it can be solved with "CustomTool" for VS. Then you can write enum's in XML file for example, and then write code will be much slimer.
--------------------------------- /* Geniality is in simplicity. */
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
public abstract class EnumBaseType<T> where T : EnumBaseType<T> { public static ReadOnlyCollection GetBaseValues() { ... } public static T GetBaseByKey(int key) { ... } } public class Rating : EnumBaseType<Rating> { public static ReadOnlyCollection GetValues(){ ... } public static Rating GetByKey(int key){ ... }
}
And Rating.GetByKey(int) will work fine.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Unfortunately it can't be done. Since those are static methods, when you call them they bypass the derived class instantiation.
If you call the GetBaseByKey or GetBaseValues before calling any of the derived class methods or properties, the collection will be empty. That's why you need to implement them on the derived class.
If you use this code things will work fine.
Rating r = Rating.Good; MessageBox.Show(Rating.GetBaseByKey(3));
But if you remove the first line and call the GetKey method, you will get nothing, because the list was not filled yet.
MessageBox.Show(Rating.GetBaseByKey(3));
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yes, you are right!
but if you force Rating initialization in base methods...
public abstract class EnumBaseType<T> where T : EnumBaseType<T>, new() { private static bool s_initialized; private static void InitializeT() { if(!s_initialized) { s_initialized = true; new T(); } } public EnumBaseType(int key, string value) { Key = key; Value = value; enumValues.Add((T)this); } protected EnumBaseType() { Key = -1; Value = value; } public static ReadOnlyCollection GetBaseValues() { InitializeT(); return enumValues.AsReadOnly(); }
It seems too complex
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Yeah, that's nice! The only problem is that now we have to implement a parameterless constructor on each class. But I guess it is better than implementing the static methods to work just as a bridge to the base class.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I disagree. I think having a public constructor in the enum type allows for instances to be created at run-time other than the instances that make up the enumeration. It also requires a parameterless constructor in the base class that uses an arbitrary key and null value.
I think this is a case where it is better to be same and have to paste a couple methods in the derived classes rather.
John Butler
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
How about a Dictionary rather than a List?
I suppose with a small enough number of values the performance will be about the same, but using a Dictionary eliminates the loop used to find a value.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
A dictionary would probably be a bit faster, but then you would be stuck with key and value only. The point of this article is to show how we can extend the enum functionality, by adding methods and/or properties.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I'm not sure you understand, I meant use a Dictionary in your class. Rather than
protected static List<T> enumValues = new List<T>();
use
protected static Dictionary<T,object> enumValues = new Dictionary<T,object>();
You can simply put null in for the value and only use the key. And leave everything else pretty much the same.
The overhead of having the null values may outweigh the speed gain involved in dictionary lookup, but I try to never use List. ... But that's just me.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi Scott,
I am aware that a description can be set and retrieved using attributes and reflection. In fact, I’ve been using this approach for a while.
This article focus on enhancing enums to work as full fledged classes, just like in Java, where enums can be extended, have additional fields and leverage all the OO goodies. While C# enum is more like a sealed wrapper around primitive types.
Take a look at the Java enum below.
enum DaysOfWeek{ SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
public boolean isWeekDay(){ return !isWeekEnd(); }
public boolean isWeekEnd(){ return (this == SUNDAY || this == SATURDAY); } }
IMHO that’s much more elegant than having another class to provide functionality around the enum.
Using an enhanced enum it can be done very easily. Unfortunately, my implementation still has the drawbacks mentioned in the article. So until MS comes with some changes concerning c# enums, I guess this is the closest we can get to the Java implementation.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I posted those links mainly because one of the problems you mention in the article that you were attempting to solve was displaying friendly names for enums and databinding. If that's all you need to do, I think this solution is more complex than is neccessary.
Cassio Alves wrote: While C# enum is more like a sealed wrapper around primitive types.
That's actually almost exactly what they are. Enums are a sealed class derived from ValueType and are restricted to numeric base types.
I understand the flexibility that the Java enums provide, but, in my opinion, these go beyond the core concept of an enum. I think it's cleaner to keep the enum as a simple data type and provide the additional functionality in either helper classes or other first-class datatypes that use the enum.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It's generally not a good idea to mix your UI code (ie dispaly strings) and base types. What if you wanted to support multiple languages?
Todd Smith
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi Todd,
I agree with you. Depending on the project, your UI should not be tied to your base business classes. But this article is not about MVP or strategies to display data in the UI.
This article is just showing how you can add enum behaviour to full-blown classes and use it anywhere in your code, not just in the UI.
Regards, Cassio Alves 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |