c# remarks - La declaración de devolución faltante en un método no válido compila




summary example (12)

En la teoría de tipos, hay algo llamado el tipo de fondo que es una subclase de cualquier otro tipo (!) Y se utiliza para indicar la no terminación entre otras cosas. (Las excepciones pueden contar como un tipo de no terminación: no finaliza a través de la ruta normal).

Entonces, desde una perspectiva teórica, se puede considerar que estas declaraciones que no terminan devuelven algo de tipo Bottom, que es un subtipo de int, de modo que uno obtiene (tipo de) su valor de retorno después de todo desde una perspectiva de tipo. Y está perfectamente bien que no tenga ningún sentido que un tipo pueda ser una subclase de todo lo demás, incluso int, porque en realidad nunca devuelve uno.

En cualquier caso, a través de la teoría explícita de tipos o no, los compiladores (escritores de compiladores) reconocen que pedir un valor de retorno después de una declaración no terminada es tonto: no hay ningún caso posible en el que pueda necesitar ese valor. (Puede ser agradable que su compilador lo advierta cuando sabe que algo no terminará pero parece que quiere que devuelva algo. Pero es mejor dejarlo para los inspectores de estilo a la pelusa, ya que tal vez necesite la firma de tipo que forma por alguna otra razón (por ejemplo, subclases) pero realmente quiere la no terminación.)

Encontré una situación en la que un método no nulo falta una declaración de devolución y el código aún se compila. Sé que las instrucciones después del ciclo while son inalcanzables (código muerto) y nunca se ejecutarán. ¿Pero por qué el compilador ni siquiera advierte sobre devolver algo? ¿O por qué un idioma nos permitiría tener un método no nulo que tenga un ciclo infinito y no devuelva nada?

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Si agrego una sentencia break (incluso una condicional) en el ciclo while, el compilador se queja de los errores infames: 'El método no devuelve un valor' (Eclipse) y 'No todas las rutas de códigos devuelven un valor' (Visual Studio)

public int doNotReturnAnything() {
    while(true) {
        if(mustReturn) break;
        //do something
    }
    //no return statement
}

Esto es cierto para Java y C #


Tu ciclo while se ejecutará para siempre y, por lo tanto, no saldrá al mismo tiempo; continuará ejecutándose. Por lo tanto, la parte externa de while {} es inalcanzable y no hay ningún punto en la escritura de retorno o no. El compilador es lo suficientemente inteligente como para saber qué parte es alcanzable y qué parte no.

Ejemplo:

public int xyz(){
    boolean x=true;

    while(x==true){
        // do something  
    }

    // no return statement
}

El código anterior no se compilará, porque puede haber un caso en el que el valor de la variable x se modifique dentro del cuerpo del ciclo while. ¡Esto hace que la parte exterior de while loop sea alcanzable! Y, por lo tanto, el compilador arrojará un error "no return statement found".

El compilador no es lo suficientemente inteligente (o más bien vago;)) para averiguar si el valor de x se modificará o no. Espero que esto lo borre todo.


