简体   繁体   中英

Write multiple list enum values into another enum

From the client I get a List where each int value is the enum int value of the Enum DayOfWeek.

The list contains only those int values (days) which are visible in a time planner.

Taking the list of int values eg 0,1,2 (sunday,monday,tuesday) how would you write those values into the DayOfWeek enum in a general way working for all 7 days or no day?

Just know that the Saturday has an index of 6 while my DayOfWeek enum has 32 as value.

[Flags]
public enum DayOfWeek
{
   Sunday = 0,
   Monday = 1,
   Tuesday = 2,
   Wednesday = 4,
   Thursday = 8,
   Friday = 16,
   Saturday = 32,
   NotSet = 64,
}

UPDATE

This is the working solution code from MarcinJuraszek which is just changed to my needs:

var visibleWeekDays = new List<int>();
            for (int i = 0; i <= 6; i++)
            {
                visibleWeekDays.Add(i);
            }

            int allBitValues = visibleWeekDays.Select(i => (int)Math.Pow(2, ((i + 6) % 7))).Aggregate((e, i) => e | i);
            AllVisibleDays = (VisibleDayOfWeek) allBitValues;


[Flags]
public enum VisibleDayOfWeek
{
    None = 0,
    Mon = 1, 
    Tue = 2,
    Wed = 4,
    Thu = 8,
    Fri = 16,
    Sat = 32,
    Sun = 64
}


 public VisibleDayOfWeek AllVisibleDays { get; set; }

The above code writes all days of a week into the VisibleDayOfWeek enum which could be easily saved now in a database field.

According to Microsoft MSDN the flag enum has its None value now set to 0 again and the rest values are the power of 2.

var input = new List<int>() { 0, 1, 2 };

var output = input.Select(i => (DayOfWeek)Math.Pow(2, ((i + 6) % 7))).ToList();

That strange ((i + 6) % 7) is necessary because standard DayOfWeek starts from Sunday, and yours has 64 as a value for Sunday.

You can use LINQ also to aggregate flags into single int value:

var x = output.Select(i => (int)i).Aggregate((e, i) => e | i);

Update

For changed flags you have to change translation query:

var output = input.Select(i => (DayOfWeek)Math.Pow(2, (i - 1) % 7)).ToList();

Like this:

private void button1_Click(object sender, EventArgs e)
{
    var mylist = new List<int>() { 0, 1, 2 }; // Sunday, Monday, Tuesday

    DayOfWeek days = (DayOfWeek)0;
    foreach (var item in mylist)
        days |= (DayOfWeek)Math.Pow(2, item);

    Debug.WriteLine(days);
    // Displays "Sunday, Monday, Tuesday"
}
[Flags]
public enum DayOfWeek
{
    None = 0,
    Sunday = 1,
    Monday = 2,
    Tuesday = 4,
    Wednesday = 8,
    Thursday = 16,
    Friday = 32,
    Saturday = 64,
}

(Edited to match the edited question)

(1) Change the name of Undefined to None; this fits the semantics better.

(2) For every DayValue, set

dayOfWeek |= (DayOfWeek) Math.Pow(2,DayValue);

DayValue is the input vale coming in: 0,1, ..., 6

You could do something like this:

var days = (DayOfWeek)listOfValues.Sum();

Edit: I might have gotten your question wrong, i thought you get a list of integers and want to convert those to DayOfWeek. If you want just all days, you could do (DayOfWeek)127 or just add AllDays = 127 to the enum.

Understanding the answer to this question requires a deeper understanding of the [Flags] attribute on your enum class. You can read about this attribute (or read some guidelines about how to use it) here at MSDN

The "Flags" attribute is kind of a left-over from the C world where you didn't have all of the fancy types that C# and the CLR give you.

For example, you could have a C program that needs to track a bunch of different orthogonal conditions (we'll call them Condition0, Condition1, Condition2 etc. . .) but to save space, or to make it easier to pass these conditions around as a single unit, we want to jam them all into one 8-bit (or larger) value. What you would do then is define the conditions such that:

  • Bit 0 tracks the state of Condition0
  • Bit 1 tracks the state of Condition1
  • Bit 2 tracks the state of Condition2
  • etc.

If you remember your binary-to-decimal conversions, you'll know that - Bit 0 represents a decimal value of 1 - Bit 1 represents a decimal value of 2 - Bit 2 represents a decimal value of 4 - Bit 4 represents a decimal value of 8 - Bit n represents a decimal value of 2 to the power of n

Therefore, we can combine those two lists and see that:

  • Condition0 has a value of 1 (0b0001 in 4-bit binary)
  • Condition1 has a value of 2 (0b0010 in 4-bit binary)
  • Condition2 has a value of 4 (0b0100 in 4-bit binary)
  • etc. . .

If we need to represents that Condition1 and Condition2 have occurred, then that would mean bits 1 and bits 2 are set. In binary, that's 0b0110 and in decimal that's "6". A value that represents Condition1 and Condition2 is simply the logical OR of Condition1 and Condition2. 0b0010 OR 0b0110 is 0b0110. 2 OR 4 is 6.

Now, you may have noticed that a logical OR and the sum of Condition1 and Condition2 come out to the same value in the above example (2 + 4 = 2 OR 4). "I know, I'll just sum all of the conditions together!" you'll say. It's way easier to write List.Sum() than a for loop with ORs and an accumulator. Unfortunately, that doesn't work all the time.

  • Consider again the value of "6" or 0b0110 from above which means "Condition1 and Condition2" are true.

  • Then combine that with a value that means "Condition0 and Condition1" are set. 0b0011 or "3" in decimal

  • If you do the logical OR - (0b0110 OR 0b0011) you get 0b0111 or decimal 7. This looks like "Condition0, Condition1, and Condition2" are all true. That seems correct!

If you added the value together, though, you'll get decimal 9 (6 + 3, or 0b0110 + 0b0011) which comes out to 0b1001. 0b1001 looks like "Condition3 is true and Condition0 is true" That can't be right. How did Condition3 get involved here and what happened to Condition1 and Condition2? They were both set when we started!

Now, back to the C# world - The "Flags" attribute means "This enum can be made up of one or more orthogonal conditions that can exist or not exist independently". It's up to you, the programmer, to assign values to the different elements of the enum such that they don't overlap (just like in C). Once that's done, you can happily combine one or more of those orthogonal conditions by performing a logical OR operation on them. You can even define elements in the enum that are combinations of previous defined elements. Consider the elements

Weekends = Saturday | Sunday,
Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,

In Conclusion: Summing the flags only works in certain scenarios (mainly when each value that is being summed represents a single condition). In order to write robust code that works correctly in all cases you should probably stick with the logical OR of the flags. It might take a few extra lines to write, but it more accurately captures your intent, is easier to maintain, and is more robust if someone passes something you weren't expecting into your method. Therefore, to combine all of the elements in your list, you should OR all of them together. Something like

FlagsEnum accumulator = 0; //Or better yet, FlagsEnum accumulator = FlagsEnum.None if you've followed the design guidelines
foreach(FlagsEnum flag in ListOfflags)
{
  accumulator |= flag;
}
return accumulator;

Now, you've thrown an extra wrinkle in the works by not making "None" 0, but I'll leave fixing that as an exercise to the reader with the added note that the MSDN guidelines recommend against doing that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM