2017-07-04 17 views
9

Rozważmy następujący fragment kodu:Enum.Parse powracający nieoczekiwane członkowie

namespace ConsoleApplication1 { 
class Program { 
    public static void Main (string[] args) { 
     var en = (TestEnum)Enum.Parse(typeof(TestEnum), "AA"); 
     Console.WriteLine(en.ToString()); 
     Console.ReadKey(); 
    } 
} 

public enum TestEnum { 
    AA = 0x01, 
    AB = 0x02, 
    AC = 0x03, 
    BA = 0x01, 
    BB = 0x02, 
    BC = 0x03 
} 
} 

Jeśli wykonasz to zmienna en dostanie wartość TestEnum.BA. Teraz dowiedziałem się z tego, że flagi wyliczające powinny być unikatowe, lub uzyskać takie nieoczekiwane rzeczy, ale nie rozumiem, co się tutaj dzieje.

Jeszcze dziwniejszą częścią jest to, że kiedy dodaję atrybut [Flagi] do TestEnum, rozwiązuje on problem i zwraca TestEnum.AA zamiast TestEnum.BA, ale dla oryginalnego wyliczenia (które jest znacznie większe, około ~ 200 członków), dla których odkryłem ten problem, nie ma to znaczenia.

Rozumiem, że wyliczenia są typem wartości, więc podczas definiowania własnych flag będzie przechowywana wartość w pamięci jako 0x01 w przypadku TestEnum.AA, a po rzutowaniu z obiektu do TestEnum będzie wykonaj wyszukiwanie dla tej wartości flagi i znajdź TestEnum.BA.

Potwierdza to również uruchamiając następującą linię:

var en = (TestEnum)(object)TestEnum.AA; 
Console.WriteLine(en.ToString()); 

który wyjściowa: BA

Więc moje pytanie brzmi: co dokładnie się tu dzieje? I co ważniejsze, dlaczego dodanie atrybutu Flags ma znaczenie?

+1

Ponieważ mają tę samą wartość, wybierze jeden dość dowolnie. [To powiązane pytanie] (https://stackoverflow.com/questions/8043027/non-unique-enum-values) może pomóc. –

+0

Nie znam wewnętrznego działania enumu, więc nie będę próbował tu odpowiadać (będą to zdolniejsze osoby, które potrafią to zrobić), ale w gruncie rzeczy atrybut "[Flagi]" ** tylko ** ma znaczenie na części 'ToString()' (na formatowaniu) ... w przeciwnym razie enum działa dokładnie tak samo – Jcl

+1

Dodanie '[Flagi]' prawdopodobnie po prostu zmienia algorytm używany do określenia poprawnego wartość. Obie odpowiedzi są poprawne. – DavidG

Odpowiedz

12

Po pierwsze, nie ma to nic wspólnego z Enum.Parse(). Podstawowym typem wyliczenia jest domyślnie int, więc w twoim przykładzie TestEnum.AA i TestEnum.BA są przechowywane jako 1 i nie ma sposobu na ich odróżnienie.

Świadek następujący kod:

Console.WriteLine(TestEnum.AA); // Prints BA 
Console.WriteLine(TestEnum.BA); // Prints BA 

Po drugie, dlatego, że ustawienie atrybutu [Flags] zmienia wyjście jest inna droga, ponieważ kod jest ostrożność podczas ustalania ciąg.

Oto the code from ReferenceSource:

private static String InternalFormat(RuntimeType eT, Object value) 
{ 
    if (!eT.IsDefined(typeof(System.FlagsAttribute), false)) // Not marked with Flags attribute 
    { 
     // Try to see if its one of the enum values, then we return a String back else the value 
     String retval = GetName(eT, value); 
     if (retval == null) 
      return value.ToString(); 
     else 
      return retval; 
    } 
    else // These are flags OR'ed together (We treat everything as unsigned types) 
    { 
     return InternalFlagsFormat(eT, value); 

    } 
} 

Uwaga jak GetName() nazywa jeśli [Flags] nie jest ustawiony, w przeciwnym razie InternalFlagsFormat() nazywa.

Implementacja GetName() kończy się wyszukiwaniem binarnym w celu znalezienia wartości, natomiast InternalFlagsFormat() kończy się wyszukiwaniem liniowym w celu znalezienia wartości.

InternalFlagsFormat() musi wykonać wyszukiwanie liniowe, ponieważ może zaistnieć potrzeba ustawienia wielu wartości (np. "X | Y | Z"), aby Microsoft zaimplementował dla niego rozwiązanie O (N). Jednak dla GetName() poszli na bardziej wydajne rozwiązanie O (Log2 (N)).

Wyszukiwanie binarne może (i robi) znaleźć inną wartość niż wartość wyszukiwania liniowego, stąd różnica.

+0

Dziękuję za bardzo dokładną odpowiedź i szukanie źródeł Enum. +1 – larzz11