Jorge Serrano
  • Home

C# 8.0 – Specification – Nullable reference types

  • By jorge
  • Sep-12-2019
  • C# 8.0
  • 0 Comments.

Índice general – C# 8.0 – Specification

Introducción

Microsoft ha extendido o añadido en C# 8.0 el control de avisos o warnings en el código con nullables.

¿Pero porqué y para qué?. ¿Era realmente necesario?.

Comparándolo con lenguajes como F# que carecen de referencias null de forma directa (F# – Null Values), esta funcionalidad de Microsoft podría parecer una moda, un capricho o un simple deseo.
Pero reconozcámoslo, el origen de utilizar null tiene nombre y apellidos, Tony Hoare.

A este afable caballero -Sir Charles Antony Richard Hoare para más señas- le debemos mucho (bueno y malo) y posee un currículum envidiable y premios recibidos entre los que encontramos el importantísimo premio Turing.
Tony Hoare admitió en una conferencia celebrada en el año 2009 haber cometido el error del billón de dólares «billion-dolar mistake«.

Ese error no es otro que haber inventado en el año 1965 las referencias null como parte del lenguaje de programación ALGOL W.

Lo que Tony Hoare comentó en aquella conferencia es lo siguiente:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Dicho de otro modo, nadie duda ni debe dudar de lo que Tony Hoare ha dado a la informática, pero como vemos (y este es un claro y cristalino ejemplo), hasta las mentes más brillantes pueden errar.
Otra cosa es que la gente se atreva a contradecir a mentes como la de Tony, pero lo cierto es que lo que a veces sobre el papel parece resultar una buena idea puede no serlo tanto visto con perspectiva.

Llegados a este punto, Microsoft implementó en .NET y en su momento la referencia null, quiero pensar que no porque Tony lo hubiera potenciado y fuera extendido su uso (muchos lenguajes de programación lo usan), sino porque como le pasaba a Tony, su uso e implementación era la más sencilla y rápida.

Lamentablemente, su uso es uno de los errores más frecuentes en programación desencadenando daños colaterales, errores, pérdidas de dinero, de tiempo y de oportunidades (estas dos últimas no las dijo Tony pero lo digo yo porque casi siempre nos fijamos en el dinero cómo principal consecuencia para analizar un determinado problema).

Para más información acerca de null reference, te invito también a acceder a este enlace sobre Null pointer (o null reference).

Así que aquí nos podríamos preguntar por ejemplo:

¿Y qué sucede en Java cuando accedemos a una refencia nula?.
Que recibimos un NullPointerException.

¿Y en .NET qué sucede?
Algo parecido a lo que pasa en Java, pero en este caso recibiremos un NullReferenceException.

Ahora bien, ¿son los null el demonio?.

No exactamente, porque como todo en la vida, dependiendo de cómo nosotros mismos programemos nuestras aplicaciones aseguraremos la robusted necesaria a nuestro sistema o no, pero es indudable que si se nos facilita de alguna manera la vida, podremos asegurar más esa robusted.

Por lo tanto, y en el caso de .NET concretamente, si Microsoft nos proporcionara la posibilidad de que seamos nosotros los que elijamos qué hacer o cómo programar el tratamiento de null, sobre todo para los más despistados o los programadores menos experimentados, podremos evitar muy posiblemente problemas no deseados en tiempo de ejecución.

Así que después de hacer este largo preámbulo, vamos a centrarnos en sí en la nueva funcionalidad que nos ocupa para que entendamos los cambios introducidos y cómo trabajar con ellos.

 

Recordando el tratamiento de null

Creo que a nadie se le escapa a estas alturas que los tipos por referencia son punteros que direccionan al dato en sí (otra cosa son los tipos por valor que no voy a tratar en esta entrada).

Otra característica a destacar es que todos los tipos por referencia son nullables, es decir, un puntero puede tener un valor null.

El clásico tipo por referencia es el tipo string.

Así que el siguiente código, no generará ningún tipo de error en tiempo de compilación:

private static void NullableReferenceTypes()
{
    string data = null;
    var result = ConvertToUpper(data);
    Console.WriteLine(result);
}

private static string ConvertToUpper(string data)
{
    return data.ToUpper();
}

La aplicación que contiene esta declaración de código se ejecutará correctamente hasta que ejecute el método NullableReferenceTypes del ejemplo.
Es entonces cuando se producirá un error de tipo:

System.NullReferenceException: 'Object reference not set to an instance of an object.'
data was null

