miércoles, 23 de agosto de 2017

Cuestiónate todo: Throw vs Throw ex

Hace unos días leía una entrada sobre excepciones en Campus MVP: GAMBADAS: Los tres principales pecados al gestionar errores y excepciones. Allí nos daban consejos para trabajar con excepciones, y entre ellos nos recordaban como debemos relanzar una excepción, y cual es la diferencia entre relanzarla haciendo throw o haciendo throw ex. Me refiero a esto:

image

En el artículo se indicaba que cuando relanzamos una excepción haciendo throw ex estamos “reseteando” el call stack, por lo que un elemento superior que capture la excepción no va a poder saber donde se generó originalmente,y obtendrá como origen la línea en la que hacemos el throw ex.

image

Según lo leí la cabeza me jugo una mala pasada y me dije: “eso esta mal, hacer throw o throw ex es lo mismo”. Lo malo es que rápidamente me fui a Twitter a pregonarlo, donde me corrigieron a la primera de cambio: no no es lo mismo. Si nos vamos a la documentación oficial de C# (https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/throw) vemos esto:
image

Parece estar meridianamente claro… ¿pero es así en todos los casos?  ¿De donde había sacado yo las idea de que era lo mismo una cosa que la otra?

El ejemplo

Vamos a comprobarlo, para ello he creado un código de ejemplo, es sencillo. Tenemos un método  que lanza una excepción, ya que hace una división por cero
public static int FaultyCode(int number)
{
    int divisor = 0;
    return number / divisor;
}
Luego tenemos un segundo método (CatchAndThrowEx) llama al anterior, recubriendo la llamada en un bloque try/catch. Una vez capturada la excepción la relanza con throw ex.
public static void CatchAndThrowEx()
{
    try
    {
        var result = FaultyCode(5);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
También un tercer método(CatchAndThrow) similar al anterior y que también llama al código que falla. La llamada es recubierta en un bloque try/catch. Una vez capturada la excepción la relanza con throw.
public static void CatchAndThrow()
{
    try
    {
        var result = FaultyCode(5);
    }
    catch(Exception ex)
    {
        throw;
    }
}
Finalmente, desde el main de una aplicación de consola, invocamos a los dos métodos que relanzan la excepción, y la capturamos, trazando su call stack, para que veamos si este ha sido “reseteado” o no.

public static void Main(string[] args)
{
    try
    {
        Console.WriteLine("==============================================");
        Console.WriteLine("CatchAndThrow:");
        CatchAndThrow();
    }
    catch (Exception ex)
    {
        ex.ToConsole();
    }

    try
    {

        Console.WriteLine("==============================================");
        Console.WriteLine("CatchAndThrowEx:");
        CatchAndThrowEx();
    }
    catch (Exception ex)
    {
        ex.ToConsole();
    }
}
Si ejecutamos lo anterior, obtenemos lo esperado: cuando se relanza con throw ex se pierde el call stack y no somos capaces de ver el método donde se originó la excepción… malo
Core 2

Seguimos cuestionando

Antes de abandonar me dije, lo he ejecutado compilando en  debug… ¿habrá cambio en release?
Si ejecuto en release (dotnet run -c Release) las cosas cambian!  Como podéis ver a continuación throw y throw ex se comportan exactamente igual.
image

¡Esto no estaba en el guión!
Visto lo visto me dije, será cosa de .Net Core 2? Vamos a comprobar en otras configuraciones. El resultado está en la siguiente tabla, y siempre es el mismo: cuando se ha compilado en release throw y throw ex se comportan igual:

PlataformaCompilaciónResultado
.Net Core 1.1 Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Core 1.1 Release Throw y Throw ex tienen el mismo comportamiento
.Net Core 2 Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Core 2 Release Throw y Throw ex tienen el mismo comportamiento
.Net Framework 4.6.2  Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Framework 4.6.2  Release Throw y Throw ex tienen el mismo comportamiento 



¿Donde está el truco?

Suele haber un motivo, siempre lo hay, que explique comportamientos extraños, comportamiento que contradicen la documentación oficial como es en este caso.

Yo ni lo había imaginado, pero Juanma (https://twitter.com/gulnor) rápidamente intuyó que pasaba: El método FaultyCode era demasiado sencillo, tanto que al compilar en Release se convierte en un inline, por lo que pasa a ser código del método que le invoca.

Si complicamos FaultyCode, el compilador no lo convertirá en un inline, y finalmente  veremos que el comportamiento vuelve a ser el esperado, y el documentado:

  • Throw mantiene el call stack
  • Throw ex lo resetea


Saludos, espero que os haya resultado interesante

sábado, 19 de agosto de 2017

Entrada vintage parte 2: .Net Core 2.0 y Visual Studio 2015

Continuando con mi entrada anterior (http://www.serginet.com/2017/08/core-vs), y una vez que .NET Core 2.0 ha sido liberado, vamos  a ver como podemos crear, compilar y ejecutar aplicaciones basadas en .NET Core 2 con proyectos tipo project.json (los de Visual Studio 2015).

Lo primero ver la situación inicial, tal y como hacíamos en la entrada anterior:

  • La versión del runtime de .NET Core, ejecutando el comando dotnet.
  • La versión del CLI, con dotnet --version. 
  • La versión del SDK (que debería ser la misma que la del CLI), con dotnet --info.

image

         
          Como veis hay un runtime 1.0.1 con un SDK (y por tanto CLI) 1.0.0-preview2-003131

  • ¿Hay otros SDK's instalados?, Para ello miramos directamente en la carpeta ProgramFiles\dotnet\SDK y vemos que en esta máquina no hay más SDK's

image

  • ¿Qué dice Windows que tenemos instalado?  Miramos a Agregar/Quitar programas, y reconfirmamos que no hay nada más instalado.

agregarQuitarAntes

Como veis parto de una situación standard de Visual Studio 2015: SDK y Tools en preview, además de un runtime de .NET 1.0.x .

Empezamos descargando el SDK desde la página de descargas oficial ( https://www.microsoft.com/net/download/core ) e instalándolo.

image

Una vez hecho, vamos a ver de nuevo como ha quedado el sistema, consultando:

  • La versión del runtime de .NET Core, ejecutando el comando dotnet.
  • La versión del CLI, con dotnet --version. 
  • La versión del SDK (que debería ser la misma que la del CLI), con dotnet --info. 


image
       Aquí vemos que ha habido cambios en el comando dotnet: ya no vemos el runtime ejecutando dotnet. Si vamos a la ayuda, vemos lo siguiente:
  • dotnet --version nos da la versión del SDK
  • dotnet --info nos da información general de .net Core, incluyendo la versión de CLI (que debe coincidir con el SDK) y el runtime

image

Ahora vamos a compilar, nos ubicamos en un proyecto tipo Visual Studio 2015 (project.json) y ejecutamos dotnet.build.
Es importante fijarnos que no tengamos un fichero project.json que nos cambie el SDK a usar, de momento queremos usar el último SDK que acabamos de instalar

La primera en la frente, tenemos un error.

image

Estamos en la misma situación de la entrada anterior (http://www.serginet.com/2017/08/core-vs): se ha eliminado el soporte a proyectos tipo project.json, por lo que si queremos compilar necesitamos usar un SDK “viejo”. Ahora es el momento de hacer uso de nuestro fichero global.json.
Ubicamos el fichero una carpeta por encima de la carpeta donde está en project.json, con el siguiente contenido:

image

y compilamos y ejecutamos sin problema.

image

Ahora estamos ejecutando una aplicación .Net Core 1.0 con un runtime 2.0, vemos el runtime ejecutando dotnet --info

image

Me sigue sorprendiendo ver una “mezcla” curiosa: el último runtime 2.0 side by side con un SDK antiguo, versión preview anterior a 1.0

Actualizando a .Net Core 2.0


Vamos a actualizar a 2.0, modificando directamente el fichero project.json para dejarlo así:

image

Para compilarlo, nos basta con dotnet restore seguido de dotnet build, y sin eliminar nuestro fichero global.json, ya que seguimos necesitando el SDK viejo que nos da compatibilidad con project.json


image


Y vemos que… ¡compila bien, sin problemas!  Hemos compilado una aplicación con target .Net Core 2.0 con un SDK compatible con Visual Studio 2015  ya que sigue usando project.json

Reto conseguido! A disfrutarlo!

martes, 15 de agosto de 2017

Entrada Vintage: problemas de versiones con .NET core y Visual Studio 2015

Esta entrada describe un problema que sufro de vez en cuando, y que de una vez por todas he decidido investigar, para documentar la solución y no volver a perder el tiempo. Quizás llegue un poco tarde… mas aun cuando acaban de anunciar .NET Core 2
Empecemos por el principio:

Dispongo de 2 equipos con Visual Studio 2015, y un tercero con Visual Studio 2017. Si, se que debería pasar todos a 2017, pero me resisto: Visual Studio 2015 funciona bien, y mi querido project.json en core me gusta, bastante.

El caso es que acostumbro a intercambiar los equipos con 2015 a la hora de programar, manteniendo el código en una carpeta sincronizada en OneDrive, por lo que siempre está al día en ambos aunque no haya hecho commit/push a mi repositorio remoto (llamadme temerario).

Más de una vez, en uno de los cambios de equipo, me he encontrado con un error a la hora de abrir una solución que contenga proyectos .NET Core, y además no pudiendo compilar a partir de ese momento ningún proyecto .NET Core.
El error al cargar la solución es el siguiente:

Error arranque

Al compilar este:

ErrorCompilar


Parece que hay algún problema en el SDK de .NET Core o bien en el tooling para Visual Studio 2015.  Siempre en el pasado lo he arreglado desinstalando SDK's y tooling e reinstalando el tooling para Visual Studio de nuevo (que también instala el SDK). Esta vez sin embargo quiero ver que genera el error para resolverlo y no volver a sufrirlo (espero).

Vamos a empezar viendo que tenemos instalado:


  • La versión del runtime de .NET Core, ejecutando el comando dotnet. Tengo el runtime 1.1.0
dotnetAntes

  • La versión del CLI, con dotnet --version. Tengo la versión 1.0.4 del CLI
dotnetVersionAntes

  • La versión del SDK (que debería ser la misma que la del CLI), con dotnet --info. Efectivamente es el 1.0.4, como el CLI
infoAntes

  • ¿Hay otros SDK's instalados?, Para ello miramos directamente en la carpeta ProgramFiles\dotnet\SDK
Versiones
Vemos que dispongo de dos SDK’s,un preview y la versión 1.0.4. Aprovecho a recordar que .NET Core siempre usa la versión más alta que tenga disponible.

  • ¿Qué dice Windows que tenemos instalado?  Miramos a Agregar/Quitar programas
ProgramasAntes

Como se ve tengo dos SDK, uno preview y el 1.04, esto coincide con lo que vi directamente en disco. Además tengo las tools para Visual Studio, que son las que parece no funcionan.

¿Como lo comprobamos? Compilando desde línea de comandos con dotnet build


error dotnetbuild

Vemos que no funciona, y para esto no interviene Visual Studio, ya que estoy compilando con el CLI.
Primera pista: el problema está en el SDK



Diferencia entre SDK y tools:


Aquí se puede ver: https://docs.microsoft.com/en-us/dotnet/core/sdk 

El SDK nos da:

  • Las herramientas de línea de comandos (CLI)
  • El runtime y librerías necesarias para compilar

En cuanto al tooling para Visual Studio, nos permite trabajar con .Net Core en Visual Studio, añadiendo plantillas de proyecto y permitiendo compilar, depurar, etc...


Primer intento: reinstalamos el SDK


Vamos a irnos a la página de descargas de core (https://www.microsoft.com/net/download/core)  y ver que tenemos disponible

Allí podemos descargar el último SDK (actualmente descarga el SDK 1.0.4) y el tooling para Visual Studio, que para 2015 es un preview (nunca dejó de serlo y ya no lo hará).

Voy a descargar el SDK y reinstalarlo, ya dijimos que todo apunta a que es el problema. Realmente al ejecutar el instalador ve que ya lo tengo y me pide reparar:

repararSDK


Después de hacerlo... nada, dotnet build sigue fallando, y la compilación desde Visual Studio también.


Segundo intento: reinstalamos el tooling para visual studio

De nuevo se encuentra en la página de descargas de .NET Core (https://www.microsoft.com/net/download/core)

tooling
Después de hacerlo (de nuevo detectó que ya estaba instalado)... sigue fallando la compilación
repairtools

Tercer intento, desinstalamos todo y empezamos de cero


Después de desinstalar todo solo voy a instalar las tools de Visual Studio 2015

image


Ahora puedo compilar, tanto desde Visual Studio como con dotnet build, Por fin… el paciente está estable


Vamos a ver como tenemos todo ahora (que versiones):

  • Versión del runtime de .NET Core, ejecutando el comando dotnet. Tengo el runtime 1.0.1
  • Versión del CLI, con dotnet --version. Tengo la versión 1.0.0-preview2-003131 del CLI
  • Versión del SDK (que debería ser la misma que la del CLI), con dotnet --info. Efectivamente es el 1.0.0-preview2-003131, como el CLI
  • ¿Hay otros SDK's instalados?, Para ello miramos directamente en la carpeta ProgramFiles\dotnet\SDK
  • image
          Parece que no, solo hay un SDK, el instalado por las tools como era de esperar.
  • ¿Qué dice Windows que tenemos instalado?  Miramos a Agregar/Quitar programas. Tenemos un Sdk preview y el tooling preview

image



Como decía el paciente esta estable, pero claro, solo dispongo de .NET Core 1.0.x , no tengo opción de 1.1 (de 2.0 ni hablamos , eso será otro post). Y lo malo, mi aplicación, que ha compilado bien, no debería poder ejecutar ya que su target es .NET Core 1.1.


image

Como esperaba, obtengo un bonito error al tratar de ejecutar. Me indica que el runtime .NET Core 1.1 no esta disponible 

image

Es curioso, aunque no puedo ejecutar, puedo compilar .NET Core 1.1, sin que haya runtime ni SDK apropiado en mi equipo


Ahora vamos a instalar un SDK reciente,que nos permita trabajar con .NET Core 1.1. La página de descargas me ofrece el SDK 1.0.4, que incluye soporte para .NET Core 1 y 1.1. Lo voy a instalar
download
image


Una vez finalizada la instalación, problemas: A partir de ese momento... si intento compilar de nuevo obtengo errores! Se ha vuelto a romper.
image
Vamos a ver de nuevo que situación tenemos:

  • Versión del runtime de .NET Core, ejecutando el comando dotnet. Tengo el runtime 1.1.0
  • Versión del CLI, con dotnet --version. Tengo la versión 1.0.4 del CLI (como en el punto de partida)
  • Versión del SDK (que debería ser la misma que la del CLI), con dotnet --info. Efectivamente es el 1.0.4, como el CLI
  • ¿Hay otros SDK's instalados?, Para ello miramos directamente en la carpeta ProgramFiles\dotnet\SDK
image
      Vemos que si, ha aparecido el SDK 1.0.4

  • ¿Qué dice Windows que tenemos instalado?  Miramos a Agregar/Quitar programas
image

Tenemos lo esperado: Tooling de Visual Studio 2015 y dos sdk’s, el instalado por el tooling y el mas reciente (1.0.4)


¿Qué ha pasado?


Pues parece que el SDK 1.0.4 no trae soporte para Visual Studio 2015, o más bien para los ficheros de proyecto tipo json.
Con el lanzamiento de Visual Studio 2017, Microsoft por fin liberó un SDK en RTM (versión >= 1), que como ya soportaba el nuevo (¿nuevo?) fichero de proyecto csproj “mataba” el soporte al fichero de proyecto tipo json.
Fijaos, fue aquí:
image

¿Como lo arreglamos?

Ya hemos mencionado que siempre se usa por defecto el SDK superior disponible, pero tenemos que hacer que el SDK a usar sea el que queramos, es decir uno con soporte a project.json.
¿Cómo se hace? Debemos crear un fichero llamado global.json, ubicado en una carpeta anterior a la que contiene el fichero project.json (y por tanto nuestro proyecto) y allí indicar el SDK a usar. En mi caso será algo así:
{
   "projects": [ "1.Queues", "2.Topics" ],
   "sdk": {
     "version": "1.0.0-preview2-003131"
   }
}
Como veis indicamos el SDK y las carpetas dentro de las cuales queremos que aplique. En mi caso las dos carpetas donde tengo proyectos. Indicar las carpetas no es obligatorio, podríamos solo indicar el sdk, que aplicaría a todo el árbol de subdirectorios.
Una vez que tenemos el fichero todo vuelve a la normalidad, y podemos compilar sin problema, además de ejecutar aplicaciones con .NET Core 1.1

image



Espero que os sea de utilidad!


Recursos: