Wybieranie domyślnej wartości typu Enum bez konieczności zmiany wartości

208

Czy w języku C # można ozdobić typ Enum atrybutem lub zrobić coś innego, aby określić, jaka powinna być wartość domyślna, bez zmiany wartości? Wymagane liczby mogą być ustawione w kamieniu z jakiegokolwiek powodu, i przydałoby się mieć kontrolę nad ustawieniami domyślnymi.

enum Orientation
{
    None = -1,
    North = 0,
    East = 1,
    South = 2,
    West = 3
}

Orientation o; // Is 'North' by default.
xyz
źródło
Czy powinieneś być w stanie zmienić wartość domyślną dla int ?, double? Itd.?
AR
2
@AR Bez urazy do przeszłości, ale nie ma sensu porównywać wyliczeń do ints abstrakcyjnie , po prostu dlatego, że są one zaimplementowane jako typy numeryczne. Możemy zaimplementować wyliczenia jako ciągi i nie zmieniłoby to ich przydatności. Uważam tutaj odpowiedź za ograniczenie ekspresji języka C #; ograniczenie z pewnością nie jest nierozerwalnie związane z ideą „odróżniania wartości od ograniczonego zbioru”.
jpaugh
1
@jpaugh Nie porównuję wyliczeń do ints "in abstract". Podaję przykłady innych typów danych (w tym ciągów znaków i innych odniesień), w których zmiana wartości domyślnych nie ma sensu. To nie jest ograniczenie, to funkcja projektowa. Konsekwencja jest twoim przyjacielem :)
AR
@AR Punkt zajęty. Wydaje mi się, że spędzam zbyt dużo czasu myśląc o enums jako konstrukcjach na poziomie typu, a konkretnie jako o typach sum (lub patrz tutaj . To znaczy, uważaj każdy wyliczenie za odrębny typ, gdzie wszystkie wartości tego typu są różne od wartości pozostałych wyliczeń. Z takiego sposobu myślenia wyliczenia nie mogą dzielić żadnej wartości i rzeczywiście, ta abstrakcja rozpada się, gdy weźmie się pod uwagę, że domyślna wartość 0danego wyliczenia może, ale nie musi być, i jest po prostu uzewnętrzniona w każdym wyliczeniu.
jpaugh
Tak, znam dyskryminowane związki zawodowe. C # nie są takie, więc nie wiem, czy warto pomyśleć o nich na tak wysokim (i niepoprawnym) poziomie. Podczas gdy „typ sumy” z pewnością ma swoje zalety, podobnie jak i wyliczanie (zakładając, że podano odpowiednie wartości). np. myColor = Colors.Red | Kolory. Niebieski. Chodzi mi o to, że jeśli chcesz typ sumy dla c #, utwórz ją, zamiast próbować zmienić przeznaczenie pojęcia wyliczenia.
AR

Odpowiedzi:

349

Domyślną wartością enum(w rzeczywistości dowolnego typu wartości) jest 0 - nawet jeśli nie jest to poprawna wartość enum. Nie można tego zmienić.

James Curran
źródło
13
Przeformułuj pytanie, aby domyślna wartość int wynosiła 42. Pomyśl, jak głupi to brzmi ... Pamięć jest pusta przez środowisko wykonawcze, zanim ją dostaniesz, wyliczenia są strukturami zapamiętaj
ShuggyCoUk
3
@DougS 1. Nie, ale przyznanie się do błędu jest błędne. 2. Nie zyskał żadnej reputacji dzięki opiniom na temat komentarzy.
Aske B.,
7
Komentarze wzbudziły moje podejrzenia co do tej odpowiedzi, więc przetestowałem ją i zweryfikowałem, że ta odpowiedź jest zdecydowanie poprawna.
Ian Newson
1
@JamesCurran Ale gdy nie używam wartości 0 dla wyliczeń, napotykam „Wyjątek typu„ System.NullReferenceException ”wystąpił w MyProjectName.dll, ale nie został przetworzony w kodzie użytkownika. Informacje dodatkowe: Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu.” błąd. Jak to naprawić? Uwaga: używam metody rozszerzenia, aby wyświetlić opisy wyliczenia, ale nie jestem pewien, jak uniknąć błędu w metodzie pomocniczej (GetDescription) zdefiniowanej na tej stronie
Jack
64

Domyślna wartość dowolnego wyliczenia wynosi zero. Jeśli więc chcesz ustawić jeden moduł wyliczający na wartość domyślną, ustaw ten jeden na zero, a wszystkie inne elementy wyliczające na niezerowe (pierwszy moduł wyliczający, który ma wartość zero, będzie wartością domyślną dla tego wyliczenia, jeśli istnieje kilka modułów wyliczających o wartości zero).

enum Orientation
{
    None = 0, //default value since it has the value '0'
    North = 1,
    East = 2,
    South = 3,
    West = 4
}

Orientation o; // initialized to 'None'

Jeśli twoje moduły wyliczające nie potrzebują jawnych wartości, upewnij się, że pierwszy moduł wyliczający jest tym, który ma być domyślnym urządzeniem wyliczającym, ponieważ „Domyślnie pierwszy moduł wyliczający ma wartość 0, a wartość każdego kolejnego modułu wyliczającego jest zwiększana o 1. ” ( Odniesienie C # )

enum Orientation
{
    None, //default value since it is the first enumerator
    North,
    East,
    South,
    West
}

Orientation o; // initialized to 'None'
Cesarz XLII
źródło
38

Jeśli zero nie działa jako poprawna wartość domyślna, możesz użyć modelu komponentu do zdefiniowania obejścia dla wyliczenia:

[DefaultValue(None)]
public enum Orientation
{
     None = -1,
     North = 0,
     East = 1,
     South = 2,
     West = 3
 }

public static class Utilities
{
    public static TEnum GetDefaultValue<TEnum>() where TEnum : struct
    {
        Type t = typeof(TEnum);
        DefaultValueAttribute[] attributes = (DefaultValueAttribute[])t.GetCustomAttributes(typeof(DefaultValueAttribute), false);
        if (attributes != null &&
            attributes.Length > 0)
        {
            return (TEnum)attributes[0].Value;
        }
        else
        {
            return default(TEnum);
        }
    }
}

a następnie możesz zadzwonić:

Orientation o = Utilities.GetDefaultValue<Orientation>();
System.Diagnostics.Debug.Print(o.ToString());

Uwaga: w górnej części pliku należy umieścić następujący wiersz:

using System.ComponentModel;

Nie zmienia to faktycznej wartości domyślnej wyliczenia w języku C #, ale umożliwia wskazanie (i uzyskanie) żądanej wartości domyślnej.

David
źródło
przepraszam, to nie działa dla ciebie. Czy możesz dodać więcej informacji? musisz dodać linię za pomocą System.ComponentModel; a następnie napisz funkcję GetDefaultEnum () w klasie Utilities. Jakiej wersji .net używasz?
David
3
+1 za ponowne użycie atrybutu modelu komponentu i czystego rozwiązania. Chciałbym polecić Ci zmienić ostatni wiersz w elseoświadczeniu w następujący sposób: return default(TEnum);. Pięść, zmniejszy wolniejsze Enum.GetValues(...).***połączenia, po drugie (i co ważniejsze) będzie ogólny dla każdego rodzaju, nawet nie-enum. Poza tym nie ogranicza się kodu, który ma być używany tylko dla wyliczeń, więc faktycznie naprawi to potencjalne wyjątki, gdy jest używany w przypadku typów innych niż wyliczanie.
Ivaylo Slavov,
1
Może to być niewłaściwe użycie atrybutu wartości domyślnej w kontekście zadanego pytania. Zadane pytanie dotyczy automatycznie inicjowanej wartości, która nie zmieni się w proponowanym rozwiązaniu. Ten atrybut jest przeznaczony dla domyślnych projektantów wizualnych różniących się od wartości początkowej. Nie ma wzmianki o osobie zadającej pytanie projektantowi wizualnemu.
David Burg,
2
W dokumentacji MSDN tak naprawdę nie ma nic, co ComponentModel lub DefaultAttribute jest funkcją „tylko dla projektantów wizualnych”. Jak stwierdza zaakceptowana odpowiedź. Krótka odpowiedź brzmi „nie, to niemożliwe”, aby ponownie zdefiniować domyślną wartość wyliczenia na coś innego niż wartość 0. Ta odpowiedź jest obejściem.
David
17

Domyślną wartością wyliczenia jest dowolne wyliczenie równe zero. Nie sądzę, że można to zmienić atrybutem lub w inny sposób.

(MSDN mówi: „Domyślną wartością wyliczenia E jest wartość wytworzona przez wyrażenie (E) 0”.)

Joe
źródło
13
Ściśle mówiąc, nie ma nawet wyliczenia z tą wartością. Zero jest nadal domyślne.
Marc Gravell
11

Nie możesz, ale jeśli chcesz, możesz zrobić jakąś sztuczkę. :)

    public struct Orientation
    {
        ...
        public static Orientation None = -1;
        public static Orientation North = 0;
        public static Orientation East = 1;
        public static Orientation South = 2;
        public static Orientation West = 3;
    }

użycie tej struktury jako prostego wyliczenia.
gdzie możesz domyślnie utworzyć pa == Orientation.East (lub dowolną inną wartość),
aby użyć samej lewy, musisz przekonwertować z int przez kod.
tam wdrożenie:

        #region ConvertingToEnum
        private int val;
        static Dictionary<int, string> dict = null;

        public Orientation(int val)
        {
            this.val = val;
        }

        public static implicit operator Orientation(int value)
        {
            return new Orientation(value - 1);
        }

        public static bool operator ==(Orientation a, Orientation b)
        {
            return a.val == b.val;
        }

        public static bool operator !=(Orientation a, Orientation b)
        {
            return a.val != b.val;
        }

        public override string ToString()
        {
            if (dict == null)
                InitializeDict();
            if (dict.ContainsKey(val))
                return dict[val];
            return val.ToString();
        }

        private void InitializeDict()
        {
            dict = new Dictionary<int, string>();
            foreach (var fields in GetType().GetFields(BindingFlags.Public | BindingFlags.Static))
            {
                dict.Add(((Orientation)fields.GetValue(null)).val, fields.Name);
            }
        } 
        #endregion
Avram
źródło
W niejawnym operatorze konwersji potrzebujesz value + 1, w przeciwnym razie teraz Twoje default(Orientation)zwroty East. Ustaw też konstruktor jako prywatny. Dobre podejscie.
nawfal
10

Jeszcze jeden sposób, który może być pomocny - użycie pewnego rodzaju „aliasu”. Na przykład:

public enum Status
{
    New = 10,
    Old = 20,
    Actual = 30,

    // Use Status.Default to specify default status in your code. 
    Default = New 
}
Dmitrij Pawłow
źródło
1
Sugestia jest dobra, jednak, jak wspomniano już w innych odpowiedziach, wyliczenia mają domyślną wartość zero. W twoim przypadku wyrażenie Status s = default(Status);nie będzie żadnym członkiem statusu, ponieważ sbędzie miało wartość (Staus) 0. Ta ostatnia jest ważna do pisania bezpośrednio, ponieważ wyliczenia mogą być rzutowane na liczby całkowite, ale zawiedzie podczas deserializacji, ponieważ struktura zapewnia, że ​​wyliczenie jest zawsze deserializowane do ważnego elementu. Twój kod będzie całkowicie poprawny, jeśli zmienisz wiersz New = 10na New = 0.
Ivaylo Slavov,
1
Tak. Nowy = 0 działałoby. Osobiście unikałbym, aby nie określać wyraźnie członków enum.
Dmitrij Pawłow
7

W rzeczywistości wartością enumdomyślną jest pierwszy element, enumktórego wartością jest 0.

Na przykład:

public enum Animals
{
    Cat,
    Dog,
    Pony = 0,
}
//its value will actually be Cat not Pony unless you assign a non zero value to Cat.
Animals animal;
Cian
źródło
9
Jednak w takim przypadku „Pony” IS „Cat” (np. Console.WriteLine(Animals.Pony);Drukuje „Cat”)
James Curran,
16
Właściwie powinieneś: nigdy nie podawać żadnej wartości LUB podawać wszystkie wartości LUB podawać tylko wartość pierwszego zdefiniowanego elementu. Oto co się dzieje: Cat nie ma żadnej wartości i jest to pierwsza wartość, więc automatycznie ustawia ją na 0. Pies nie ma wartości, automatycznie ustawia ją na PreviousValue + 1, tj. 1. Poney ma wartość, pozostaw wartość. Więc Kot = Kucyk = 0, Pies = 1. Jeśli masz {Kot, Pies, Kucyk = 0, Żaba}, to Pies = Żaba = 1.
276648
4

The default value of enum is the enummember equal to 0 or the first element(if value is not specified) ... Ale napotkałem krytyczne problemy z wykorzystaniem enum w moich projektach i przezwyciężyłem, robiąc coś poniżej ... Jakkolwiek moja potrzeba była związana z poziomem klasy ...

    class CDDtype
    {
        public int Id { get; set; }
        public DDType DDType { get; set; }

        public CDDtype()
        {
            DDType = DDType.None;
        }
    }    


    [DefaultValue(None)]
    public enum DDType
    {       
        None = -1,       
        ON = 0,       
        FC = 1,       
        NC = 2,       
        CC = 3
    }

i uzyskaj oczekiwany wynik

    CDDtype d1= new CDDtype();
    CDDtype d2 = new CDDtype { Id = 50 };

    Console.Write(d1.DDType);//None
    Console.Write(d2.DDType);//None

Co jeśli wartość pochodzi z DB ... Ok w tym scenariuszu ... przekaż wartość w poniższej funkcji i przekonwertuje wartość na wyliczoną ... poniżej funkcja obsłużyła różne scenariusze i jest ogólna ... i wierz mi, że to jest bardzo szybki ..... :)

    public static T ToEnum<T>(this object value)
    {
        //Checking value is null or DBNull
        if (!value.IsNull())
        {
            return (T)Enum.Parse(typeof(T), value.ToStringX());
        }

        //Returanable object
        object ValueToReturn = null;

        //First checking whether any 'DefaultValueAttribute' is present or not
        var DefaultAtt = (from a in typeof(T).CustomAttributes
                          where a.AttributeType == typeof(DefaultValueAttribute)
                          select a).FirstOrNull();

        //If DefaultAttributeValue is present
        if ((!DefaultAtt.IsNull()) && (DefaultAtt.ConstructorArguments.Count == 1))
        {
            ValueToReturn = DefaultAtt.ConstructorArguments[0].Value;
        }

        //If still no value found
        if (ValueToReturn.IsNull())
        {
            //Trying to get the very first property of that enum
            Array Values = Enum.GetValues(typeof(T));

            //getting very first member of this enum
            if (Values.Length > 0)
            {
                ValueToReturn = Values.GetValue(0);
            }
        }

        //If any result found
        if (!ValueToReturn.IsNull())
        {
            return (T)Enum.Parse(typeof(T), ValueToReturn.ToStringX());
        }

        return default(T);
    }
Moumit
źródło
-1 za celowe nieudzielenie odpowiedzi na zadane pytanie. To nie jest oczywisty problem z pracą domową, który powinien być jedynym przypadkiem, w którym celowo nie odpowiadasz.
Xynariz
1
@Xynariz .. przepraszam chłopaki .. moje komentarze były błędnie błędnie, ponieważ moją prawdziwą intencją było dostarczenie rozwiązania w inny sposób ... Więc zmieniłem swoje komentarze ...: D
Moumit
2

Wartość domyślna dla typu wyliczenia wynosi 0:

  • Domyślnie pierwszy moduł wyliczający ma wartość 0, a wartość każdego kolejnego modułu wyliczającego jest zwiększana o 1.

  • Wyliczenie typu wartości ma wartość wytworzoną przez wyrażenie (E) 0, gdzie E jest identyfikatorem wyliczenia.

Możesz sprawdzić dokumentację dla enum C # tutaj , a dokumentację dla wartości domyślnych C # tutaj .

acoelhosantos
źródło
1

Jeśli zdefiniujesz domyślny wyliczenie jako wyliczenie o najmniejszej wartości, możesz użyć tego:

public enum MyEnum { His = -1, Hers = -2, Mine = -4, Theirs = -3 }

var firstEnum = ((MyEnum[])Enum.GetValues(typeof(MyEnum)))[0];

firstEnum == Mine.

Nie zakłada to, że wyliczenie ma wartość zerową.

Tal Segal
źródło
0

Domyślnie jest to pierwszy w definicji. Na przykład:

public enum MyEnum{His,Hers,Mine,Theirs}

Enum.GetValues(typeOf(MyEnum)).GetValue(0);

To wróci His

Tony Hopkinson
źródło
ha ha; „wartość domyślna” wynosi zero. ALE! mówi, domyślna wartość „zero” zdarza się tylko jako domyślny symbol pierwszej nazwanej wartości; ! innymi słowy, język wybiera domyślny zamiast ciebie; tak się też zdarza, że ​​zero jest wartością domyślną int .. lol
user1953264
0
enum Orientations
{
    None, North, East, South, West
}
private Orientations? _orientation { get; set; }

public Orientations? Orientation
{
    get
    {
        return _orientation ?? Orientations.None;
    }
    set
    {
        _orientation = value;
    }
}

Jeśli ustawisz właściwość na null, zwróci Orientations.None on get. Właściwość _orientation ma domyślnie wartość NULL.

Faruk A Feres
źródło
-5
[DefaultValue(None)]
public enum Orientation
{
    None = -1,
    North = 0,
    East = 1,
    South = 2,
    West = 3
}

Następnie w kodzie możesz użyć

public Orientation GetDefaultOrientation()
{
   return default(Orientation);
} 
Ruslan Urban
źródło
14
/! \ WRONG /! \ DefaultValueAttribute tak naprawdę nie ustawia wartości, służy jedynie celom informacyjnym. Następnie do ciebie należy sprawdzenie, czy twój wyliczenie, pole, właściwość ... ma ten atrybut. PropertyGrid używa go, abyś mógł kliknąć prawym przyciskiem myszy i wybrać „Resetuj”, również jeśli nie używasz wartości domyślnej, tekst jest pogrubiony - ALE - nadal musisz ręcznie ustawić swoją właściwość na żądaną wartość domyślną.
user276648,