Sailing Judo
Sailing Judo

Reputation: 11243

How can I make something like a dynamic enumeration in C#?

In an application I'm building I had an enumeration of account statuses:

 public enum AccountStatus
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

However, I needed more information from an AccountStatus so I made a class which has a few extra useful properties:

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
 }

This class get populated from a database table that might look like this:

 1,  "Active",       True,  True
 2,  "Trial",        True,  True 
 3,  "ExpiredTrial", False, True
 4,  "Expelled",     False, False

This is really handy when I have a customer object that uses the AccountStatus because I can write code like:

 if(customer.Status.CanReactivate) // Show reactivation form

However, I have lost something equally important. I can no longer do this:

 if(customer.Status == AccountStatus.Active)  // allow some stuff to happen

What would be the best way, if its even possible, to include something that will allow me to mimic the enumeration within the class. I know that I could add public static fields to the AccountStatus class, but ultimately this doesn't work because if the database changes the code would have to be manually updated. By this, I mean:

 public static readonly AccountStatus Active = new AccountStatus(1);
 public static readonly AccountStatus Trial = new AccountStatus(2);
 // etc, etc ...

I imagine there is probably a pattern for this somewhere, I just don't know what its called.

Any ideas?

CLARIFICATION

Based on the answers so far I need to clarify a couple of things.

The table above is a brief example. In my actual table there a many records, I have 12 in there right now. Plus we can add more or remove some existing. This is what I meant by "dynamic" in my question title.

Secondly, I gave a very simple use case for the ability I lost which apparently confused matters. Here is another real example:

 if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

... neither Trial nor ExpiredTrial are boolean values on the property. I don't want to add them either. That would set an even worse precedent than the one I'm trying to avoid (meaning I would have to add a new property to the class every time I added a new record to the table).

UPDATE

I selected an answer which didn't really meet was I was looking for, but suggests that I was looking for something unnecessary. After thinking about this, I concur. While adding an enum or static fields does duplicate some work (ie, having the values in both code and in a table) I think the benefits outweigh the negatives.

Upvotes: 2

Views: 1893

Answers (10)

Md Rahatur Rahman
Md Rahatur Rahman

Reputation: 3244

I think what judo tried to explain is - a new status in the DB will require to put checking s for that new status in the conditional blocks. I think I am also doing something same. Only thing I am doing is I am also using another 'rank' field so that I can do range comparison instead of hard coding all the statuses. For example instead of doing:

if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

if I could put them in order I could do:

if(customer.Status < AccountStatus.Trial) as in our enum we can put them as ordered. So a new status in a new page wont break the other page's logics (depends on the rank of the status).

Upvotes: 0

Robert Harvey
Robert Harvey

Reputation: 180788

I still think your best bet is to add your missing cases to the class.

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
      public bool Trial {get; set;}
      public bool ExpiredTrial {get; set;}
 }

Which you can call in a simpler form than your example:

if(customer.AccountStatus.Trial || customer.AccountStatus.ExpiredTrial)

If you need to check a UserDefined status, expose that as a separate property:

public AccountStatusCode Status  {get; set;}

...and call it like this:

if(customer.Status == AccountStatus.Active)

You can still add a constructor to it if you want to set an initial status.

Upvotes: 1

Emile Vrijdags
Emile Vrijdags

Reputation: 1578

if you do/want something like this in your application:

if(customer.Status == AccountStatus.Active)

You have to know in your code that "Active" is a possible status. How else would you be able to write the actual word Active in your code. The status object can be dynamic, but the rest of the program that uses the status has to know what types of status exist in order to do something useful with it. What if active doesn't exist anymore, the status object may not need to be reimplemented, but the code that uses it does.

If every kind status is fully defined by parameters like it almost seems (active and trail have the same parameters, so more are needed to differentiate (expiration date?)), then check those parameters.

If a combination of parameters has to have a name, then make some kind of lookuptable where you can translate the name into its associated parameters, or its inverse. That way the name can be dynamic for that piece of code, and the parameter values are to upto some degree.

A possible real dynamic solution would be to implement some sort of scripting language/xml file/... that enables the user to specify the kinds of status, their parameters, and associate them with system behavior.

Upvotes: 1

Robert Harvey
Robert Harvey

Reputation: 180788

OK, well if you're working in C# 3.0, you could try extension methods:

// Define extension method like this:
public static bool IsActive(this AccountStatus status)      
{            
    get { return status == AccountStatusCode.Active; }      
}
// 
// Call it like this:
if (AccountStatus.IsActive())

That keeps it out of your class.

Upvotes: 0

Gabe Moothart
Gabe Moothart

Reputation: 32082

Rather than working with a strongly-typed enum, you could just do comparisons using a string:

public static readonly AccountStatus Active = new AccountStatus("Active");

or load the type from your database:

public static readonly AccountStatus Trial = new AccountStatus( reader["StatusField"] );

You can then do explicit comparisons:

if(customer.Status == "Active")

You lose the strong typing, but that's what dynamic means :-). You can store the known string values in constants to get some of this back.

edit

You could of course do this using the corresponding integer values, like you hinted at the end of your post. But strings are easier to read, and in this case using integers doesn't offer any sort of typing benefits.

Upvotes: 2

Robert Harvey
Robert Harvey

Reputation: 180788

This code does what you described in your post. I didn't code CanReactivate because you didn't say what the logic for that was.

 public enum AccountStatusCode
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

 public class AccountStatus
 {
      private AccountStatusEnum status
      //
      // Constructor sets initial status.
      public AccountStatus(int status)
      {
          this.Status = (AccountStatusCode)status;
      }

      public int Id { get; set; }
      public string Description { get; set; }
      //
      // 
      public bool IsActive 
      { 
           get { return status == AccountStatusCode.Active; }
      }
      public bool CanReactivate { get; set; }
 }

Note that, since you said you wanted to specify the initial account status as an int, I accept an int in the constructor, but then I cast it to an AccountStatusEnum for assigning it to the member variable. That's probably not the best practice...You should pass the constructor an AccountStatusCode value.

Upvotes: 0

Pavel Minaev
Pavel Minaev

Reputation: 101565

I don't understand why you can't just write:

if (customer.Status.IsActive)

Upvotes: 1

Pawel Krakowiak
Pawel Krakowiak

Reputation: 10090

But why can't you use the enumeration as a property of that class..?

public enum State
{
    Active = 1,
    Trial = 2,
    Canceled = 3
}

public class AccountStatus
{
    public int Id {get; set;}
    public State State {get; set;}
    public string Description {get; set;}
    public bool IsActive {get; set;}
    public bool CanReactivate {get; set;}
}

And then:

if(customer.Status == AccountStatus.State.Active)  // allow some stuff to happen

Upvotes: 4

Fredrik M&#246;rk
Fredrik M&#246;rk

Reputation: 158309

I think you could achieve this by using a Flags enumeration where you can combine values:

[Flags]
public enum AccountStatus
{
    Expelled = 1,
    Active = 2,
    CanReactivate = 4,
    Canceled = 8,
    Trial = Active | CanReactivate,
    ExpiredTrial = CanReactivate,        
}

However, it feels as if those different enum values move along different scales (some describe state, some describe valid actions), so it might not be the right solution. Perhaps you should instead split it into two enums.

Upvotes: 1

cwap
cwap

Reputation: 11287

You could make a many-to-many relationship between AccountStatus and Description. This way you can, at runtime, load all the different Descriptions you got, and then compare against those, using some sort of enumeration :)

Upvotes: 0

Related Questions