how to use flags attribute with class inherit from enumeration
You're overcomplicating this too much. The first issue I suspect is you (or your business analyst) don't have enough grasp on the busines subject - i.e. shift. What you have here are two, different enumerations:
public enum ScheduleType
{
Unknown = 0,
Fixed,
Rotated
}
public enum ScheduleLoad
{
Unknown = 0,
FullTime,
PartTime
}
Next, in the UI you need two different dropdown boxes / radio groups to allow the user to arrange the shift layout, then save this in two different properties of your object.
However, if you insist on having this in one enumeration, thus one propery with flagged enum values, you need to validate the user input before saving the flags to you store.
[Flags]
public enum ShiftLayout
{
Unknown = 0,
Fixed = 1,
Rotated = 2,
FullTime = 4,
PartTime = 8,
Flexible = 16
}
Then the validation is performed like this:
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;
}
This following ScheduleType
example has the ability to hold multiple types similar to how bit fields are used. Note the hex values used for the value of the types that would allow logical operations to determine what types make up the current value.
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;
}
}
Using the HasFlag
to determine what combination exists within the flag, the desired business rules can be applied.
for example
//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");
The rules were applied when combining flags to prevent the creation of any unwanted combinations.
It is derived from the following supporting value objects
FlagsValueObject
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;
}
}
EnumValueObject
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;
}
}
Simple example unit test.
[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;
}
}
The HasFlag
property allows the ability to check what types exist within the flag as demonstrated in the following example.
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);
}
}
//...
}
Just use 2 enums.
1 for Work type (fixed etc) and 1 for work load (full time etc)
then a bool for flexible.
Do not complicate things for no reason, as, looking at what you did, you put up a lot of unnecessary code to do a comparison.
If you really want to keep everything in one enum, you will save so much more code by doing an enum like
- Fixed
- FixedFullTime
- FixedPartTime
- Rotated
- RotatedFullTime
- RotatedPartTime
etc etc etc with all the combinations.
You have a low number of enum combinations and it's not worth doing custom code to check all the combinations with an IComparable
Just use different enums and in your schedule class those
public bool IsFixed { get; }
public bool IsFlexible { get; }
public bool IsFullTime { get; }
with a comparison between Fixed/Rotated, Fulltime/Parttime etc etc
or use only a single enum.