如何使用flags属性继承自枚举

时间:2018-04-26 10:11:50

标签: c# oop enums domain-driven-design

如何在继承Enumeration有限制的类中实现多项选择?

如果我有五种时间表类型:

  • 固定时间表
  • 轮播时间表
  • 完整时间表
  • 兼职时间表
  • 灵活的时间表

前两个选项与(Fixed vs Rotated)相对,第二个选项(FullTime vs PartTime)与之对比,我的意思是时间表不能同时为fixedrotatedfulltime and parttime同时。但它可能是Fixed and FullTime例如。

固定工作时间表,包括每周工作的小时数和工作天数,并且一旦雇主和工人商定了小时数和天数,就会保持一致。

灵活的工作时间表,员工和雇主共同努力确定他们能够承诺的一周中的小时数和天数。 全职工作时间表,通常需要每周37-40小时的承诺。由于工作时间长,具有全职时间表的职业有资格获得工作福利。这些福利包括休假,假期和疾病,健康保险以及不同的退休计划选择。

兼职工作时间表,即任何不足全职工作的时间表。

轮换工作时间表,让员工在一天或一周,秋千和夜班之间循环。这个周期有助于在所有员工之间分配不同的班次,这样就不会有人因为不太理想的时间而陷入困境。

所以我做了以下事情:

public class Schedule
{
    public Schedule()
    {

    }

    private ICollection<ScheduleDetail> _assignedWeeks;
    public int Id { get; set; }
    public string Name { get; set; }
    public int WorkingGroupId { get; set; }
    public ScheduleType ScheduleType { get; set; }
    public bool IsFixed { get; }
    public bool IsFlexible { get; }
    public bool IsFullTime { get; }
    public ICollection<ScheduleDetail> AssignedWeeks { get => _assignedWeeks; set => _assignedWeeks = value; }
}
public abstract class ScheduleType : Enumeration
    {
        protected ScheduleType(int value, string displayName) : base(value, displayName)
        {
        }
        public static readonly ScheduleType Fixed
       = new FixedType();
        public static readonly ScheduleType Flexible
            = new FlexibleType();
        public static readonly ScheduleType FullTime
            = new FullTimeType();
        public static readonly ScheduleType PartTime
           = new PartTimeType();
        public static readonly ScheduleType Rotated
           = new RotatedType();



        private class FixedType : ScheduleType
        {
            public FixedType() : base(1, "Fixed Work Schedule")
            {
            }
        }

        private class FlexibleType : ScheduleType
        {
            public FlexibleType() : base(2, "Flexible Work Schedule")
            {
            }
        }

        private class FullTimeType : ScheduleType
        {
            public FullTimeType() : base(3, "Full Time Work Schedule")
            {
            }
        }

        private class PartTimeType : ScheduleType
        {
            public PartTimeType() : base(4, "Part Time Work Schedule")
            {
            }
        }
        private class RotatedType : ScheduleType
        {
            public RotatedType() : base(5, "Rotated Work Schedule")
            {
            }
        }
    }
