C# 旗帜枚举及;逐位运算与&x201C;位串”;

C# 旗帜枚举及;逐位运算与&x201C;位串”;,c#,bit-manipulation,enum-flags,C#,Bit Manipulation,Enum Flags,一位同事建议我们将一周中选择的几天存储为1和0的7个字符字符串,即周一和周五的“1000100”。我更喜欢(并强烈建议)带有标志枚举和位操作的解决方案,我认为这是一种更干净的方法,其他开发人员应该更容易理解 [Flags()] public enum Weekdays : int { Monday = 1, Tuesday = 2, Wednesday = 4, Thursday = 8, Friday = 16, Saturday =

一位同事建议我们将一周中选择的几天存储为1和0的7个字符字符串,即周一和周五的“1000100”。我更喜欢(并强烈建议)带有标志枚举和位操作的解决方案,我认为这是一种更干净的方法,其他开发人员应该更容易理解

  [Flags()]
  public enum Weekdays : int
  {
    Monday = 1,
    Tuesday = 2,
    Wednesday = 4,
    Thursday = 8,
    Friday = 16,
    Saturday = 32,
    Sunday = 64
  }
然而,当我开始实现一个示例解决方案时,我意识到也许简单的字符串方法毕竟更容易:如果只是查看数据,那么位字符串肯定比“17”更明显。我发现C#按位操作违反直觉,而且非常冗长:

Weekdays workDays = Weekdays.Monday | Weekdays.Tuesday;
if ((workDays & Weekdays.Monday) == Weekdays.Monday) 
{...}
当然,这可以很好地包装到扩展方法中,但是我们突然得到了至少与字符串解决方案相同数量的代码行,我很难说按位代码更容易阅读

尽管如此,我仍然会使用标志枚举和按位操作。我能想到的主要好处是

  • 更好的性能
  • 存储所需的空间更少
那么,如何向我的同事推销按位解决方案呢?我应该吗?在字符串上使用此方法的其他好处是什么?在完成示例项目之后,我发现团队仍然选择了基于字符串的解决方案。我需要一些更好/更有力的论据。
为什么要使用标志枚举而不是简单的位字符串?

有趣的是,这两种方法完全相同;只有flags方法更明显

我个人会使用这些标志(尽管可能,根据您的型号,最好将列表存储为一个列表,与持有它的人相对应)

--编辑


我想,要明确的是,绩效真的不需要考虑你在做什么。所以就用最具可读性的。(其中,IMHO是命名的标志)。

问题应该围绕人眼是否会真正看到这个存储值。若真是这样,一种人类可读的格式显然很重要(但若真是这样,我会提出更大的理由,比如一组真实的日名)


然而,至少在我构建的所有应用程序中,这种数据进入某个地方的一个小字段,再也看不到,除非通过c代码——这意味着位标志绝对是最简单的——它们是代码中最容易被人读取的。您的同事真的想编写一个字符串解析器,将0和1映射到值,而不是使用内置的、使用了40多年的按位操作思想?

Flags方法是惯用的(即,这是有经验的程序员所做的,并且习惯于看到和做的,至少在C/C++/C语言中是如此).

制作一个可以容纳工作日组合的类。在类中,您可以用任何一种方式表示数据,但我肯定会选择标志枚举而不是字符串。在类之外,您只需使用枚举值,实际逻辑封装在类中

比如:

[Flags]
public enum Days {
   Monday = 1,
   Tuesday = 2,
   Wednesday = 4,
   Thursday = 8,
   Friday = 16,
   Saturday = 32,
   Sunday = 64,
   MondayToFriday = 31,
   All = 127,
   None = 0
}

public class Weekdays {

   private Days _days;

   public Weekdays(params Days[] daysInput) {
      _days = Days.None;
      foreach (Days d in daysInput) {
         _days |= d;
      }
   }

   public bool Contains(Days daysMask) {
      return (_days & daysMask) == daysMask;
   }

   public bool Contains(params Days[] daysMasks) {
      Days mask = Days.None;
      foreach (Days d in daysMasks) {
         mask |= d;
      }
      return (_days & mask) == mask;
   }

}
用法示例:

Weekdays workdays = new Weekdays(Days.MondayToFriday);
if (workdays.Contains(Days.Monday, Days.Wednesday)) {
   ...
}

您不应该创建非标准数据结构来替换标准数据结构(在本例中,是DayOfWeek内置的enum)。相反,扩展现有结构。这与您刚才提到的位标志方法的工作方式基本相同

namespace ExtensionMethods
{
    public static class Extensions
    {
        /*
         * Since this is marked const, the actual calculation part will happen at
         * compile time rather than at runtime.  This gives you some code clarity
         * without a performance penalty.
         */
        private const uint weekdayBitMask =
            1 << Monday 
            | 1 << Tuesday
            | 1 << Wednesday
            | 1 << Thursday
            | 1 << Friday;
        public static bool isWeekday(this DayOfWeek dayOfWeek)
        {
            return 1 << dayOfWeek & weekdayBitMask > 0;
        }
    }   
}

使用标志枚举的好处:

  • 标准方法:
  • 意图是明确的
  • 可维护性——新程序员应该很容易学会这一点
  • 易于扩展--支持新的旗帜组合(例如周末)
  • 快速
使用标志枚举的负面影响:

  • 难以理解的人的数据表示(例如,为17设置了哪些标志?)

使用位串的好处:

  • 程序员很容易看到哪些位是以字符串形式设置的
使用位串的负面影响:

  • 非标准方法
  • 对于不熟悉您的设计的程序员来说,更难理解
  • 可能更容易设置“垃圾”值(例如stringValue=“Sunday”)
  • 不必要的字符串创建
  • 不必要的字符串解析
  • 额外开发工作
  • 重塑车轮(但甚至不是圆形车轮)

能够查看位串以查看设置的内容有多重要?如果很难知道17是星期一和星期五,您可以使用计算器将其转换为二进制。或者添加某种字符串表示法以供“显示”(或调试)使用。没那么难


在我看来,如果要使位字符串接近实数,则需要进行大量的封装,以使其达到标志枚举已经提供的抽象级别。如果方法是直接操作位串,那么这将很难读取(和理解),并且可能容易出错

e、 g.您可能最终会看到:

days = "1000101"; // fixed bug where days were incorrectly set to "1010001"

没有解决您的问题,但既然已经有了System.DayOfWeek,为什么还要为此创建自己的枚举?System.DayOfWeek枚举值是0到6,而不是2(1、2、4、8、16、32和64)的倍数,我需要执行位运算(AFAIK)。此外,星期一是一周的第一天为了减少冗长,您可以编写:
if((workDays&Weekdays.Monday)!=0)
让我建议第三个选项:
HashSet
。您可以获得类型安全性和直观操作!我认为
workDays.Contains(DayOfWeek.Monday)
(workDays&Weekdays.Monday)!=0
工作日[0]=“1”
。我不认为建议实际使用字符串作为“替代”方法,是吗?因为我同意这是疯狂的行为。也许我理解错了,我就是这么读的
days = "1000101"; // fixed bug where days were incorrectly set to "1010001"