如何在继承Enumeration
有限制的类中实现多项选择?
如果我有五种时间表类型:
前两个选项与(Fixed vs Rotated)
相对,第二个选项(FullTime vs PartTime)
与之对比,我的意思是时间表不能同时为fixed
和rotated
或fulltime 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类以及(全职和兼职)的小时数
我会对任何建议或建议表示感谢?
答案 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为工作负荷(全职等)
然后是一个灵活的布尔。
不要无缘无故地复杂化,因为,看看你做了什么,你提出了很多不必要的代码来进行比较。
如果你真的想把所有内容保存在一个枚举中,你可以通过像
这样的枚举来节省更多的代码等所有组合等等。
您的枚举组合数量很少,并且不值得使用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; }
}
请注意,我已从您的实施中删除了大多数方法。为简洁起见,删除了一些(Equals
,GetAll
),但其他人(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