I’ve been tagged. . . and enumerated classes – level 200
After being tagged my numerous people, here's my 5. My
long-time readers might know some of these if they've been reading closely over
the past few years, but here are 5 things about me that aren't widely known:
- In college, I was a member of the Texas
A&M country & western dance team: www.aggiewranglers.com – I got to travel
the country and world performing c&w dance. I married a girl who was also
on the team. (all partner dancing, people, no line dancing). Think "girl being
thrown in the air". - In 2003 I was
called up for Army service to Iraq – was there 1 year and 3 days. I'm 1.5 months
from separation, so I'm hoping the nothing BIG happens between now and March
3rd. - I was using Yahoo before it had
e-mail service, and my account is one of those that is really jacked up because
my id I originally registered had to be an e-mail address. - I started writing database web applications
before the dawn of ASP 1.0 – Anybody remember .idc and .htx files? – My CGI work
was configuring WODA for a few clients. - When I was a kid, I read every book on
motorcycles/dirtbikes/ATVs in the public library, and I'd moved on to the adult
section. I was the only young kid who knew the difference between a two-stroke
and four-stroke engine. I now have a motorcycle, love to ride dirtbikes, and I'm
lobbying my wife for a Harley.
Now, I'm going to tag Scott Bellware 5 times.
So
that this post is now void of technical content, here is a tip for using
enumerations in your applications:
Consider the following class:
1 public class WorkOrder
2 {
3 private Status
_status;
4
private string _description;
5
6 public Status
Status
7
{
8
get { return _status; }
9 set { _status = value; }
10 }
11 public string
Description
12 {
13 get { return
_description; }
14 set { _description = value; }
15 }
16 }
Notice that we are using a
Status class (enum):
public enum Status
{
Pending,
InProcess,
CompleteNApproved,
}
My system manages work
orders. Realize that this is an over-simplified example, but what happens when
you need to list our many WorkOrder(s) in a grid or for a printable report?
Easy, right? No. You have this Status property that is very ugly when
displayed. "InProcess", "CompleteNApproved". Your users don't want to see
that. In business speak, it needs to be displayed as "In-Process", and
"Complete & Approved". How do we do this easily in code? On possible way
is to create a Facade class that is used in reports. This facade class could
translate the enum to our required Status titles. This is undesirable because
we then have to wrap each WorkOrder instance in a new class just for display
purposes. I would prefer that our WorkOrder could be displayed on its own, but
we have to do something about that enum. Let's turn our enum into an enumerated
class:
First, I'll rename the enum like so:
public enum StatusValue
{
Pending,
InProcess,
CompleteNApproved,
}
Then I'll make Status a
class with 3 static instances:
public class Status
{
private static Status
_pending = new Status(StatusValue.Pending, "Pending");
private static
Status _inProcess = new Status(StatusValue.InProcess, "In-Process");
private static
Status _complete = new Status(StatusValue.CompleteNApproved, "Complete & Approved");
private StatusValue _statusValue;
private string _displayTitle;
private Status(StatusValue statusValue, string displayTitle)
{
_statusValue = statusValue;
_displayTitle = displayTitle;
}
public StatusValue StatusValue
{
get { return
_statusValue; }
}
public string
DisplayTitle
{
get { return _displayTitle; }
}
public override
string ToString()
{
return _displayTitle;
}
public static Status Pending
{
get {
return _pending; }
}
public static Status
InProcess
{
get { return _inProcess; }
}
public static Status Complete
{
get {
return _complete; }
}
}
Notice that I've overridden the ToString() method
so that my _displayTitle field is returned. Now, when I throw my WorkOrder
class (or List<T> of instances) into a grid or some other binding
container, I'll see the proper display title for my Status. Now my users are
happy, and I can move a WorkOrder class around anywhere in my application. For
persisting to the database, I use the StatusValue (which can be Int32, Int16, or
byte) in my database table. It's easy to do that whether I'm using a
hand-crafted SQL statement or (my favorite) NHibernate for my ORM
solution.
Our lack of built-in enumerated classes is something I plan to
gripe about at the up-coming MVP Summit. Our enum construct is very weak and is
no more than glorified primitives. Consider what Java has as of version 5
(taken from http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html):
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7),
PLUTO (1.27e+22, 1.137e6);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
public double mass() { return mass; }
public double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
public double surfaceGravity() {
return G * mass / (radius * radius);
}
public double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
Now we need to play catch-up again. If in C# I was able to declare my
Status enum like this, I could give it a friendly display title and even some
methods right there in the enum definition. I would have less code to maintain,
and I would get the build in enum behavior for free.
Nice trick. One thing that I have done, along the same lines, is to create my own custom attributes to hold a user friendly display value. Then I write a helper to pull that information out of the attribute. Argueably not as flexible as what you’ve done here, but it got the job done for what I needed to do.
I second the custom attribute idea. Once you have the attribute class set up and a “helper” function to pull the attribute out, the syntax is very similiar like you descibed for the java implementation.
I would argue that the attribute method with use of reflection is just as flexible and easy, if not better. With the process that Jeff describes, you still have to hard-code the values that you want inside of the status class instead of at the enum level. That makes it damn hard to later add on other properties that you’d like to attribute to that enum.
The only room for improvement, I see, on the attribute method is maybe make the process of pulling the attribute through reflection a bit cleaner.
Daniel,
Perhaps a code sample of this attribute method?
Hi Jeffrey,
I found this attribute example on the codeproject and used it several times.
http://www.codeproject.com/csharp/enumwithdescription.asp
Johan,
Yes, I’m aware of that sample. In my opinion, it’s a bad option. Yes, it’s possible, but only through reflection and only by adding a UI-related property in your domain class.
With the attribute approach, there is no way to override the display text at runtime or configure it for a particular customer (and forget localization or translation).
Hi Jeffrey,
You’re right it should not be in the domain. I asume you’re using the class which is some kind of descriptor in the presenter. I like the idea and use it the next time. It is always good to see something new.
I’m not sure about adopting an enum scheme like JDK 5. However, I do have a gripe about .Net Enum and that is the lack of type safety since you can cast any number to one not just a number that is in range.
I wouldn’t shy away from the [Description] attribute, Jeffrey. Sure it’s UI related information in the domain, but it doesn’t have to be localized. You could just as easy add a localization constant instead of the string there.
Since that would be such an easy change, I wouldn’t worry about it until that was actually a requirement, citing YAGNI with the localization. (Usually this is an upfront requirement anyway)
Ben,
You are right about the YAGNI in most cases, and if this display text never needs to change, I’m sure there is a way to make that solution not feel so dirty, but any time the display text is needed in a view, this reflection code is going to have to be called (or a function that wraps the reflection code). It’s a lot of duplication.
Creating an enumerated class cuts that enumeration and is the solution that Java developers have used pre-JDK5 since 1995.
The codeproject that Johan sites is what I was referring too.
I commented that the reflection code could be cleaner and I see that as the same problem you sited: having to “add a UI-related property in your domain class.”.
I re-read, in detail, your implementation of a enumerated class and now agree (guess i jumped on the attribute wagon to soon!) that its definitely the way it should head.
Should Microsoft copy java’s syntax? It seems really clean, and the syntax seems to be a natural fit into the current .net language. What other features should it contain? ( generics? inheritance?)
FWIW, in my current project/contract, I’m going to opt with the technique that you show here. Your “enumerated class” approach does offer some flexibility that I won’t get with custom attributes on an enum (most notiably with custom serialization of said value).
I do something very similar however, your Status class eliminates the need for a StatusValue enum.
Change Status.Value to an int rather than the enum, implement equals/gethashcode, then change your private statics to public static readonlys, and your other fields to readonly.
Makes the enum superfluous.
I also use this technique to represent well known lookup table entries in code.
Bill,
That’s pretty darned close to how my team has it in our production app, but I guess I watered it down too much for the blog post. Yes, equals and GetHashCode() are definitely necessary as now we have value objects and not entities. I also map the Status class to the database through a StatusType NHibernate translator that knows to take the status code and store it in the database column (and read it back, etc).
Hello.
I did something similar with my enums. I have a class EnumClass, which I derive from and fill it with enum values together with their string descriptions. All enum values are in a collection (actually, EnumClass is derived from Collection). This way I can use the final class for each enum as a datasource – for comboboxes.
We’ve used the sort of approach you are discussing, moving the description class out of the domain at the same time.