Puedo estar equivocado, pero algunos depuradores permiten la modificación de variables. Aquí, mientras que x no es modificado por código y será optimizado por JIT uno podría modificar x a falso y el método debería devolver algo (si tal cosa está permitida por C # depurador).


No hay ninguna situación en la que la función pueda alcanzar su fin sin devolver un valor apropiado. Por lo tanto, el compilador no tiene nada de qué quejarse.


"¿Por qué el compilador ni siquiera advierte sobre devolver algo? ¿O por qué un lenguaje nos permite tener un método no nulo que tiene un ciclo infinito y no devuelve nada?".

Este código también es válido en todos los demás idiomas (¡probablemente excepto Haskell!). Porque la primera suposición es que estamos "intencionalmente" escribiendo algún código.

Y hay situaciones en las que este código puede ser totalmente válido, como si fuera a usarlo como un hilo; o si devolvía una Task<int> , podría hacer una comprobación de errores basada en el valor int devuelto, que no debería devolverse.


Visual Studio tiene el motor inteligente para detectar si ha escrito un tipo de devolución y luego debe tener una declaración de devolución en la función / método.

Al igual que en PHP, su tipo de devolución es verdadero si no ha devuelto nada. el compilador obtiene 1 si nada ha regresado.

A partir de este

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

El compilador sabe que, si bien la declaración en sí misma tiene una naturaleza poco clara, no la considere. y el compilador php se volverá verdadero automáticamente si escribe una condición en la expresión de while.

Pero no en el caso de VS le devolverá un error en la pila.


El compilador de Java es lo suficientemente inteligente como para encontrar el código inalcanzable (el código después del ciclo while)

y dado que es inalcanzable , no tiene sentido agregar una declaración de return allí (después de los extremos)

lo mismo ocurre con el condicional if

public int get() {
   if(someBoolean) {   
     return 10;
   }
   else {
     return 5;
   }
   // there is no need of say, return 11 here;
}

dado que la condición booleana someBoolean solo puede evaluar como true o false , no es necesario proporcionar una return explícitamente después de if-else , porque ese código es inalcanzable y Java no se queja de ello.


El compilador sabe que el ciclo while nunca dejará de ejecutarse, por lo tanto, el método nunca terminará, por lo tanto, una declaración de return no es necesaria.


Los detalles del caso de Java para esto (que probablemente sean muy similares al caso C #) tienen que ver con la forma en que el compilador Java determina si un método puede regresar.

Específicamente, las reglas son que un método con un tipo de devolución no debe poder completarse normalmente y en su lugar siempre debe completarse abruptamente (abruptamente aquí indicando a través de una declaración de devolución o una excepción) según JLS 8.4.7 .

Si se declara que un método tiene un tipo de devolución, se produce un error en tiempo de compilación si el cuerpo del método puede completarse normalmente. En otras palabras, un método con un tipo de devolución debe regresar solo mediante el uso de una declaración de devolución que proporciona un retorno de valor; no está permitido "dejar caer el extremo de su cuerpo" .

El compilador busca ver si la terminación normal es posible según las reglas definidas en JLS 14.21 Declaraciones inalcanzables, ya que también define las reglas para la finalización normal.

Notablemente, las reglas para declaraciones inalcanzables crean un caso especial solo para bucles que tienen una expresión de constante true definida:

Una instrucción while se puede completar normalmente si al menos uno de los siguientes es verdadero:

  • La instrucción while es alcanzable y la expresión de condición no es una expresión constante (§15.28) con valor verdadero.

  • Hay una declaración de interrupción alcanzable que sale de la instrucción while.

Entonces si la instrucción while puede completarse normalmente , entonces es necesaria una declaración de retorno debajo ya que el código se considera alcanzable, y cualquier ciclo while sin una declaración de interrupción alcanzable o expresión true constante se considera capaz de completarse normalmente.

Estas reglas significan que su declaración while con una expresión verdadera constante y sin break nunca se considera que se complete normalmente , por lo que nunca se considera que sea accesible ningún código que se encuentre debajo. El final del método está debajo del bucle, y como todo lo que está debajo del bucle es inalcanzable, también lo es el final del método, por lo que el método no puede completarse normalmente (que es lo que busca el que realiza el compilador).

if declaraciones, por otro lado, no tienen la excepción especial con respecto a las expresiones constantes que se otorgan a los bucles.

Comparar:

// I have a compiler error!
public boolean testReturn()
{
    final boolean condition = true;

    if (condition) return true;
}

Con:

// I compile just fine!
public boolean testReturn()
{
    final boolean condition = true;

    while (condition)
    {
        return true;
    }
}

El motivo de la distinción es bastante interesante, y se debe al deseo de permitir indicadores de compilación condicional que no causen errores de compilación (desde el JLS):

Uno podría esperar que la declaración if se maneje de la siguiente manera:

  • Una instrucción if-then puede completarse normalmente si al menos uno de los siguientes es verdadero:

    • La instrucción if-then es alcanzable y la expresión de condición no es una expresión constante cuyo valor es verdadero.

    • La declaración de entonces puede completarse normalmente.

    El enunciado then es alcanzable si la declaración if-then es alcanzable y la expresión de condición no es una expresión constante cuyo valor es falso.

  • Una instrucción if-then-else puede completarse normalmente si la instrucción then puede completarse normalmente o la declaración else puede completarse normalmente.

    • El enunciado then es alcanzable si la declaración if-then-else es alcanzable y la expresión de condición no es una expresión constante cuyo valor es falso.

    • La declaración else es alcanzable si la declaración if-then-else es alcanzable y la expresión de condición no es una expresión constante cuyo valor es verdadero.

Este enfoque sería coherente con el tratamiento de otras estructuras de control. Sin embargo, para permitir que la declaración if se use convenientemente para fines de "compilación condicional", las reglas reales difieren.

Como ejemplo, la siguiente instrucción da como resultado un error en tiempo de compilación:

while (false) { x=3; } while (false) { x=3; } porque la declaración x=3; no es accesible; pero el caso superficialmente similar:

if (false) { x=3; } if (false) { x=3; } no da como resultado un error en tiempo de compilación. Un compilador de optimización puede darse cuenta de que la declaración x=3; nunca se ejecutará y puede optar por omitir el código para esa declaración del archivo de clase generado, pero la instrucción x=3; no se considera "inalcanzable" en el sentido técnico especificado aquí.

La razón de este tratamiento diferente es permitirles a los programadores definir "variables de bandera" tales como:

static final boolean DEBUG = false; y luego escribe un código como:

if (DEBUG) { x=3; } if (DEBUG) { x=3; } La idea es que debería ser posible cambiar el valor de DEPURACIÓN de falso a verdadero o de verdadero a falso y luego compilar el código correctamente sin ningún otro cambio en el texto del programa.

¿Por qué la declaración de interrupción condicional da como resultado un error de compilación?

Como se cita en las reglas de alcanzabilidad del bucle, un bucle while también puede completarse normalmente si contiene una instrucción break accesible. Dado que las reglas para la accesibilidad de una cláusula then de una sentencia if no toman en absoluto en cuenta la condición de if , dicha cláusula condicional if sentencia if siempre se considera alcanzable.

Si la break es alcanzable, entonces el código después del bucle nuevamente también se considera alcanzable. Como no hay un código alcanzable que dé como resultado una terminación abrupta después del bucle, se considera que el método puede completarse normalmente, por lo que el compilador lo marca como un error.


¿Por qué un lenguaje nos permite tener un método no nulo que tiene un ciclo infinito y no devuelve nada?

La regla para los métodos no nulos es que cada ruta de código que devuelve debe devolver un valor , y esa regla se cumple en su programa: cero de cero rutas de código que regresan devuelven un valor. La regla no es "cada método no nulo debe tener una ruta de código que retorna".

Esto le permite escribir stub-methods como:

IEnumerator IEnumerable.GetEnumerator() 
{ 
    throw new NotImplementedException(); 
}

Ese es un método no vacío. Tiene que ser un método no nulo para satisfacer la interfaz. Pero parece una tontería hacer que esta implementación sea ilegal porque no devuelve nada.

Que su método tiene un punto final inalcanzable debido a un goto (recuerde, un while(true) es simplemente una forma más agradable de escribir goto ) en lugar de un throw (que es otra forma de goto ) no es relevante.

¿Por qué el compilador ni siquiera advierte sobre devolver algo?

Porque el compilador no tiene buena evidencia de que el código sea incorrecto. Alguien escribió while(true) y parece probable que la persona que hizo eso sabía lo que estaban haciendo.

¿Dónde puedo leer más sobre el análisis de accesibilidad en C #?

Vea mis artículos sobre el tema, aquí:

ATBG: alcanzabilidad de facto y de jure

Y también podría considerar leer la especificación C #.


La especificación de Java define un concepto llamado Unreachable statements . No tiene permitido tener una declaración inalcanzable en su código (es un error de tiempo de compilación). Ni siquiera está permitido tener una declaración de devolución después del tiempo (verdadero); declaración en Java. Un while(true); La declaración hace que las siguientes declaraciones sean inalcanzables por definición, por lo tanto, no necesita una declaración de return .

Tenga en cuenta que si bien el problema de Detener es indecidible en un caso genérico, la definición de Declaración inalcanzable es más estricta que simplemente detenerse. Está decidiendo casos muy específicos donde un programa definitivamente no se detiene. El compilador teóricamente no puede detectar todos los bucles infinitos y las sentencias inalcanzables, pero tiene que detectar casos específicos definidos en la especificación (por ejemplo, el caso while(true) )


Esto me recuerda a "Is a" versus "tiene a" trade-off. A veces es más fácil y tiene más sentido heredar directamente de una súper clase. Otras veces tiene más sentido crear una clase independiente e incluir la clase que habría heredado como una variable miembro. Aún puede acceder a la funcionalidad de la clase, pero no está vinculado a la interfaz ni a ninguna otra restricción que pueda derivarse de la herencia de la clase.

Que haces Como con muchas cosas ... depende del contexto. La guía que usaría es que para heredar de otra clase, realmente debería haber una relación "es una". Por lo tanto, si escribe una clase llamada BMW, podría heredarse de un automóvil porque un BMW realmente es un automóvil. Una clase de Caballo puede heredar de la clase de Mamíferos porque un caballo en realidad es un mamífero en la vida real y cualquier funcionalidad de Mamíferos debe ser relevante para Caballos. ¿Pero puedes decir que un equipo es una lista? Por lo que puedo decir, no parece realmente un Equipo "es una" Lista. Entonces, en este caso, tendría una Lista como variable miembro.







c# java oop design