public abstract class Enumeration : IComparable
    {
        private readonly int _value;
        private readonly string _displayName;

        protected Enumeration()
        {
        }

        protected Enumeration(int value, string displayName)
        {
            _value = value;
            _displayName = displayName;
        }

        public int Value
        {
            get { return _value; }
        }

        public string DisplayName
        {
            get { return _displayName; }
        }

        public override string ToString()
        {
            return DisplayName;
        }

        public static IEnumerable<T> GetAll<T>() where T : Enumeration, new()
        {
            var type = typeof(T);
            var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

            foreach (var info in fields)
            {
                var instance = new T();
                var locatedValue = info.GetValue(instance) as T;

                if (locatedValue != null)
                {
                    yield return locatedValue;
                }
            }
        }

        public override bool Equals(object obj)
        {
            var otherValue = obj as Enumeration;

            if (otherValue == null)
            {
                return false;
            }

            var typeMatches = GetType().Equals(obj.GetType());
            var valueMatches = _value.Equals(otherValue.Value);

            return typeMatches && valueMatches;
        }

        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }

        public static int AbsoluteDifference(Enumeration firstValue, Enumeration secondValue)
        {
            var absoluteDifference = Math.Abs(firstValue.Value - secondValue.Value);
            return absoluteDifference;
        }

        public static T FromValue<T>(int value) where T : Enumeration, new()
        {
            var matchingItem = parse<T, int>(value, "value", item => item.Value == value);
            return matchingItem;
        }

        public static T FromDisplayName<T>(string displayName) where T : Enumeration, new()
        {
            var matchingItem = parse<T, string>(displayName, "display name", item => item.DisplayName == displayName);
            return matchingItem;
        }

        private static T parse<T, K>(K value, string description, Func<T, bool> predicate) where T : Enumeration, new()
        {
            var matchingItem = GetAll<T>().FirstOrDefault(predicate);

            if (matchingItem == null)
            {
                var message = string.Format("'{0}' is not a valid {1} in {2}", value, description, typeof(T));
                throw new ApplicationException(message);
            }

            return matchingItem;
        }

        public int CompareTo(object other)
        {
            return Value.CompareTo(((Enumeration)other).Value);
        }
    }

因此,基于特定选项或选项集的用户选择,我必须调用一个方法来在Schedule类中设置标志(IsFixed,...)来控制(固定和旋转)中的scheduledetails类以及(全职和兼职)的小时数

我会对任何建议或建议表示感谢?

6 个答案:

答案 0 :(得分:10)

你太过于复杂了。我怀疑的第一个问题是你(或你的业务分析师)对业务主题没有足够的把握 - 即 shift 。你在这里有两个不同的枚举:

public enum ScheduleType 
{
    Unknown = 0,
    Fixed,
    Rotated
}

public enum ScheduleLoad 
{
    Unknown = 0,
    FullTime,
    PartTime
}

接下来,在UI中,您需要两个不同的下拉框/单选按钮组,以允许用户排列移位布局,然后将其保存在对象的两个不同属性中。

但是,如果你坚持在一个枚举中使用它,因此一个带有标记枚举值的属性,你需要验证用户输入,然后将标志保存到你的存储。

[Flags]
public enum ShiftLayout
{
    Unknown = 0,
    Fixed = 1,
    Rotated = 2,
    FullTime = 4,
    PartTime = 8,
    Flexible = 16
}

然后验证执行如下:

public bool IsShiftLayoutValid(ShiftLayout layout)
{
    var isValid = layout.HasFlag(ShiftLayout.Flexible) 
        && (layout & ~ShiftLayout.Flexible) == ShiftLayout.Unknown;

    if (!isValid && !layout.HasFlag(ShiftLayout.Flexible))
    {
        var hasValidSchedule = (layout.HasFlag(ShiftLayout.Fixed) && !layout.HasFlag(ShiftLayout.Rotated))
            || layout.HasFlag(ShiftLayout.Rotated);

        var hasValidTime = (layout.HasFlag(ShiftLayout.FullTime) && !layout.HasFlag(ShiftLayout.PartTime))
            || layout.HasFlag(ShiftLayout.PartTime);

        isValid = hasValidSchedule && hasValidTime;
    }

    return isValid;
}

答案 1 :(得分:5)

以下ScheduleType示例能够保存多种类型,类似于使用位字段的方式。请注意用于类型值的十六进制值,这些值允许逻辑操作确定哪些类型构成当前值。

public class ScheduleType : FlagsValueObject<ScheduleType> {
    public static readonly ScheduleType Fixed = new ScheduleType(0x01, "Fixed");
    public static readonly ScheduleType Flexible = new ScheduleType(0x02, "Flexible");
    public static readonly ScheduleType FullTime = new ScheduleType(0x04, "Full Time");
    public static readonly ScheduleType PartTime = new ScheduleType(0x08, "Part Time");
    public static readonly ScheduleType Rotated = new ScheduleType(0x10, "Rotated");

    protected ScheduleType(int value, string name)
        : base(value, name) {
    }

    private ScheduleType(ScheduleType a, ScheduleType b) {
        foreach (var kvp in a.Types.Union(b.Types)) {
            Types[kvp.Key] = kvp.Value;
        }            
        Name = string.Join(", ", Types.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Value)) + " Work Schedule";
        Value = Types.Keys.Sum();
    }

    protected override ScheduleType Or(ScheduleType other) {
        var result=new ScheduleType(this, other);

        //Applying validation rules on new combination
        if (result.HasFlag(Fixed) && result.HasFlag(Rotated))
            throw new InvalidOperationException("ScheduleType cannot be both Fixed and Rotated");

        if (result.HasFlag(FullTime) && result.HasFlag(PartTime))
            throw new InvalidOperationException("ScheduleType cannot be both FullTime and PartTime");

        return result;
    }
}

使用HasFlag确定标志中存在哪种组合,可以应用所需的业务规则。

例如

//Applying validation rules on new combination
if (result.HasFlag(Fixed) && result.HasFlag(Rotated))
    throw new InvalidOperationException("ScheduleType cannot be both Fixed and Rotated");

if (result.HasFlag(FullTime) && result.HasFlag(PartTime))
    throw new InvalidOperationException("ScheduleType cannot be both FullTime and PartTime");

组合标志时应用了这些规则,以防止产生任何不需要的组合。

它来自以下支持价值对象

public abstract class FlagsValueObject<T> : EnumValueObject where T : FlagsValueObject<T> {
    protected readonly IDictionary<int, string> Types = new SortedDictionary<int, string>();

    protected FlagsValueObject(int value, string name)
        : base(value, name) {
        Types[value] = name;
    }

    protected FlagsValueObject() {

    }

    public static T operator |(FlagsValueObject<T> left, T right) {
        return left.Or(right);
    }

    protected abstract T Or(T other);

    public virtual bool HasFlag(T flag) {
        return flag != null && (Value & flag.Value) == flag.Value;
    }

    public virtual bool HasFlagValue(int value) {
        return (Value & value) == value;
    }
}

public class EnumValueObject : IEquatable<EnumValueObject>, IComparable<EnumValueObject> {

    protected EnumValueObject(int value, string name) {
        Value = value;
        Name = name;
    }

    protected EnumValueObject() {

    }

    public virtual string Name { get; protected set; }

    public virtual int Value { get; protected set; }

    public static bool operator ==(EnumValueObject left, EnumValueObject right) {
        return Equals(left, right);
    }

    public static bool operator !=(EnumValueObject left, EnumValueObject right) {
        return !Equals(left, right);
    }

    public int CompareTo(EnumValueObject other) {
        return Value.CompareTo(other.Value);
    }

    public bool Equals(EnumValueObject other) {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Value.Equals(other.Value);
    }

    public override bool Equals(object obj) {
        return obj is EnumValueObject && Equals((EnumValueObject)obj);
    }

    public override int GetHashCode() {
        return Value.GetHashCode();
    }

    public override string ToString() {
        return Name;
    }
}

简单的单元测试示例。

[TestClass]
public class ScheduleTypeValueObjectTests {
    [TestMethod]
    public void Should_Merge_Names() {
        //Arrange
        var fixedSchedult = ScheduleType.Fixed; //Fixed Work Schedule
        var fullTime = ScheduleType.FullTime; // Full Time Work Schedule
        var type = fixedSchedult | fullTime;

        //Act
        var actual = type.Name;

        //Assert
        actual.Should().Be("Fixed, Full Time Work Schedule");
    }


    [TestMethod]
    [ExpectedException(typeof(InvalidOperationException))]
    public void Should_Fail_Bitwise_Combination() {
        //Arrange
        var fullTime = ScheduleType.FullTime; // Full Time Work Schedule
        var partTime = ScheduleType.PartTime;

        var value = fullTime | partTime;
    }
}

HasFlag属性允许检查标志中存在哪些类型,如以下示例所示。

public class Schedule {
    public Schedule(
        //...
        ScheduleType scheduleType
        //...
        ) {

        //...

        ScheduleType = scheduleType;
    }

    //...

