简体   繁体   English

如何在C#中创建类似动态枚举的东西?

[英]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: 但是,我需要来自AccountStatus的更多信息,所以我创建了一个具有一些额外有用属性的类:

 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: 当我有一个使用AccountStatus的客户对象时,这非常方便,因为我可以编写如下代码:

 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. 我知道我可以将公共静态字段添加到AccountStatus类,但最终这不起作用,因为如果数据库发生更改,则必须手动更新代码。 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. 在我的实际表中有很多记录,我现在有12个记录。 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. ... Trial和ExpiredTrial都不是属性上的布尔值。 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 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. 虽然添加枚举或静态字段确实复制了一些工作(即,在代码和表中都有值),但我认为其好处大于负面因素。

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

Rather than working with a strongly-typed enum , you could just do comparisons using a string: 您可以使用字符串进行比较,而不是使用强类型enum

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. 但字符串更容易阅读,在这种情况下使用整数不提供任何类型的打字好处。

I think you could achieve this by using a Flags enumeration where you can combine values: 我认为你可以通过使用Flags枚举实现这一点,你可以在其中组合值:

[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. 也许你应该把它分成两个枚举。

我不明白为什么你不能写:

if (customer.Status.IsActive)

if you do/want something like this in your application: 如果您在应用程序中执行/想要这样的操作:

if(customer.Status == AccountStatus.Active) 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. 你怎么能在你的代码中写出实际的单词Active。 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. 一种可能的真正动态解决方案是实现某种脚本语言/ xml文件/ ...,使用户能够指定状态类型,它们的参数,并将它们与系统行为相关联。

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: 如果需要检查UserDefined状态,请将其公开为单独的属性:

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. 如果要设置初始状态,仍可以向其添加构造函数。

You could make a many-to-many relationship between AccountStatus and Description. 您可以在AccountStatus和Description之间建立多对多关系。 This way you can, at runtime, load all the different Descriptions you got, and then compare against those, using some sort of enumeration :) 这样,您可以在运行时加载所有不同的描述,然后使用某种枚举进行比较:)

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. 我没有编写CanReactivate代码,因为你没有说出它的逻辑是什么。

 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. 请注意,既然您说要将初始帐户状态指定为int,我在构造函数中接受一个int,但之后我将其转换为AccountStatusEnum以将其分配给成员变量。 That's probably not the best practice...You should pass the constructor an AccountStatusCode value. 这可能不是最佳实践......您应该将构造函数传递给AccountStatusCode值。

OK, well if you're working in C# 3.0, you could try extension methods : 好吧,如果你在C#3.0工作,你可以尝试扩展方法

// 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. 这使它远离你的课堂。

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. 我唯一要做的就是我也在使用另一个'rank'字段,以便我可以进行范围比较而不是硬编码所有状态。 For example instead of doing: 例如,而不是做:

if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial) 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. if(customer.Status <AccountStatus.Trial)在我们的枚举中我们可以按顺序放置它们。 So a new status in a new page wont break the other page's logics (depends on the rank of the status). 因此,新页面中的新状态不会破坏其他页面的逻辑(取决于状态的等级)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM