Elegir el valor predeterminado de un tipo Enum sin tener que cambiar los valores

209

En C #, ¿es posible decorar un tipo Enum con un atributo o hacer otra cosa para especificar cuál debería ser el valor predeterminado, sin tener que cambiar los valores? Los números requeridos pueden establecerse en piedra por cualquier razón, y sería útil tener control sobre el valor predeterminado.

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

Orientation o; // Is 'North' by default.
xyz
fuente
¿Debería poder cambiar el valor predeterminado para int ?, ¿doble ?, etc.
AR
2
@AR No te ofendas, pero no tiene sentido comparar enumeraciones con ints en el resumen , simplemente porque se implementan como tipos numéricos. Podríamos implementar enumeraciones como cadenas, y no cambiaría su utilidad. Considero que la respuesta aquí es una limitación de la expresividad del lenguaje C #; la limitación ciertamente no es inherente a la idea de "distinguir valores de un conjunto restringido".
jpaugh
1
@jpaugh No estoy comparando enumeraciones con ints "en abstracto". Estoy dando ejemplos de otros tipos de datos (incluidas cadenas y otras referencias) en los que cambiar los valores predeterminados no tiene sentido. No es una limitación, es una característica de diseño. La consistencia es tu amiga :)
AR
@AR Punto tomado. Supongo que paso demasiado tiempo pensando en enums como construcciones de nivel de tipo, específicamente como tipos de suma (o vea aquí . Es decir, considere cada enumeración como un tipo distinto, donde todos los valores de ese tipo son distintos de los valores de cualquier otra enumeración. Desde ese punto de vista, es imposible que las enumeraciones compartan algún valor, y de hecho, esta abstracción se desmorona cuando se considera que el valor predeterminado de 0puede o no ser válido para una enumeración dada, y simplemente se calza en cada enumeración.
jpaugh
Sí, estoy familiarizado con los sindicatos discriminados. Las enumeraciones de C # no son eso, así que no sé si es útil pensar en ellas a un nivel tan alto (e incorrecto). Mientras que un 'tipo de suma' ciertamente tiene sus ventajas, también lo hace y enum (suponiendo que se proporcionen los valores apropiados). por ejemplo, myColor = Colors.Red | Colores Azul. Mi punto principal es que si desea un tipo de suma para c #, haga uno, en lugar de intentar reutilizar el concepto de una enumeración.
AR

Respuestas:

350

El valor predeterminado para un enum(de hecho, cualquier tipo de valor) es 0, incluso si ese no es un valor válido para eso enum. No se puede cambiar.

James Curran
fuente
13
Reformular la pregunta para hacer que el valor predeterminado para un int sea 42. Piense en lo tonto que los sonidos ... la memoria se blanqueó por el tiempo de ejecución antes de recibirlo, enumeraciones son estructuras recuerdan
ShuggyCoUk
3
@DougS 1. No, pero admitir que te equivocaste es. 2. No ganó ninguna reputación de votos positivos.
Aske B.
77
Los comentarios me hicieron sospechar acerca de esta respuesta, así que la probé y verifiqué que esta respuesta definitivamente es correcta.
Ian Newson
1
@JamesCurran Pero cuando no uso valores 0 para enumeraciones, encuentro "Una excepción del tipo 'System.NullReferenceException' ocurrió en MyProjectName.dll pero no se manejó en el código de usuario. Información adicional: La referencia de objeto no está establecida en una instancia de un objeto". error. ¿Como arreglarlo? Nota: Utilizo un método de extensión para mostrar las Descripciones de enum, pero no estoy seguro de cómo evitar el error en el método auxiliar (GetDescription) definido en esta página
Jack
65

El valor predeterminado de cualquier enumeración es cero. Entonces, si desea establecer un enumerador como valor predeterminado, establezca ese uno en cero y todos los demás enumeradores en no cero (el primer enumerador que tenga el valor cero será el valor predeterminado para esa enumeración si hay varios enumeradores con el valor cero).

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'

Si sus enumeradores no necesitan valores explícitos, simplemente asegúrese de que el primer enumerador sea el que desea que sea el enumerador predeterminado, ya que "Por defecto, el primer enumerador tiene el valor 0, y el valor de cada enumerador sucesivo aumenta en 1. " ( Referencia de C # )

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

Orientation o; // initialized to 'None'
Emperador XLII
fuente
38

Si cero no funciona como el valor predeterminado adecuado, puede usar el modelo de componente para definir una solución alternativa para la enumeración:

[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);
        }
    }
}

y luego puedes llamar:

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

Nota: deberá incluir la siguiente línea en la parte superior del archivo:

using System.ComponentModel;

Esto no cambia el valor predeterminado real del lenguaje C # de la enumeración, pero proporciona una forma de indicar (y obtener) el valor predeterminado deseado.

David
fuente
lo siento, no te funciona. ¿Puedes agregar más información? necesita agregar la línea usando System.ComponentModel; y luego escriba la función GetDefaultEnum () en una clase de Utilidades. ¿Qué versión de .net estás usando?
David
3
+1 para la reutilización del atributo del modelo de componente y la solución limpia. Me gustaría recomendar cambiar la última línea de la elsedeclaración de la siguiente manera: return default(TEnum);. Puño, reducirá las Enum.GetValues(...).***llamadas más lentas , segundo (y más importante) será genérico para cualquier tipo, incluso sin enumeraciones. Además, no restringe el código para que se use solo para enumeraciones, por lo que esto realmente solucionará posibles excepciones cuando se use sobre tipos que no sean enum.
Ivaylo Slavov
1
Esto puede ser un mal uso del atributo de valor predeterminado en el contexto de la pregunta formulada. La pregunta que se hace es sobre el valor inicializado automáticamente, que no cambiará con la solución propuesta. Este atributo está destinado a los valores predeterminados de un diseñador visual que difieren del valor inicial. La persona que hace la pregunta del diseñador visual no menciona nada.
David Burg
2
Realmente no hay nada en la documentación de MSDN que ComponentModel o DefaultAttribute sea una característica única de "diseñador visual". Como dice la respuesta aceptada. La respuesta corta es "no, es imposible" redefinir el valor predeterminado de una enumeración a algo que no sea el valor 0. Esta respuesta es una solución alternativa.
David
17

El valor predeterminado de una enumeración es cualquier enumeración equivalente a cero. No creo que esto se pueda cambiar por atributo u otros medios.

(MSDN dice: "El valor predeterminado de una enumeración E es el valor producido por la expresión (E) 0").

Joe
fuente
13
Estrictamente, ni siquiera es necesario que haya una enumeración con este valor. Cero sigue siendo el valor predeterminado.
Marc Gravell
11

No puedes, pero si quieres, puedes hacer algún truco. :)

    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;
    }

uso de esta estructura como enumeración simple.
donde puede crear pa == Orientation.East (o cualquier valor que desee) de forma predeterminada
para usar el truco en sí, debe convertir de int por código.
allí la implementación:

        #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
fuente
En su operador de conversión implícita que necesita value + 1, de lo contrario ahora sus default(Orientation)devoluciones East. Además, haga que el constructor sea privado. Buen enfoque.
nawfal
10

Una forma más que podría ser útil: utilizar algún tipo de "alias". Por ejemplo:

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

    // Use Status.Default to specify default status in your code. 
    Default = New 
}
Dmitry Pavlov
fuente
1
La sugerencia es buena, sin embargo, como ya se mencionó en otras respuestas, las enumeraciones tienen un valor predeterminado de cero. En su caso, la expresión Status s = default(Status);no será ninguno de los miembros de estado, ya sque tendrá el valor de (Staus) 0. Este último es válido para escribir directamente, ya que las enumeraciones se pueden convertir a enteros, pero fallará durante la deserialización, ya que el marco se asegura de que una enumeración siempre se deserialice a un miembro válido. Su código será perfectamente válido si cambia la línea New = 10a New = 0.
Ivaylo Slavov
1
Si. Nuevo = 0 funcionaría. Yo personalmente evitaría no establecer miembros de enumeración explícitamente.
Dmitry Pavlov
7