    public ScheduleType ScheduleType { get; set; }
    public bool IsFixed {
        get {
            return ScheduleType != null && ScheduleType.HasFlag(ScheduleType.Fixed);
        }
    }
    public bool IsFlexible {
        get {
            return
                ScheduleType != null && ScheduleType.HasFlag(ScheduleType.Flexible);
        }
    }
    public bool IsFullTime {
        get {
            return
                ScheduleType != null && ScheduleType.HasFlag(ScheduleType.FullTime);
        }
    }

    //...
}

答案 2 :(得分:4)

只需使用2个枚举。

1表示工作类型(固定等) 和1为工作负荷(全职等)

然后是一个灵活的布尔。

不要无缘无故地复杂化,因为,看看你做了什么,你提出了很多不必要的代码来进行比较。

如果你真的想把所有内容保存在一个枚举中,你可以通过像

这样的枚举来节省更多的代码
  • 固定
  • FixedFullTime
  • FixedPartTime
  • 旋转的
  • RotatedFullTime
  • RotatedPartTime

等所有组合等等。

您的枚举组合数量很少,并且不值得使用IComparable检查所有组合的自定义代码

只需使用不同的枚举,并在您的计划类中使用

public bool IsFixed { get; }
public bool IsFlexible { get; }
public bool IsFullTime { get; }

比较固定/旋转,全职/兼职等等

或仅使用单个枚举。

答案 3 :(得分:4)

正如我在第一个答案中所说 - 这太复杂了。但是可以实现。

当我重新阅读帖子标题时,我意识到您可能希望将逻辑操作应用于类,而不是使用标准C#enum。就此而言,人们需要自己实施运营商。所以我玩了一下让我的手脏了,瞧... ...

首先,定义虚拟Enumeration类。

public abstract class Enumeration
{

    public virtual int Id { get; set; }

    public virtual string Description { get; protected set; }

}

请注意,我已从您的实施中删除了大多数方法。为简洁起见,删除了一些(EqualsGetAll),但其他人(FromValue<T>)后来改进了实施;继续阅读...

public partial class ShiftVariant : Enumeration
{
    #region Variations' classes

    private class FixedShiftVariation : ShiftVariant
    {
        public FixedShiftVariation()
        {
            Id = FixedShiftId;
            Description = "Fixed Shift";
        }
    }

    private class RotatedShiftVariant : ShiftVariant
    {
        public RotatedShiftVariant()
        {
            Id = RotatedShiftId;
            Description = "Rotated Shift";
        }
    }

    private class FullTimeShiftVariation : ShiftVariant
    {
        public FullTimeShiftVariation()
        {
            Id = FullTimeShiftId;
            Description = "Full-time Shift";
        }
    }

    private class PartTimeShiftVariation : ShiftVariant
    {
        public PartTimeShiftVariation()
        {
            Id = PartTimeShiftId;
            Description = "Part-time Shift";
        }
    }

    private class FlexibleShiftVariation : ShiftVariant
    {
        public FlexibleShiftVariation()
        {
            Id = FlexibleShiftId;
            Description = "Flexible Shift";
        }
    }

    #endregion

    protected static int FixedShiftId = 2;
    protected static int RotatedShiftId = 4;
    protected static int FullTimeShiftId = 8;
    protected static int PartTimeShiftId = 16;
    protected static int FlexibleShiftId = 32;

    public static ShiftVariant NotSet = new ShiftVariant() { Id = 0, Description = "Unknown" };

    public static ShiftVariant FixedShift = new FixedShiftVariation();

    public static ShiftVariant RotatedShift = new RotatedShiftVariant();

    public static ShiftVariant FullTimeShift = new FullTimeShiftVariation();

    public static ShiftVariant PartTimeShift = new PartTimeShiftVariation();

    public static ShiftVariant FlexibleShift = new FlexibleShiftVariation();

    private static Dictionary<int, ShiftVariant> AllTheVariations = new Dictionary<int, ShiftVariant>
    {
        { FixedShiftId, FixedShift },
        { RotatedShiftId, RotatedShift },
        { FullTimeShiftId, FullTimeShift },
        { PartTimeShiftId, PartTimeShift },
        { FlexibleShiftId, FlexibleShift }
    };

    /// <summary>
    /// Enable initialization off of an integer.
    /// This replaces your FromValue<T> method. You can repeat it for strings too, matching the descriptions.
    /// </summary>
    /// <param name="id"></param>
    public static implicit operator ShiftVariant(int id)
    {
        return AllTheVariations.ContainsKey(id) ? AllTheVariations[id] : new ShiftVariant { Id = id };
    }

    /// <summary>
    /// Enable binary OR
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <returns></returns>
    public static ShiftVariant operator |(ShiftVariant left, ShiftVariant right)
    {
        return new ShiftVariant
        {
            Id = left.Id | right.Id,
        };
    }

    /// <summary>
    /// Enable binary AND
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <returns></returns>
    public static ShiftVariant operator &(ShiftVariant left, ShiftVariant right)
    {
        return new ShiftVariant
        {
            Id = left.Id & right.Id
        };
    }

    /// <summary>
    /// Enable COMPLEMENT ONE'S (negation)
    /// </summary>
    /// <param name="left"></param>
    /// <returns></returns>
    public static ShiftVariant operator ~(ShiftVariant left)
    {
        return new ShiftVariant
        {
            Id = ~left.Id
        };
    }

    private string CalculatedDesc = null;

    public override string Description
    {
        get => CalculatedDesc ?? CalculateDescription();
        protected set => CalculatedDesc = value;
    }

    public override string ToString()
    {
        return Description;
    }

    /// <summary>
    /// Figure out the description by walking currently set flags
    /// </summary>
    /// <returns></returns>
    private string CalculateDescription()
    {
        CalculatedDesc = string.Empty;

        if (AllTheVariations.ContainsKey(Id))
        {
            CalculatedDesc = AllTheVariations[Id].Description;
        }
        else
        {
            foreach (var variation in AllTheVariations)
            {
                if ((Id & variation.Key) == variation.Key)
                {
                    CalculatedDesc += " | " + variation.Value.Description;
                }
            }

            CalculatedDesc = CalculatedDesc.TrimStart(" |".ToCharArray());
        }

        return CalculatedDesc;
    }
}

主类 - ShiftVariant - 被声明为私有,因此稍后(可能在另一个程序集中)您可以使用新的枚举扩展它,如下所示:

public class ShiftVariant
{
    private class SomeOtherShiftVariation : ShiftVariation
    {
        // ...
    }

    protected static int SomeOtherShiftId = 64;

    public static ShiftVariation SomeShift = new SomeOtherShiftVariation();

    // add the new shift variation to AllTheVariations dictonary

}

答案 4 :(得分:3)

我会使用1个枚举器类型并将它们注释为标志,您可以通过枚举器顶部的[Flags]放置它。

然后,您可以创建只读属性或在枚举中声明它们。包含任何标志的组,使这些标志对该选择有效。这样,您不仅可以使用有效选项,还可以通过检查标志是否按位组来在数据库中重复使用它。就像你的代码一样。

我认为这是一个简单的问题:

[Flags]
public enum Role
{
    None=0
    NormalUser = 1,
    Moderator  = 2,
    Accounting = 4,
    Membership = 8,
    Blocked    = 16
    BackOffice = Membership | Accounting | Moderator  
    Admin      = NormalUser | Moderator  | Membership 
}

您可以将所有内容存储在“类型角色”的属性中。 您可以成为BackOffice角色并被阻止,您可以在c#中进行测试 使用按位比较

if(testRole & Role.Blocked == Role.Blocked)
{
   return;//blocked user
}

以下是a nice article关于TSQL中的逐位测试,按位操作很快并且可以在我认为的所有数据库中使用

答案 5 :(得分:3)

当您拥有包含组合的元素列表时,通常会使用标志属性。您只能使用Bitwise操作。你可以在这里找到完整的细节。

https://msdn.microsoft.com/en-us/library/system.flagsattribute(v=vs.110).aspx