Jak przekazać wiele wartości wyliczenia w języku C #?

117

Czasami podczas czytania kodu C # innych osób widzę metodę, która akceptuje wiele wartości wyliczenia w jednym parametrze. Zawsze myślałem, że to całkiem fajne, ale nigdy się nad tym nie zastanawiałem.

Cóż, teraz myślę, że mogę tego potrzebować, ale nie wiem jak

  1. ustaw podpis metody, aby to zaakceptować
  2. pracować z wartościami w metodzie
  3. zdefiniować wyliczenie

aby osiągnąć tego rodzaju rzeczy.


W mojej konkretnej sytuacji chciałbym skorzystać z System.DayOfWeek, który jest zdefiniowany jako:

[Serializable]
[ComVisible(true)]
public enum DayOfWeek
{ 
    Sunday = 0,   
    Monday = 1,   
    Tuesday = 2,   
    Wednesday = 3,   
    Thursday = 4,   
    Friday = 5,    
    Saturday = 6
}

Chcę mieć możliwość przekazania jednej lub więcej wartości DayOfWeek do mojej metody. Czy będę mógł używać tego konkretnego wyliczenia w obecnej postaci? Jak mam wykonać 3 rzeczy wymienione powyżej?

Ronnie Overby
źródło
1
Jak wspomniano poniżej, możesz użyć wyliczenia Flags. Możesz chcieć sprawdzić tajniki wyliczeń języka C # , które zawierają więcej informacji na temat pracy z wyliczeniami Flags.
ChaseMedallion,

Odpowiedzi:

181

Kiedy definiujesz wyliczenie, po prostu przypisz mu [Flags], ustaw wartości na potęgę dwójki i będzie działać w ten sposób.

Nic innego się nie zmienia, poza przekazywaniem wielu wartości do funkcji.

Na przykład:

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

public void RunOnDays(DaysOfWeek days)
{
   bool isTuesdaySet = (days & DaysOfWeek.Tuesday) == DaysOfWeek.Tuesday;

   if (isTuesdaySet)
      //...
   // Do your work here..
}

public void CallMethodWithTuesdayAndThursday()
{
    this.RunOnDays(DaysOfWeek.Tuesday | DaysOfWeek.Thursday);
}

Aby uzyskać więcej informacji, zobacz dokumentację MSDN dotyczącą typów wyliczenia .


Edytuj w odpowiedzi na dodatki do pytania.

Nie będziesz mógł użyć tego wyliczenia w takiej postaci, w jakiej jest, chyba że chcesz zrobić coś takiego, jak przekazanie go jako tablica / kolekcja / tablica parametrów. Umożliwiłoby to przekazanie wielu wartości. Składnia flag wymaga, aby wyliczenie było określone jako flagi (lub aby język był bastardowany w sposób, który nie został zaprojektowany).

Reed Copsey
źródło
9
Myślę, że jeśli chcesz, aby to zadziałało, wartości należy zdefiniować jako potęgi dwóch, po jednym bitu dla każdej wartości. Dzieje się tak, że przekazujesz bitowe OR wartości w parametrze, które możesz sprawdzić za pomocą operacji bitowych AND. Jeśli nie zadbasz o zdefiniowanie wartości, jak na przykład 1,2,4,8 itd., Będziesz miał problemy.
Denis Troller
1
Samo ustawienie samego atrybutu Flags nie działa automatycznie. Właśnie tego spróbowałem.
Richard Anthony Hein
1
Masz rację i usunąłem dezinformację. Poprawiono również kod Reeda.
Matthew Scharley
@monoxide: Naprawiłem to w tym samym czasie - przepraszam za nadpisanie twoich zmian.
Reed Copsey
2
Żaden problem ... Nadal wolę używać wartości szesnastkowych dla takich wartości flag, wydaje mi się to bardziej zrozumiałe, ale każda z nich ma swoje własne.
Matthew Scharley
78

Myślę, że bardziej eleganckim rozwiązaniem jest użycie HasFlag ():

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

    public void RunOnDays(DaysOfWeek days)
    {
        bool isTuesdaySet = days.HasFlag(DaysOfWeek.Tuesday);

        if (isTuesdaySet)
        {
            //...
        }
    }

    public void CallMethodWithTuesdayAndThursday()
    {
        RunOnDays(DaysOfWeek.Tuesday | DaysOfWeek.Thursday);
    }
