views:

423

answers:

7

I have a list of rather meaningless codes that I'm processing with a VB.NET Windows application. For the business logic I'm writing to process those codes, I'd like to use meaningful constants (like ServiceNotCovered or MemberNotEligible) instead of the original codes (like "SNCV" and "MNEL").

As far as I can tell, Enums can only map to numeric values, not to strings. So the best I've been able to come up with is a static class that exposes the constants as static readonly string fields that are internally set equal to the code values, as follows.

Public Class MyClass

    private _reasonCode as String()
    Public Property ReasonCode() As String
        'Getter and Setter...
    End Property

    Public Class ReasonCodeEnum
        Private Sub New()
        End Sub
        Public Shared ReadOnly ServiceNotCovered As String = "SNCV"
        Public Shared ReadOnly MemberNotEligible As String = "MNEL"
        'And so forth...
    End Class

End Class

'Calling method
Public Sub ProcessInput()
    Dim obj As New MyClass()
    Select Case obj.ReasonCode
        Case MyClass.ReasonCodeEnum.ServiceNotCovered
            'Do one thing
        Case MyClass.ReasonCodeEnum.MemberNotEligible
            'Do something different
        'Other enum value cases and default
    End Select
End Sub

In the example above, it would be nice if I could define MyClass.ReasonCode as having type ReasonCodeEnum, but then I'd have to make ReasonCodeEnum a nonstatic class and give it a way of setting and returning a value.

What I'm wondering is whether there's a way to use the built-in Enum functionality to do what I'm doing, or if not, are there any standard design patterns for this type of thing.

+6  A: 

You could put the strings in a dictionary and find the equivalent enum value, rather than a large Select Case statement:

Public Enum ReasonCode
    ServiceNotCovered
    MemberNotEligible
End Enum


Private mapping As New Dictionary(Of String, ReasonCode)
' Add the required mappings and look up the dictionary...
Mehrdad Afshari
A: 

Create generic Dictionary where key would be string (or Enum) and value as delegate. calling dictionary by key you could execute action bound to it.

Andrija
+2  A: 

Two options suggest themselves:

1) Use an enum and add a Description attribute to each value. You can then fairly easily build a map from value to description.

Benefits:

  • Still a value type
  • Can use in switch statements

Drawbacks:

  • Not really OO

2) Don't use a .NET enum - use something more like Java enums. This basically involves writing a public immutable class with a private constructor and provide public (readonly) shared properties/fields. Here's some C# to demonstrate what I mean - hopefully you're better at reading C# than I am at writing VB :)

public sealed class Reason
{
    public static readonly Reason ServiceNotCovered = new Reason("SNCV");
    public static readonly Reason MemberNotEligible = new Reason("MNEL");

    private readonly string code;

    private Reason(string code)
    {
        this.code = code;
    }

    public string Code
    {
        get { return code; }
    }
}

Now, you can't switch on this unfortunately (at least not in C# - I don't know if VB's Select is more flexible) but wherever you would switch it's worth thinking about whether you could provide the same functionality within the enum itself. That's a nicely OO way of thinking about it. Different reasons can provide different functionality via polymorphism. For example:

public class Reason
{
    public static readonly Reason ServiceNotCovered = new ServiceReason();
    public static readonly Reason MemberNotEligible = new EligibilityReason();

    private readonly string code;

    private Reason(string code)
    {
        this.code = code;
    }

    public string Code
    {
        get { return code; }
    }

    public abstract void DoSomething();

    private class ServiceReason : Reason
    {
        internal ServiceReason() : base("SVNC") {}

        public override void DoSomething()
        {
            // Whatever
        }
    }

    private class EligibiltyReason : Reason
    {
        internal EligibiltyReason() : base("MNEL") {}

        public override void DoSomething()
        {
            // Do something else
        }
    }
}

You can then "group" different reasons which have similar behaviour together by creating as many derived types as there are groups - and the caller doesn't have to know anything about them.

This is all assuming that VB's access control works the same way as C#'s in terms of nested types being able to access the private members (in particular the constructor) of their outer classes.

It's a fairly verbose solution in terms of the code, but it does help to keep all the decision-making with respect to the "enum" right in the type itself. There's another disadvantage though, in terms of it being a reference type - so you'll need to check for nullity in the normal way. On the other hand, enums don't provide any real defence against bad values either - you have to use Enum.IsDefined if you want to check an argument.

Jon Skeet
+1  A: 

Although it doesn't solve the problem of assigning string values to enum, which I think might be solved as others have pointed out by a simple Dictionary

Dictionary<string, MyEnum> StringEnumMap;

You should take a look at the Stateless project in google code (my prefered one) or the Simple State Machine on codeplex to encapsulate the logic. Its verbosity is just amazing and I think it could fit perfectly what you need. An example from the stateless project homepage:

var phoneCall = new StateMachine<State, Trigger>(State.OffHook);

phoneCall.Configure(State.OffHook)
    .Allow(Trigger.CallDialed, State.Ringing);

phoneCall.Configure(State.Ringing)
    .Allow(Trigger.HungUp, State.OffHook)
    .Allow(Trigger.CallConnected, State.Connected);

phoneCall.Configure(State.Connected)
    .OnEntry(t => StartCallTimer())
    .OnExit(t => StopCallTimer())
    .Allow(Trigger.LeftMessage, State.OffHook)
    .Allow(Trigger.HungUp, State.OffHook)
    .Allow(Trigger.PlacedOnHold, State.OnHold);

phoneCall.Configure(State.OnHold)
    .SubstateOf(State.Connected)
    .Allow(Trigger.TakenOffHold, State.Connected)
    .Allow(Trigger.HungUp, State.OffHook)
    .Allow(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
Jorge Córdoba
+1 Thanks for the link (and a different approach I hadn't thought of).
John M Gant
A: 

You can have two enums, one with cryptic constants, e.g. "NA", and one with descriptive constants, e.g. "NotAvailable". Constants meaning the same thing should map to the same integer. Converting between enums and integers is easy, so the remaining issue is how to convert easily between enums and strings.

Here's how:

using System.ComponentModel;
...
EnumConverter conv = new EnumConverter( typeof( MyEnum ) );
...
conv.ConvertToString( ... );
conv.ConvertFromString( ... );

No guarantees this will work fast, but it will spare you from a large switch statement.

azheglov
+1  A: 

You can emulate custom enums of any type in VB.Net using the code I posted here:
http://stackoverflow.com/questions/940002/getting-static-field-values-of-a-type-using-reflection/940340#940340

Joel Coehoorn
+1  A: 

As a learning Project The original question and the group's response are excellent. Other than having a specific reason for over complicating the issue, why not just use Constants?

Const ServiceNotCovered As String = "SNCV"
Const MemberNotEligible As String = "MNEL"

The above code is a simple static implementation. If altering or adding new values, in actual use is desirable, and/or you wish to not have to recompile - then setting the values from an external data resource is another simple alternative.

Another alternative is to simply set string values that you can change at will. Be them simple strings, array, dictionary or dozens of other methods - the basic concept here is using simple descriptive words IN THE CODE for the programmers reference - the user is (and should be) completely unaware of this coding convention.

So this is really a programmers' choice and is only restricted by factors such as re-usability, frequency of change, understanding by others and the level of expected variability of the values.