En realidad enum, el valor predeterminado de an es el primer elemento en enumcuyo valor es 0.

Así por ejemplo:

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
fuente
9
Sin embargo, en ese caso "Pony" ES "Gato" (por ejemplo, Console.WriteLine(Animals.Pony);imprime "Gato")
James Curran
16
En realidad, usted debe: nunca especificar ningún valor O especificar todos los valores O solo especificar el valor del primer miembro definido. Lo que sucede aquí es que: Cat no tiene valor y es el primer valor, por lo que lo establece automáticamente en 0. El perro no tiene valor, lo establece automáticamente en PreviousValue + 1, es decir, 1. Poney tiene un valor, deje el valor. Entonces Cat = Pony = 0, Dog = 1. Si tienes {Cat, Dog, Pony = 0, Frog} entonces Dog = Frog = 1.
user276648
4

The default value of enum is the enummember equal to 0 or the first element(if value is not specified) ... Pero me he enfrentado a problemas críticos al usar la enumeración en mis proyectos y los he superado al hacer algo a continuación ... Cómo siempre se relacionó mi necesidad con el nivel de la clase ...

    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
    }

y obtener el resultado esperado

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

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

Ahora, ¿qué pasa si el valor proviene de DB ... Ok, en este escenario ... pase el valor en la función a continuación y convertirá el valor en enum ... la función a continuación manejó varios escenarios y es genérico ... muy rapido ..... :)

    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
fuente
-1 por no responder intencionalmente la pregunta dada. Este no es un problema obvio de tarea, que debería ser el único caso en el que intencionalmente no responda.
Xynariz
1
@Xynariz ... lo siento chicos ... mis comentarios fueron negativos por error porque mi verdadera intención era proporcionar una solución de otra manera ... Así que cambié mis comentarios ...: D
Moumit
2

El valor predeterminado para un tipo de enumeración es 0:

  • " Por defecto, el primer enumerador tiene el valor 0, y el valor de cada enumerador sucesivo se incrementa en 1. "

  • " El tipo de valor enum tiene el valor producido por la expresión (E) 0, donde E es el identificador de enumeración " .

Puede consultar la documentación para la enumeración de C # aquí , y la documentación para la tabla de valores predeterminados de C # aquí .

acoelhosantos
fuente
1

Si define la enumeración predeterminada como la enumeración con el valor más pequeño, puede usar esto:

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

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

firstEnum == Mine.

Esto no supone que la enumeración tenga un valor cero.

Tal Segal
fuente
0

El valor predeterminado es el primero en la definición. Por ejemplo:

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

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

Esto volverá His

Tony Hopkinson
fuente
jaja; El valor por defecto es cero. ¡PERO! él dice, el valor predeterminado "cero" solo pasa a ser el símbolo predeterminado del primer valor nombrado; ! en otras palabras, el idioma elige el predeterminado en lugar de usted; esto también sucede que cero es el valor predeterminado de un 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;
    }
}

Si establece la propiedad en nulo, devolverá Orientaciones. Ninguna en get. La propiedad _orientation es nula por defecto.

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

Luego en el código puedes usar

public Orientation GetDefaultOrientation()
{
   return default(Orientation);
} 
Ruslan Urban
fuente
14
/! \ WRONG /! \ El DefaultValueAttribute no establece realmente el valor, es solo para fines informativos. Entonces depende de usted verificar si su enumeración, campo, propiedad, ... tiene ese atributo. PropertyGrid lo usa para que pueda hacer clic con el botón derecho y seleccionar "Restablecer", también si no está utilizando el valor predeterminado, el texto se muestra en negrita, PERO, aún tiene que establecer manualmente su propiedad en el valor predeterminado que desea.
user276648