Claro está en que podríamos mitigar el problema utilizando un try…catch, pero también es lógico pensar que eso conlleva un coste y que lo «lógico y razonable» es que a la función no entre un null.
Así que también podríamos preguntar antes si data es null, pero ¿y si se nos olvida?.

Así que como vemos, hay opciones de mitigar el problema, pero nuestras aplicaciones no están exentas de sufrir el problema planteado.

¿Y cuál es el objetivo principal entonces?.
Que en tiempo de desarrollo recibamos información sobre estos posibles olvidos para que evitemos problemas en tiempo de ejecución.

 

Nullable reference types

En C# 8.0 deberemos indicar explícitamente que queremos que un tipo por refencia utilice null.
Esto se consigue de la misma manera a como lo hacemos con los tipor por valor, añadiendo en el tipo de referencia el carácter: ?

Para lograr este propósito tenemos dos formas:

  • Indicarlo así en el proyecto (fichero csproj)
  • Utilizar directivas

Utilizarlo en el proyecto implica su uso para todo el código del proyecto.
Utilizarlo en directivas implica su uso en porciones de código únicamente.

 

 

 

CS8600 Converting null literal or possible null value to non-nullable type

Veamos un ejemplo de su uso para el caso del código CS8600.

Dentro de mi proyecto (.csproj), he definido a éste de la forma:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Nullable habilitará o no el uso de warnings en el caso de uso de null.
Los posibles valores de Nullable son ‘disable’, ‘enable’, ‘warnings’ o ‘annotations’.

Lo más habitual será utilizar disable o enable.
En nuestro caso enable para que nos avise de esos «olvidos o despistes».

Si compilamos el código que vimos anteriormente, recibiremos para string data = null; un warning:

Warning    CS8600 Converting null literal or possible null value to non-nullable type.

Sin embargo, si nuestro código contiene más asignaciones a null, podemos recibir más avisos, y quizás, sólo queremos recibir mensajes para una porción de código concreta, por lo que ahí, podemos utilizar directivas.

El fichero de proyecto no debería tener ninguna definición a Nullable, así que la podemos eliminar para demostrar cómo funcionan las directivas.

Nuestro código utilizando directivas quedará de la siguiente forma:

private static void NullableReferenceTypes()
{
    #nullable enable
    string data = null;
    #nullable restore
    var result = ConvertToUpper(data);
    Console.WriteLine(result);
}

private static string ConvertToUpper(string data)
{
    return data.ToUpper();
}

En este caso, sólo aparecerá un warning para string data = null;.

Llegados a este punto, que duda cabe que cuando aparece un sólo warning en compilación, deberíamos revisar nuestra aplicación para que se despliegue sin ningún warning.
¿A que todos lo hacéis?.
Deberíamos hacerlo SIEMPRE sin duda, pero las prisas… y esas cosas… en fin… luego pasa lo que pasa en producción y nos volvemos locos intentando saber porqué.

Así que imaginemos que queremos que esos warnings sean errores.
Es decir, en mi caso quiero utilizar Nullable pero que los warnings de tipo CS8600 me los devuelva como errores.

Bastará en este caso modificar nuestro fichero de proyecto (csproj) para incluir WarningsAsErrors de la forma:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <Nullable>enable</Nullable>
    <WarningsAsErrors>CS8600</WarningsAsErrors>
  </PropertyGroup>
</Project>

Si compilamos ahora nuestra aplicación, comprobaremos que ésta no se compila y devuelve un error del tipo:

Error CS8600 Converting null literal or possible null value to non-nullable type.

Si queremos incluir como errores más de un tipo CSxxxx, deberemos añadirlos concatenados y separados por ;

También podemos combinar entre directivas y WarningsAsErrors.

 

CS8602 Possible dereference of a null reference

Volviendo a los posibles avisos sobre tipos por referencia, veamos el CS8602 que indica posible de-referencia a referencia de nulos.

El tratamiento es muy parecido a lo que veíamos anteriormente, así que aunque los trate separadamente para demostrar cómo son y cómo diferenciarlos, el tratamiento se repite (recuerda que son tipos por referencia nullable).

Nuestro fichero de proyecto en este caso lo he dejado de esta forma:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

El código que devuelve un warning de tipo CS8602 queda de la siguiente forma:

private static void PossibleDereferenceNullableReferenceTypes()
{
    string data = null;
    Console.WriteLine(data.Length);
}

