как использовать атрибут flags с наследованием класса от перечисления - PullRequest
0 голосов
/ 26 апреля 2018

Как реализовать множественные выборы в классе, которые наследуются от Enumeration С ограничениями?

Если у меня есть пять типов расписаний:

  • Фиксированный график
  • Повернутое расписание
  • Расписание FullTime
  • Расписание PartTime
  • Гибкое расписание

Первые два варианта - против (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);
        }
    }

Таким образом, основываясь на выборе пользователя для конкретной опции или набора опций, мне нужно вызвать метод для установки флагов (IsFixed,...) в классе Schedule, чтобы управлять классом scheduledetails в (Fixed and rotated) и количеством часов за (полный и неполный рабочий день)


Буду благодарен за любые предложения или рекомендации?

Ответы [ 6 ]

0 голосов
/ 04 мая 2018

Атрибут Flags обычно используется, когда у вас есть списки элементов с комбинациями.Вы используете его только с побитовыми операциями.Вы можете найти полную информацию здесь.

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

0 голосов
/ 03 мая 2018

Как я сказал в своем первом ответе - это слишком сложно. Но достижимо.

Когда я перечитал заголовок поста, я понял, что вы можете применить логические операции к классу, используемому вместо стандартного 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

}
0 голосов
/ 02 мая 2018

Просто используйте 2 перечисления.

1 для типа работ (фиксированный и т. Д.) и 1 для рабочей нагрузки (полный рабочий день и т. д.)

затем bool для гибкого.

Не усложняйте вещи без всякой причины, так как, глядя на то, что вы сделали, вы выкладываете много ненужного кода для сравнения.

Если вы действительно хотите сохранить все в одном перечислении, вы сэкономите намного больше кода, выполнив перечисление, подобное

  • Fixed
  • FixedFullTime
  • FixedPartTime
  • повернуты
  • RotatedFullTime
  • RotatedPartTime

и т. Д. И т. Д. Со всеми комбинациями.

У вас небольшое количество комбинаций перечислений, и не стоит создавать собственный код для проверки всех комбинаций с помощью IComparable

Просто используйте разные перечисления и в вашем расписании те

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

со сравнением между Фиксированным / Повернутым, Fulltime / Parttime и т. Д.

или используйте только одно перечисление.

0 голосов
/ 01 мая 2018

Ты слишком усложняешь это. Первая проблема, которую я подозреваю, заключается в том, что вы (или ваш бизнес-аналитик) недостаточно разбираетесь в теме бизнеса - то есть shift . Здесь у вас есть два разных перечисления:

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

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

Далее, в пользовательском интерфейсе вам понадобятся два разных выпадающих списка / группы радиосвязи, чтобы пользователь мог расположить макет сдвига, а затем сохранить его в двух разных свойствах вашего объекта.

Однако, если вы настаиваете на том, чтобы это было в одном перечислении, то есть в одном случае с отмеченными значениями перечисления, вам необходимо проверить введенные пользователем данные перед сохранением флагов в хранилище.

[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;
}
0 голосов
/ 30 апреля 2018

Я бы использовал 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 
}

Вы можете хранить все в свойстве Type Role.Вы можете быть ролью BackOffice и быть заблокированным, вы проверяете это в c #, используя побитовое сравнение

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

Вот хорошая статья о побитовом тестировании в TSQL, побитовые операции выполняются быстро идоступно во всех базах данных я думаю

0 голосов
/ 29 апреля 2018

В следующем примере 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);
        }
    }

    //...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...