Esta ha sido una de las quejas de la mina desde que empecé a usar .NET, pero yo estaba curioso en el caso de que me faltaba algo. Mi fragmento de código no compilará (por favor, perdona la naturaleza forzada de la muestra) porque (según el compilador) una instrucción return que falta:

public enum Decision { Yes, No}

    public class Test
    {
        public string GetDecision(Decision decision)
        {
            switch (decision)
            {
                case Decision.Yes:
                    return "Yes, that's my decision";
                case Decision.No:
                    return "No, that's my decision";

            }
        }
    }

Ahora sé que puedo simplemente coloque una instrucción predeterminada para deshacerse de el compilador de advertencia, pero en mi opinión no sólo es que el código redundante, su código peligroso. Si la enumeración estaba en otro archivo y otro desarrollador viene y añade tal vez a mi enumeración sería manejado por mi defecto cláusula que no sabe nada acerca de tal vezs y hay una muy buena oportunidad vamos a introducir un error de lógica.

Mientras que, si el compilador me deja utilizar mi código de arriba, a continuación, podría identificar que tenemos un problema como mi caso, declaración de no volverían a cubrir todos los valores de mi enumeración. Claro que suena mucho más seguro para mí.

Esto es tan fundamentalmente malo para mí que quiero saber si sólo es algo que me falta, o simplemente nos tenemos que ser muy cuidadosos al utilizar enumeraciones en las sentencias switch?

EDICIÓN:
Sé que puedo plantear excepciones en el defecto o añadir un retorno fuera del conmutador, pero esto todavía son fundamentalmente hacks para dar la vuelta a un error del compilador que no debe ser un error.

Con respecto a una enumeración realmente ser un int, que es uno de los .NET pequeños secretos sucios que es bastante vergonzoso realmente. Permítanme declarar una enumeración con un número finito de posibilidades por favor me dan una compilación por:

Decision fred = (Decision)123;

y, a continuación, lanzar una excepción si alguien intenta algo como:

int foo = 123;
Decision fred = (Decision)foo;

EDIT 2:

Un par de personas han hecho comentarios sobre lo que ocurre cuando la enumeración es en un montaje diferente y cómo esto podría resultar en problemas. Mi punto es que este es el comportamiento, creo que debe suceder. Si puedo cambiar de un método de firma esto conducirá a problemas, mi premis es que la modificación de una enumeración debe ser este mismo. Tengo la impresión de que mucha gente no creo entender acerca de las enumeraciones en .NET. Yo creo que el comportamiento que está mal, y yo esperaba que alguien podría haber conocido algunos muy oscura característica que habría alterado mi opinión al respecto .NETO de las enumeraciones.

  • Creo que te refieres a «public enum Decisión { Sí, No, FileNotFound }»
  • bueno Julieta… pero probablemente debería dejar claro que es una broma, no estoy seguro de que esto es obvio para todo el mundo 😉
  • ¿De qué manera es esta de «un error del compilador que no debe ser un error»? El código es incorrecto, y el hecho de que usted sucede ser el cambio en un enum no tiene nada que ver con el error del compilador. Trate de cambiar en cualquier otro tipo sin incluir una default sección y verás el mismo error. (Incluso si usted usa un bool, que sólo puede estar en uno de dos estados).
  • Yo no estoy viendo el problema aquí. Desde las enumeraciones pueden cambiar con el tiempo, sólo tiene sentido exigir que un caso de incumplimiento. Si no se requiere, entonces pude ver que en el caso de dar la cuerda para colgar. Por ejemplo, si su enumeración vive en un montaje diferente y que la asamblea se cambia sin la tuya se vuelve a compilar, ¿qué pasaría? El programa se bloquee en tiempo de ejecución. Tiene perfecto sentido exigir un dfault caso, ya que las enumeraciones son modificables en cualquier momento. Bools, por otro lado… que uno no tiene sentido.
  • trabajando en su base de que las enumeraciones pueden cambiar, así que puede métodos, eso no significa que todas las clases deben tener un método predeterminado para caer de nuevo en el caso .RED no puede encontrar la firma del método que usted solicitó?
  • el compilador me dice que hay una instrucción return que faltan ya que debido a la .NET implimentation de una enumeración puede encontrar una ruta de código que no encuentro tienen un valor de retorno. Que es cierto sólo porque .NET enumeraciones no son ciertas las enumeraciones. Si fueran sólo hay dos rutas posibles a través de la rutina.
  • Esto no es debido a la .NETO de la aplicación de enum, es la manera en que la switch declaración de obras: podrás conseguir el mismo error si se utiliza una bool y cubrir el true y false caminos, pero omite el default; o si usted utiliza un byte y cubrir todos los 256 posibles caminos sin un default; o el uso de un corto y cubrir todos los 65536 caminos sin un default, etc, etc.
  • El compilador tiene sentido. Para un indeterminado caso, de la tierra después de que el interruptor, con lo que no le quedan instrucción return. La forma en que lo veo, tienes dos opciones válidas: (A) agregar un valor predeterminado, que arroja NotImplementedException, lo que tiene sentido para proteger contra posibles nuevos valores de enumeración y otros valores integrales que su enumeración no define, pero sin duda puede ser transferida a este método, o (B) añadir una instrucción return con un comentario que indica la razón por la que podría ser válido para llegar aquí y conscientemente no hacer nada.