Al compilar nuestra aplicación, recibiremos un warning de tipo:

Warning    CS8602 Possible dereference of a null reference

En este caso, una forma de resolver el problema sería:

private static void PossibleDereferenceNullableReferenceTypes()
{
    string data = null;
    Console.WriteLine(data?.Length);
}

Nota: Aquí en este ejemplo, string data = null; genera un warning de tipo CS8600.

 

8603 Possible null reference return.

Continuando con los diferentes problemas con los que podemos encontrarnos, también podemos recibir avisos sobre otras partes del código relacionadas con lo tipos por referencia nulos, como por ejemplo con el código CS8603.

El tratamiento vuelve a ser el mismo.

Nuestro fichero de proyecto en este caso lo he dejado de esta forma:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

El código que devuelve un warning de tipo CS8603 queda de la siguiente forma:

private static string PossibleNullableReferenceTypes()
{
    var collection = new List();
    return collection.Count > 0 ? collection[0] : null;
}

Al compilar nuestra aplicación, recibiremos un warning de tipo:

Warning    CS8603 Possible null reference return.

Exactamente de la misma forma, podemos hacer que este tipo de warnings se conviertan en errores.

En este caso, una forma de resolver el problema sería:

private static string PosibleNullableReferenceTypes()
{
    var collection = new List();
    return collection.Count > 0 ? collection[0] : String.Empty;
}

Ahora bien, si queremos que realmente el valor de la función pueda ser null entonces podemos indicar que el tipo sea nullable, por lo que nuestro código «compatible» quedaría de la siguiente forma:

private static string? PosibleNullableReferenceTypes()
{
    var collection = new List();
    return collection.Count > 0 ? collection[0] : null;
}

 

CS8604 Possible null reference argument for parameter xxx

Y lo mismo sucede en el caso del warning con código CS8604.

Nuestro fichero de proyecto quedará de la siguiente forma:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Y un ejemplo de código que desencadene este tipo de código sería el siguiente:

private static void PosibleArgumentNullableReferenceTypes()
{
    string data = null;            
    var result = ConvertToUpper(data);
}

En este caso concreto, el mensaje que aparecerá en el compilador será similar al siguiente:

Warning CS8604 Possible null reference argument for parameter 'data' in 'string Program.ConvertToUpper(string data)'.

También nos podemos encontrar con el código CS8601.
No lo voy a mostrar aquí, pero el tratamiento será exactamente igual.

Podemos encontrar más información sobre estos códigos de error en la información de Microsoft sobre ErrorCode.cs.

Y sobre todos los códigos de error introducidos en C# 8.0 en este otro enlace sobre ErrorCode.cs.

 

null-forgiving operator

Ahora bien, también tenemos la posibilidad de «decirle» al compilador que no tenga en cuenta advertencias sobre un posible nulo sobre todo en el tratamiento del código CS8602.
Es lo que se conoce como null-forgiving operator.

El operador en concreto es: !

Lo mejor para entenderlo es verlo con un ejemplo:

Imaginemos la siguiente clase de partida:

private class Employee
{
    public string? Name { get; set; }
}

Ahora imaginemos el siguiente código de aplicación:

private static void NullForgivingOperator()
{
    Employee? person = GetEmployee();
    Console.WriteLine(person.Name);
}

private static Employee? GetEmployee()
{
    return new Employee() { Name = "John" };
}

Si compilamos el código recibiremos un warning de código CS8602.

Lo que viene a indicarnos el compilador es que «es posible» que lo que tengamos en person.Name sea un null.

Imaginemos ahora que estamos completamente seguros de que NUNCA vendrá un null ahí.
No queremos que el compilador tenga en cuenta o considere la posibilidad de que venta un null, así que vamos a «forzar» que el compilador ignore su tratamiento de análisis.

Para ello, utilizaremos el operador !

Nuestro código en este ejemplo, quedará de la siguiente forma:

private static void NullForgivingOperator()
{
    Employee? person = GetEmployee();
    Console.WriteLine(person!.Name);
}

 

Otras consideraciones

Una advertencia IMPORTANTE sobre todo al principio de usar esta forma de trabajar.
El uso de esta característica NO IMPLICA que nos relajemos o dejemos de atender posibles usos como verificación de null o uso de try…catch por poner dos ejemplos, sin embargo, no deberíamos tener que preguntar por null si hacemos bien las cosas.