Tillito
źródło
Jest jeszcze jedna ważna rzecz, którą należy wiedzieć, robiąc to w ten sposób: dodaje to znacznie większej elastyczności, gdy trzeba dynamicznie wypełniać wyliczenie. Np .: Jeśli użyjesz pól wyboru do zdefiniowania dni, w których chcesz, aby Twój program działał, i spróbujesz to zrobić w sposób opisany powyżej, kod będzie nieczytelny. Zamiast tego możesz to zrobić w ten sposób: ... DaysOfWeek days = new DaysOfWeek(); if (cbTuesday.Checked) days |= DaysOfWeek.Tuesday; if (cbWednesday.Checked) days |= DaysOfWeek.Wednesday;...
CoastN
25

Druga odpowiedź Reeda. Jednak podczas tworzenia wyliczenia należy określić wartości dla każdego elementu członkowskiego wyliczenia, aby było to pole bitowe. Na przykład:

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

    None = 0,
    All = Weekdays | Weekend,
    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekend = Sunday | Saturday,
    // etc.
}
Jakub
źródło
1
Pomoże ci, jeśli kiedykolwiek będziesz musiał sprawdzić wartość DaysOfWeek, na przykład jeśli czytasz taką, która jest przechowywana w bazie danych. Ponadto zapewnia lepszą dokumentację innym programistom, ponieważ niektóre wyliczają dni tygodnia zaczynając od poniedziałku zamiast niedzieli.
Jacob
3
Dla jasności i łatwości obsługi zapisałbym wyliczenie „Wszystkie” jako „Niedziela | Poniedziałek | Wtorek | Środa | Czwartek | Piątek | Sobota”… i użyłbym podobnej notacji dla „Dni powszednich” i „Weekendu”.
Robert Cartaino
Jest to w porządku, jeśli chcesz utworzyć własne wyliczenie, ale aby odpowiedzieć na pytanie, nie możesz zmienić istniejącego wyliczenia DaysOfWeek
Robert Paulson
2
Nie „może chcesz” ... MUSISZ. Tego wymagają porównania bitowe. Pozostawienie wartości nieokreślonych nie zadziała.
Richard Anthony Hein
11

W mojej konkretnej sytuacji chciałbym skorzystać z System.DayOfWeek

Nie możesz użyć System.DayOfWeek jako [Flags]wyliczenia, ponieważ nie masz nad nim kontroli. Jeśli chcesz mieć metodę, która akceptuje wiele DayOfWeek, będziesz musiał użyć paramssłowa kluczowego

void SetDays(params DayOfWeek[] daysToSet)
{
    if (daysToSet == null || !daysToSet.Any())
        throw new ArgumentNullException("daysToSet");

    foreach (DayOfWeek day in daysToSet)
    {
        // if( day == DayOfWeek.Monday ) etc ....
    }
}

SetDays( DayOfWeek.Monday, DayOfWeek.Sunday );

W przeciwnym razie możesz utworzyć własne [Flags]wyliczenie opisane przez wielu innych respondentów i użyć porównań bitowych.

Robert Paulson
źródło
2
Nie wiem, dlaczego nie pomyślałem o parametrach. Powinno to być bardzo przydatne w przypadku używania wyliczeń, które nie są polami bitowymi.
Ronnie Overby
10
[Flags]
public enum DaysOfWeek
{
  Mon = 1,
  Tue = 2,
  Wed = 4,
  Thur = 8,
  Fri = 16,
  Sat = 32,
  Sun = 64
}

Musisz określić liczby i zwiększać je w ten sposób, ponieważ przechowuje wartości w sposób bitowy.

Następnie po prostu zdefiniuj metodę, aby wziąć to wyliczenie

public void DoSomething(DaysOfWeek day)
{
  ...
}

i nazwać to czymś w rodzaju

DoSomething(DaysOfWeek.Mon | DaysOfWeek.Tue) // Both Monday and Tuesday

Aby sprawdzić, czy jedna z wartości wyliczenia została uwzględniona, sprawdź je za pomocą operacji bitowych, takich jak