InformationsquelleAutor Mark | 2009-07-08

10 Comentarios

  1. 37

    Diablos, la situación es mucho peor de lo que solo se ocupan de las enumeraciones. Ni siquiera podemos hacer esto para bools!

    public class Test {        
      public string GetDecision(bool decision) {
        switch (decision) {
           case true: return "Yes, that's my decision";                
           case false: return "No, that's my decision"; 
        }
      }
    }

    Produce el mismo error.

    Incluso si usted resuelve todos los problemas con las enumeraciones de ser capaces de asumir cualquier valor, usted todavía tiene este problema. El análisis de flujo de las reglas del idioma, simplemente no consideran los interruptores sin valores predeterminados para ser «exhaustiva» de todas las posibles rutas de código, incluso cuando usted y yo sabemos que son.

    Me gustaría mucho que arreglar eso, pero, francamente, tenemos muchas prioridades más altas de la fijación de esta tonta problema, así que nunca he llegado a hacerlo.

    • Gracias Eric por ser capaz de ver a través de mis divagaciones al corazón de la cuestión. Sin embargo, no ve usted que en el caso de la enumeración es, en realidad puede conducir a problemas, obligando a redundantes defecto caso de que me convierta en peligrosa.
    • Yep. El hecho de que las enumeraciones no son nada más que fantasía enteros, y el hecho de que las enumeraciones pueden cambiar de versión a versión, los hacen peligrosos para el uso. Eso es lamentable; la próxima vez que el diseño de un tipo de sistema desde cero, usted sabrá no repetir este error.
  2. 17

    Que es porque el valor de decision en realidad podría ser un valor que no es parte de la enumeración, por ejemplo :

    string s = GetDecision((Decision)42);

    Este tipo de cosa no está impedido por el compilador o el CLR. El valor también podría ser una combinación de valores de enumeración :

    string s = GetDecision(Decision.Yes | Decision.No);

    (aunque la enumeración no tiene la Flags atributo)

    Porque de eso, usted debe siempre poner un default caso de que usted cambie, ya que no podemos comprobar todos los posibles valores de manera explícita

    • Aprecio que usted puede hacer esas cosas, pero para mí son sólo los casos de .RED que le da la cuerda para colgar.
    • Bueno, en realidad que tiene sentido para las enumeraciones con el Flags atributo… este atributo significa que usted puede combinar los valores, que a menudo resulta en valores que no son explícitamente parte de la enumeración, pero son válidos, sin embargo. Sin embargo, me gustaría que el compilador permitir que esto sólo para Flags las enumeraciones…
    • En realidad, se está dando suficiente cuerda para interactuar de manera eficiente con COM existente y la api de WIN32 que definir enumeran los tipos integrales para sus banderas. Pero sí, tienes que tener cuidado con eso de la cuerda.
  3. 16

    Lanzar una excepción en el defecto cláusula:

    default:
        throw new ArgumentOutOfRangeException("decision");

    Esto asegura que todos los posibles caminos que están cubiertos, evitando los errores de la lógica resultante de añadir un nuevo valor.

  4. 7
    public enum Decision { Yes, No}
    
    public class Test
    {
        public string GetDecision(Decision decision)
        {
            switch (decision)
            {
                case Decision.Yes:
                    return "Yes, that's my decision";
                case Decision.No:
                    return "No, that's my decision";
                default: throw new Exception(); //raise exception here.
    
            }
        }
    }
  5. 2

    El valor predeterminado es para protegerlo. Lanzar una excepción de la predeterminada y si alguien añade un extra de enum, está cubierto con algo de bandera.

    • Sé que puedo hacer eso, pero aún sigue siendo la introducción de una excepción de tiempo de ejecución que podrían evitarse. .NETA te obliga a usar los saltos en lugar de permitir otoño-throughs para evitar errores en la lógica de forzar el uso de un defecto es simplemente buscar problemas.
    • No puede ser capturado en tiempo de compilación porque de lo que Thomas L dijo, por lo que el defecto está ahí para asegurarse de que está atrapado.
  6. 2

    Me doy cuenta de que este es un hilo de resurrección…

    Yo, personalmente, siento que la forma en que el interruptor funciona es correcta, y no funciona como me gustaría lógicamente desea.

    Estoy sorprendido de oír quejarse de la etiqueta predeterminada.

    Si sólo tiene un estricto conjunto de enumeraciones o valores que se están probando, usted no necesita todos los de la excepción líneas de manipulación, o una devolución fuera del conmutador, etc.

    Sólo tienes que poner la etiqueta predeterminada a través de una de las otras etiquetas, tal vez la etiqueta que sería la respuesta más común. En el caso de ejemplo es probable que no importa que uno. Corto, dulce, y satisface sus necesidades para deshacerse de la advertencia del compilador:

    switch (decision)
    {
        default:
        case Decision.Yes:
            return "Yes, that's my decision";
        case Decision.No:
            return "No, that's my decision";
    }

    Si usted no desea que el defecto de ser que Sí, poner la etiqueta predeterminada por encima de la etiqueta.

    • De nuevo, me doy cuenta de que este es un hilo de resurrección – la ironía no está perdido en mí… El OP punto es que si se va a añadir un Decision.Maybe a su enumeración, a continuación, su sugerencia de que no iba a funcionar. Ninguno de los actuales rutas de código se aplican para Maybe por lo que su código se esconde un error. Si usted se permite omitir el defecto, el compilador podría decir «aguante! Usted no devuelve nada si la decisión fue tal vez!» y en tiempo de compilación que tendría el potencial de error marcados para usted. Sospecho que el OP es consciente de que usted puede poner por defecto en una rama existente, simplemente no lo quieren hacer.
  7. 2

    Por el bien de compartir una peculiar idea si nada, aquí va:

    Siempre puede implementar su propio fuerte enumeraciones

    …y desde la introducción de la nameof operador también se pueden utilizar en el interruptor de los casos.
    (No es que no se podía técnicamente hacerlo antes, pero era difícil hacer tal código legible y refactorizar amigable.)

    public struct MyEnum : IEquatable<MyEnum>
    {
    private readonly string name;
    private MyEnum(string name) { name = name; }
    public string Name
    {
    //ensure observable pureness and true valuetype behavior of our enum
    get { return name ?? nameof(Bork); } //<- by choosing a default here.
    }
    //our enum values:
    public static readonly MyEnum Bork;
    public static readonly MyEnum Foo;
    public static readonly MyEnum Bar;
    public static readonly MyEnum Bas;
    //automatic initialization:
    static MyEnum()
    {
    FieldInfo[] values = typeof(MyEnum).GetFields(BindingFlags.Static | BindingFlags.Public);
    foreach (var value in values)
    value.SetValue(null, new MyEnum(value.Name));
    }
    /* don't forget these: */
    public override bool Equals(object obj)
    {
    return obj is MyEnum && Equals((MyEnum)obj);
    }
    public override int GetHashCode()
    {
    return Name.GetHashCode();
    }
    public override string ToString()
    {
    return Name.ToString();
    }
    public bool Equals(MyEnum other)
    {
    return Name.Equals(other.Name);
    }
    public static bool operator ==(MyEnum left, MyEnum right)
    {
    return left.Equals(right);
    }
    public static bool operator !=(MyEnum left, MyEnum right)
    {
    return !left.Equals(right);
    }
    }

    y usarlo así:

    public int Example(MyEnum value)
    {
    switch(value.Name)
    {
    default: //case nameof(MyEnum.Bork):
    return 0;
    case nameof(MyEnum.Foo):
    return 1;
    case nameof(MyEnum.Bar):
    return 2;
    case nameof(MyEnum.Bas):
    return 3;
    }
    }

    y usted, por supuesto, llamar a ese método como:

    int test = Example(MyEnum.Bar); //returns 2

    Que ahora podemos conseguir fácilmente el Nombre básicamente se trata de un bono, y sí algunos de los lectores de señalar que esto es básicamente un Java enum sin el nulo caso (ya que no es una clase). Y al igual que en Java se pueden agregar los datos adicionales y /o propiedades que usted desea, por ejemplo, un valor ordinal.

    Legibilidad: Comprobar!

    Intellisense: Comprobar!

    Refactorability: Comprobar!

    Es un tipo de valor: Comprobar!

    Cierto enumeración: Comprobar!



    Es eficiente? En comparación a los nativos de las enumeraciones; no.

    Se debe usar esto? Hmmm….

    ¿Qué tan importante es para usted tener una verdadera enumeraciones así que usted puede deshacerse de enum verificaciones en tiempo de ejecución y sus correspondientes excepciones?

    No sé. Realmente no puedo contestar que para usted estimado lector; a cada uno lo suyo.

    …De hecho, mientras escribo esto, me di cuenta de que probablemente sería más limpio para permitir que la estructura «wrap» normal enum. (La estática de la estructura de los campos y la normal correspondiente enumeración de creación de reflejo de uno a otro con la ayuda de reflexión similar como la anterior). Nunca uso el normal enum como un parámetro y en lo que eres bueno.

    ACTUALIZACIÓN :

    Yepp, pasó la noche a cabo pruebas de mis ideas, y estaba en lo cierto: ahora tengo casi perfecto java-estilo de las enumeraciones en c#. El uso es limpia y mejora el rendimiento.
    Lo mejor de todo: todos los guarros de mierda es encapsulado en la base de clase, su propia aplicación concreta puede ser tan limpio como este:

    //example java-style enum:
    public sealed class Example : Enumeration<Example, Example.Const>, IEnumerationMarker
    {
    private Example () {}
    ///<summary> Declare your enum constants here - and document them. </summary>
    public static readonly Example Foo = new Example ();
    public static readonly Example Bar = new Example ();
    public static readonly Example Bas = new Example ();
    //mirror your declaration here:
    public enum Const
    {
    Foo,
    Bar,
    Bas,
    }
    }

    Esto es lo que puede hacer:

    • Usted puede agregar cualquier cantidad de campos privados que desee.
    • Usted puede agregar cualquier cantidad de público no estático campos que desee.
    • Usted puede agregar cualquier cantidad de propiedades y métodos que desee.
    • Usted puede diseñar su constructores sin embargo que usted desea, porque:
    • Usted puede olvidarse de base constructor de molestia. Constructor Base es el parámetro menos!

    Esto es lo que debe hacer:

    1. Su enumeración debe ser una clase cerrada.
    2. Todas sus constructores deben ser privadas.
    3. Su enumeración debe heredar directamente de la Enumeración<T, U> y heredar el vacío IEnumerationMarker interfaz.
    4. El primer parámetro de tipo genérico a la Enumeración<T, U> debe ser su clase enum.
    5. Para cada campo estático público debe existir un nombre idéntico valor en el Sistema.Enum (que se especifica como el segundo parámetro de tipo genérico a la Enumeración<T, U>).
    6. Todo su público los campos estáticos debe ser de sólo lectura y de su tipo enum.
    7. Todo su público los campos estáticos debe asignar un único valor no nulo durante el tipo de inicialización.

    En el momento en que cada invariante anterior es afirmado en el tipo de inicialización. Podría intentar ajustar más tarde para ver si algunos de ellos pueden ser detectados en tiempo de compilación.

    Los Requisitos De Justificación:

    1. Su enumeración deben ser sellados porque si no es así entonces otros invariantes vuelto mucho más complicado para ningún beneficio evidente.
    2. Permitiendo público constructores no tiene sentido. Es un tipo de enumeración, que es básicamente un singleton tipo pero con un conjunto fijo de las instancias en lugar de sólo uno.
    3. Misma razón que la primera. La reflexión y algunos de los otros invariantes y comprobación de restricción sólo se vuelve desordenado si no lo es.
    4. Necesitamos este parámetro de tipo genérico por lo que el tipo de sistema puede ser utilizado únicamente para almacenar nuestros datos de enumeración con rendimiento compilar/Jit tiempo de enlaces. No hash-tablas u otros lentos mecanismos son usados! En teoría se puede quitar, pero creo que no vale la pena el costo adicional en la complejidad y el rendimiento para hacerlo.
    5. Este debería ser bastante obvio. Necesitamos estas constantes para la fabricación de elegantes interruptor de declaraciones. Por supuesto, yo podría hacer una segunda enumeración de tipo que no tiene; usted todavía podría ser capaz de cambiar el uso de la nameof método mostrado anteriormente. Simplemente no sería tan eficiente. Todavía estoy contemplando si debe relajarse con este requisito o no. Voy a experimentar en él…
    6. Enum constantes debe ser pública y estática, por razones obvias, y los campos readonly porque: a) tener readonly instancias de enumeración significa que todas las comprobaciones de igualdad se simplifica a la igualdad de referencia; b) las propiedades son más flexibles y detallado, y, francamente, tanto de aquellos que no son deseables propiedades para una enumeración de implementación. Por último todos los públicos campos estáticos deben ser de su enumeración de tipo simplemente porque; a) mantiene el tipo de enumeración más limpio, con menos desorden; b) hace que la reflexión más simple; y c) usted es libre de hacer lo que sea con las propiedades de todos modos, así que es un muy suave restricción.
    7. Esto es debido a que estoy tratando de mantener la «desagradable reflexión magia» a un mínimo. No quiero que mi enum implementación requieren de plena confianza de ejecución. Que limitaría seriamente su utilidad. Más precisamente, llamando a los constructores privados o escribiendo a los campos readonly puede lanzar excepciones de seguridad en un bajo entorno de confianza. Por lo tanto su enumeración debe crear una instancia de su enum constantes en tiempo de inicialización de sí mismo – luego podemos rellenar el (interna) de la clase de base de datos de los casos se «limpia».

    Así que de todos modos, ¿cómo puede usted utilizar estos java-estilo de las enumeraciones?

    Bien he implementado esta materia por ahora:

    int ordinal = Example.Bar.Ordinal; //will be in range: [0, Count-1]
    string name = Example.Bas.Name; //"Bas"
    int count = Enumeration.Count<Example>(); //3
    var value = Example.Foo.Value; //<-- Example.Const.Foo
    Example[] values;
    Enumeration.Values(out values);
    foreach (var value in Enumeration.Values<Example>())
    Console.WriteLine(value); //"Foo", "Bar", "Bas"
    public int Switching(Example value)
    {
    if (value == null)
    return -1;
    //since we are switching on a System.Enum tabbing to autocomplete all cases works!
    switch (value.Value)
    {
    case Example.Const.Foo:
    return 12345;
    case Example.Const.Bar:
    return value.GetHasCode();
    case Example.Const.Bas:
    return value.Ordinal * 42;
    default:
    return 0;
    }
    }

    El resumen de clase de Enumeración también pondrá en marcha el IEquatable<Example> interfaz para nosotros, incluyendo a == y != los operadores que trabajarán en el Ejemplo de los casos.

    Aparte de la reflexión necesaria durante el tipo de inicialización todo está limpio y eficiente. Probablemente se mueva para poner en práctica las colecciones especializadas de java tiene para las enumeraciones.

    Entonces, ¿dónde está este código, entonces?

    Quiero ver si puedo limpiar un poco más antes de que me suelte, pero es probable que sea en una rama de dev en GitHub por el final de la semana – a menos que otras de mis locos proyectos en los que trabajar! ^_^

    Hasta ahora en los GitHub

    Ver Enumeration.cs y Enumeration_T2.cs.

    Actualmente son parte de la rama de dev de un wip de la biblioteca en el que estoy trabajando.
    (Nada es «desmontable», sin embargo y sujeto a cambios recientes en cualquier momento.)

    …Por ahora el resto de la biblioteca es sobre todo una mierda tonelada de repetitivo para ampliar toda la gama de métodos para multi-rango de las matrices, hacer multi-rango de las matrices utilizable con Linq, y performante ReadOnlyArray envolturas (inmutable structs) para exponer (privado) de las matrices de una manera segura sin la cringy necesidad de crear copias todo el tiempo.

    Todo* excepto la última dev comete es totalmente documentado y IntelliSense ambiente.

    (*java tipos de enumeración son todavía wip y serán documentadas adecuadamente una vez que haya finalizado su diseño.)

  8. 1

    Siempre pienso que por defecto como la caída a través de/excepción.

    Así que aquí no sería tal vez, pero en lugar de ello sería «no Válido de Decisión, póngase en contacto con el soporte».

    No veo cómo se iba a caer a través de eso, pero que iba a ser el cajón de sastre/caso de excepción.

  9. 0

    Además para el caso de, usted puede convertir cualquier tipo int a su enumeración y tienen una enumeración que no manejo. También está el caso en el que, si la enumeración es en una externa .dll, y que .dll está actualizado, no romper su código si una opción adicional que se agrega a la enum (como Sí, No, tal vez). Así, para el manejo de los futuros cambios que necesita el valor predeterminado caso así. No hay ninguna manera de garantizar en tiempo de compilación que usted sabe que cada valor enum tiene para su vida.

  10. 0

    Lugar de quejarse de cómo la instrucción switch funciona, evitar por completo mediante el uso de un método de extensión en el enum como explicó aquí y aquí.

    Lo bueno de este enfoque es que no se encuentran en una situación y el olvido de actualizar su GetDecision instrucción switch al agregar un nuevo valor de enumeración, porque todos juntos en el mismo lugar – en la declaración de enumeración.

    La eficacia de este enfoque es que no me conocen y, realmente, ni siquiera una consideración en este punto. Eso es correcto, realmente no me importa porque es mucho más fácil para me que me figura, «Phhhtt tornillo de la computadora que es de lo que se allí – para trabajar duro.» (Me puede lamentar que la actitud de Skynet toma el relevo.)

    Si alguna vez me necesita obtener uno de los valores de atributo de vuelta a la enumeración de valor, simplemente puedo construir un diccionario inverso de y rellenar con una sola línea de código.

    (Que suele hacer un ‘no se establece» como el primer elemento de la enumeración, porque, como el OP notas, un C# enum es realmente un int para una variable no inicializada va a ser cero, o que el primer valor de enumeración)

    public enum Decision
    {
    [DisplayText("Not Set")]
    NotSet,
    [DisplayText("Yes, that's my decision")]
    Yes,
    [DisplayText("No, that's my decision")]
    No
    }
    public static class XtensionMethods
    {
    public static string ToDisplayText(this Enum Value)
    {
    try
    {
    Type type = Value.GetType();
    MemberInfo[] memInfo =
    type.GetMember(Value.ToString());
    if (memInfo != null && memInfo.Length > 0)
    {
    object[] attrs = memInfo[0]
    .GetCustomAttributes(typeof(DisplayText), false);
    if (attrs != null && attrs.Length > 0)
    return ((DisplayText)attrs[0]).DisplayedText;
    }
    }
    catch (Exception ex)
    {
    throw new Exception(
    "Error in XtensionMethods.ToDisplayText(Enum):\r\n" + ex.Message);
    }
    return Value.ToString();
    }
    [System.AttributeUsage(System.AttributeTargets.Field)]
    public class DisplayText : System.Attribute
    {
    public string DisplayedText;
    public DisplayText(string displayText)
    {
    DisplayedText = displayText;
    }
    }
    }

    Uso en línea como:

    myEnum.ToDisplayText();

    O envuelto en una función, si te apetece:

    public string GetDecision(Decision decision)
    {
    return decision.ToDisplayText();
    }

Dejar respuesta

Please enter your comment!
Please enter your name here