Las migraciones deben ser realizadas de forma gradual.
En otra entrada intentaré indicar lo que a mi juicio sería un correcto flujo de trabajo a la hora de utilizar esta características en aplicaciones de C# que queremos migrar a C# 8.0.

Happy Coding!

Comments

Deja un comentario Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

← Previous Post Next Post →

Jorge Serrano

MVP Reconnect


¡Subscríbete a mi canal!
YouTube

Donaciones
Donation

Entradas recientes

  • Go – Arrays
  • Go – Operators
  • Go – Constants
  • Go – Tipos de Datos
  • Go – Variables
  • Hello Go-rld!
  • Introducción a Go o Golang
  • JSON Patch en ASP.NET Core 5 Web API
  • Null Checking en C#
  • ¿Porqué mi página web por defecto de ASP.NET Core no se vé en mi Azure Web App y me da un 404?

Categorías

  • .NET 5
  • .NET Core
  • .NET Core 3.0
  • .NET Core 3.1
  • .NET Framework 2.0
  • .NET Framework 3.0
  • .NET Framework 3.5
  • .NET Framework 4.0
  • .NET Framework 4.5
  • .NET Framework 4.6
  • .NET Framework 4.7
  • .NET Framework 4.8
  • .NET Standard 2.0
  • .NET Standard 2.1
  • AMQP
  • Android
  • Angular
  • API REST
  • Apple
  • Apple iOS
  • Apple macOs
  • Arquitectura
  • ASP.NET
  • ASP.NET Core
  • ASP.NET Core 3
  • ASP.NET Core 5
  • AWS
  • Azure App Service
  • Azure Application Insights
  • Azure Cosmos DB
  • Azure Database Migration Service
  • Azure Databricks
  • Azure DevOps
  • Azure Event Grid
  • Azure Functions
  • Azure IoT
  • Azure Portal
  • Azure PowerShell
  • Azure Queue Storage
  • Azure SQL
  • Azure Storage
  • Azure Virtual Datacenter
  • Azure WebApps
  • Big Data
  • Bing
  • Blazor
  • Blog
  • Bots
  • C#
  • C# 7.0
  • C# 7.1
  • C# 7.2
  • C# 7.3
  • C# 8.0
  • C# 9.0
  • Channel 9
  • Codeplex
  • Codespaces
  • Containers
  • Debugging
  • DevOps
  • Docker
  • Electron
  • Entity Framework
  • Entity Framework Core
  • Entity Framework Core 3.0
  • Entity Framework Core 5
  • Eventos
  • F#
  • FaaS
  • FeatureFlags
  • FeatureToggles
  • Feeds
  • Fluent Assertions
  • General
  • GIMP
  • Git
  • GitHub
  • Go
  • Google
  • Google Analytics
  • Gradle
  • gRPC
  • GSA
  • Historia de la Informática
  • HoloLens
  • HtmlAgilityPack
  • IdentityServer4
  • Inkscape
  • Ionic
  • iOS
  • IoT
  • Java
  • JavaScript
  • JDBC
  • JSON
  • Kubernetes
  • Lenguajes de Programación
  • Libros y Cursos
  • LINQ
  • Linux
  • LiteDB
  • Machine Learning
  • macOS
  • Microservices
  • Microsoft
  • Microsoft .NET Framework 4.5
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Build
  • Microsoft Ignite
  • Microsoft Learn
  • Microsoft Orleans
  • Microsoft Surface Go
  • Microsoft Teams
  • ML.NET
  • MQTT
  • MRO
  • MS-DOS
  • MsCoders Madrid
  • MVP
  • NancyFx
  • Node.js
  • NoSQL
  • NuGet
  • NUnit
  • OData
  • ODP.NET Core
  • Office 2007
  • Office 2010
  • Office 2013
  • Office 2016
  • Office 2019
  • Office 365
  • Open Source
  • Open XML SDK
  • Opinión
  • Orchard CMS
  • OT
  • PaaS
  • Patterns
  • PdfSharpCore
  • Performance
  • PHP
  • Postman
  • Power BI
  • PowerShell
  • PowerShell Core
  • Productividad
  • Project Server 2019
  • R
  • Rendimiento
  • Scala
  • Scraper
  • Security
  • Serverless
  • Service Fabric
  • SharePoint Server 2019
  • SignalR
  • Sin categoría
  • Sistemas Distribuidos
  • Skype
  • Skype for Business Server 2019
  • Small Basic Online
  • SQL Server 2005
  • SQL Server 2008
  • SQL Server 2012
  • SQL Server 2014
  • SQL Server 2016
  • SQL Server 2017
  • SQL Server 2019
  • STOMP
  • Swagger
  • Testing
  • TFS 2017
  • TFS 2018
  • Tools
  • TypeScript
  • Unity
  • UWP
  • UX
  • Visio
  • Visual Basic
  • Visual Studio 2010
  • Visual Studio 2012
  • Visual Studio 2013
  • Visual Studio 2015
  • Visual Studio 2017
  • Visual Studio 2017 for Mac
  • Visual Studio 2019
  • Visual Studio 2019 for Mac
  • Visual Studio App Center
  • Visual Studio Code
  • Visual Studio IntelliCode
  • Visual Studio Live Share
  • Visual Studio Live Share Audio
  • Visual Studio Online
  • VS Anywhere
  • Vue.js
  • Web API
  • WebAssembly
  • WinDbg
  • Windows
  • Windows 10
  • Windows Compatibility Pack
  • Windows Phone 10
  • Windows Phone 7
  • Windows Phone 8
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2016
  • Windows Server 2019
  • Windows Service
  • WinForms
  • WinUI
  • WPF
  • Xamarin
  • Xbox
  • Xcode
  • Xiaomi Mi Band 2
  • xUnit
  • YAML

Archivos

  • enero 2021
  • diciembre 2020
  • noviembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • julio 2020
  • junio 2020
  • mayo 2020
  • abril 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • diciembre 2019
  • noviembre 2019
  • octubre 2019
  • septiembre 2019
  • agosto 2019
  • julio 2019
  • junio 2019
  • mayo 2019
  • abril 2019
  • marzo 2019
  • febrero 2019
  • enero 2019
  • diciembre 2018
  • noviembre 2018
  • octubre 2018
  • septiembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • abril 2018
  • marzo 2018
  • febrero 2018
  • enero 2018
  • diciembre 2017
  • noviembre 2017
  • octubre 2017
  • septiembre 2017
  • agosto 2017
  • julio 2017
  • junio 2017
  • febrero 2015
  • octubre 2014
  • junio 2014
  • marzo 2014
  • febrero 2014
  • enero 2014
  • diciembre 2013
  • septiembre 2013
  • agosto 2013
  • julio 2013
  • junio 2013
  • abril 2013
  • febrero 2013
  • enero 2013
  • diciembre 2012
  • noviembre 2012
  • septiembre 2012
  • agosto 2012
  • junio 2012
  • mayo 2012
  • abril 2012
  • marzo 2012
  • febrero 2012
  • enero 2012
  • diciembre 2011
  • noviembre 2011
  • octubre 2011
  • septiembre 2011
  • agosto 2011
  • julio 2011
  • junio 2011
  • mayo 2011
  • abril 2011
  • marzo 2011
  • enero 2011
  • diciembre 2010
  • noviembre 2010
  • octubre 2010
  • septiembre 2010
  • agosto 2010
  • julio 2010
  • junio 2010
  • mayo 2010
  • abril 2010
  • marzo 2010
  • febrero 2010
  • enero 2010
  • diciembre 2009
  • noviembre 2009
  • octubre 2009
  • septiembre 2009
  • agosto 2009
  • julio 2009
  • junio 2009
  • mayo 2009
  • abril 2009
  • marzo 2009
  • febrero 2009
  • enero 2009
  • diciembre 2008
  • noviembre 2008
  • octubre 2008
  • septiembre 2008
  • agosto 2008
  • julio 2008
  • junio 2008
  • mayo 2008
  • abril 2008
  • marzo 2008
  • febrero 2008
  • enero 2008
  • diciembre 2007
  • noviembre 2007
  • octubre 2007
  • septiembre 2007
  • agosto 2007
  • julio 2007
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007
  • febrero 2007
  • enero 2007
  • diciembre 2006
  • noviembre 2006
  • octubre 2006
  • septiembre 2006
  • agosto 2006
  • julio 2006
  • junio 2006
  • mayo 2006
About This Site

A cras tincidunt, ut tellus et. Gravida scel ipsum sed iaculis, nunc non nam. Placerat sed phase llus, purus purus elit.

Archives Widget
  • January 2010
  • December 2009
  • November 2009
  • October 2009
Categories
  • Entertainment
  • Technology
  • Sports & Recreation
  • Jobs & Lifestyle
Search
  • twitter

Powered by WordPress  |  Business Directory by InkThemes.

This site uses cookies: Find out more.