public void DoSomething(DaysOfWeek day)
{
  if ((day & DaysOfWeek.Mon) == DaysOfWeek.Mon) // Does a bitwise and then compares it to Mondays enum value
  {
    // Monday was passed in
  }
}
Tetraneutron
źródło
Nie odpowiada na pytanie. „W mojej konkretnej sytuacji chciałbym użyć System.DayOfWeek”
Robert Paulson
Ale to jest dokładnie to, czego szukałem. W każdym razie dzięki.
Tillito
3
[Flags]
    public enum DaysOfWeek{


        Sunday = 1 << 0,
        Monday = 1 << 1,
        Tuesday = 1 << 2,
        Wednesday = 1 << 3,
        Thursday = 1 << 4,
        Friday =  1 << 5,
        Saturday =  1 << 6
    }

wywołaj metodę w tym formacie

MethodName (DaysOfWeek.Tuesday | DaysOfWeek.Th Thursday);

Zaimplementuj metodę EnumToArray, aby uzyskać przekazane opcje

private static void AddEntryToList(DaysOfWeek days, DaysOfWeek match, List<string> dayList, string entryText) {
            if ((days& match) != 0) {
                dayList.Add(entryText);
            }
        }

        internal static string[] EnumToArray(DaysOfWeek days) {
            List<string> verbList = new List<string>();

            AddEntryToList(days, HttpVerbs.Sunday, dayList, "Sunday");
            AddEntryToList(days, HttpVerbs.Monday , dayList, "Monday ");
            ...

            return dayList.ToArray();
        }
Rony
źródło
Nie odpowiada na pytanie. „W mojej konkretnej sytuacji chciałbym użyć System.DayOfWeek”
Robert Paulson
Dzięki, Robert, ale naprawdę nie musisz zwracać na to uwagi w każdej odpowiedzi.
Ronnie Overby
@Ronnie - To nie moja wina, że ​​jest tak wiele prawie zduplikowanych odpowiedzi, które tak naprawdę nie odpowiedziały na pytanie.
Robert Paulson
@Rony - wyliczenia ToString () i Enum.Parse () poprawnie wyprowadzą / przeanalizują wyliczenie [Flags] (o ile oczywiście uważasz na wersje wyliczania)
Robert Paulson
@Robert - Nie sądzę, żeby ktokolwiek Cię winił. Po prostu pozwól głosom zabrać głos.
Ronnie Overby
3

Oznacz swoje wyliczenie atrybutem [Flags]. Upewnij się również, że wszystkie twoje wartości wykluczają się wzajemnie (dwie wartości nie mogą się sumować, aby się równać), jak 1,2,4,8,16,32,64 w twoim przypadku

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

Jeśli masz metodę, która akceptuje wyliczenie DayOfWeek, użyj operatora bitowego lub (|), aby używać wielu elementów członkowskich razem. Na przykład:

MyMethod(DayOfWeek.Sunday|DayOfWeek.Tuesday|DayOfWeek.Friday)

Aby sprawdzić, czy parametr zawiera określony element członkowski, użyj bitowego i operatora (&) dla elementu członkowskiego, którego szukasz.

if(arg & DayOfWeek.Sunday == DayOfWeek.Sunday)
Console.WriteLine("Contains Sunday");
statenjason
źródło
Nie odpowiada na pytanie. „W mojej konkretnej sytuacji chciałbym użyć System.DayOfWeek”
Robert Paulson
2

Reed Copsey ma rację i dodałbym do oryginalnego postu, gdybym mógł, ale nie mogę, więc zamiast tego będę musiał odpowiedzieć.

Używanie [Flags] po prostu do starego wyliczenia jest niebezpieczne. Uważam, że podczas używania flag należy jawnie zmienić wartości wyliczenia na potęgi dwóch, aby uniknąć kolizji wartości. Zobacz wytyczne dotyczące FlagsAttribute i Enum .

Dan
źródło
-3

Coś takiego powinno wskazywać, czego szukasz:

[Flags]
public enum SomeName
{
    Name1,
    Name2
}

public class SomeClass()
{
    public void SomeMethod(SomeName enumInput)
    {
        ...
    }
}
Andrew Siemer
źródło