En C#, un tipo nullable (Nullable <T> o T?) permite que un tipo de valor (como int, bool, double) pueda contener además el valor null. El valor null se utiliza para representar una ausencia de valor. Sin embargo, los tipos de valor (como int, double, bool) no pueden ser null por defecto, siempre tienen un valor predeterminado.
Esto es útil cuando un valor puede ser opcional o no siempre está disponible.
Ejemplo: crear variables tipo int y bool con valor null.
int? edad = null; // válido bool? activo = true; // válido
Aquí usamos la sintaxis corta T? para incluir null. La sintaxis larga sería como esto:
Nullable<int> numero1 = null; Nullable<string> cadena = null;
Un ejemplo más práctico sería como esto:
int? edad = null; if (edad.HasValue) { Console.WriteLine("Edad: " + edad.Value); } else { Console.WriteLine("Edad no especificada"); }
Operadores útiles:
- ?? → operador de coalescencia nula (valor por defecto si es null).
- ?. → acceso seguro a miembros (C# 6+).
Ejemplo de uso:
int? valor = null; int resultado = valor ?? -1; // devuelve -1 si valor es null
Usando el método GetValueOrDefault() :
int? numero = null; Console.WriteLine(numero.GetValueOrDefault()); // imprime 0
Para los programadores Java esto se les hará familiar, pues existe una clase genérica llamada Optional que es similar a Nullable<T>. Miremos un ejemplo comparativo:
En Java:
Optional<String> usuario = Optional.ofNullable(null); System.out.println(usuario.orElse("Invitado"));
En C#:
string? usuario = null; Console.WriteLine(usuario ?? "Invitado");
Métodos principales de Nullable<T>
He aquí una tabla de los principales métodos:
| Método/Propiedad | Propósito | Ejemplo | Nota clave |
|---|---|---|---|
| HasValue | Indica si contiene un valor | if (x.HasValue) { ... } |
true si no es null |
| Value | Devuelve el valor almacenado | int v = x.Value; |
Lanza InvalidOperationException si es null |
| GetValueOrDefault() | Devuelve el valor o el default del tipo | int v = x.GetValueOrDefault(); |
Si es null, devuelve 0 para int, false para bool, etc. |
| GetValueOrDefault(defaultValue) | Devuelve el valor o un valor por defecto especificado | int v = x.GetValueOrDefault(-1); |
Útil para valores personalizados |
| ToString() | Convierte el valor a cadena | Console.WriteLine(x.ToString()); |
Devuelve "" si es null |
Ahora comparemos la clase Optional de Java con Nullable<T> de C#.
| Aspecto | Java Optional<T> | C# Nullable<T> |
|---|---|---|
| Propósito | Evitar null en retornos y expresar ausencia de valor de forma explícita | Permitir que tipos de valor (int, bool, etc.) puedan ser null |
| Sintaxis | Optional<String> nombre = Optional.of("Carraro"); |
int? edad = null; (azúcar sintáctico de Nullable<int>) |
| Tipos soportados | Cualquier tipo (referencia o valor) | Solo tipos de valor (structs, int, double, bool, etc.) |
| Creación | Optional.of(value) / Optional.ofNullable(value) / Optional.empty() |
new Nullable<int>(5) / int? x = null; |
| Acceso al valor | get() (lanza excepción si vacío) |
.Value (lanza excepción si es null) |
| Verificación de presencia | isPresent() / isEmpty() |
.HasValue |
| Valores por defecto | orElse(default) / orElseGet(Supplier) |
.GetValueOrDefault() / .GetValueOrDefault(default) |
| Transformaciones | map(Function) / flatMap(Function) / filter(Predicate) |
No tiene transformaciones funcionales integradas |
| Acciones condicionales | ifPresent(Consumer) / ifPresentOrElse(...) |
Uso de HasValue + condicionales tradicionales |
| Excepciones | orElseThrow() / orElseThrow(Supplier) |
.Value lanza InvalidOperationException si es null |
| Uso recomendado | Como tipo de retorno en APIs y Streams para evitar null | Para representar datos opcionales en BD, formularios, cálculos |
| Introducción | Java 8 (2014) | .NET 2.0 (2005) |
Ejemplo. Crear un programa en C# que emplee los métodos Nullable en su sintaxis corta y larga
Program.cs
using System; class Program { static void Main() { // 1. empty() string? emptyOpt = null; Console.WriteLine("emptyOpt: " + (emptyOpt ?? "null")); // 2. of() string? opt = "Hello"; Console.WriteLine("opt: " + opt); // 3. ofNullable() string? nullableOpt = null; Console.WriteLine("nullableOpt: " + (nullableOpt ?? "null")); // 4. isPresent() Console.WriteLine("opt is present: " + (opt != null)); Console.WriteLine("emptyOpt is present: " + (emptyOpt != null)); // 5. ifPresent() if (opt != null) { Console.WriteLine("Value in opt: " + opt); } // 6. isEmpty() Console.WriteLine("emptyOpt is empty: " + (emptyOpt == null)); // 7. get() try { Console.WriteLine("Value from opt: " + opt); Console.WriteLine("Value from emptyOpt: " + emptyOpt!); // Forzamos acceso } catch (Exception e) { Console.WriteLine("Exception from emptyOpt.get(): " + e); } // 8. orElse() string value = opt ?? "Default Value"; Console.WriteLine("orElse value: " + value); string defaultValue = emptyOpt ?? "Default Value"; Console.WriteLine("orElse default value: " + defaultValue); // 9. orElseGet() string valueWithSupplier = opt ?? GetDefault(); Console.WriteLine("orElseGet value: " + valueWithSupplier); string defaultValueWithSupplier = emptyOpt ?? GetDefault(); Console.WriteLine("orElseGet default value: " + defaultValueWithSupplier); // 10. orElseThrow() try { string throwValue = emptyOpt ?? throw new ArgumentException("Value not present"); } catch (Exception e) { Console.WriteLine("Exception from orElseThrow: " + e); } // 11. map() int? lengthOpt = opt?.Length; Console.WriteLine("Mapped lengthOpt: " + (lengthOpt.HasValue ? lengthOpt.Value.ToString() : "null")); // 12. flatMap() string? flatMapped = opt != null ? opt.ToUpper() : null; Console.WriteLine("FlatMapped opt: " + (flatMapped ?? "null")); // 13. filter() string? filtered = (opt != null && opt.Length > 3) ? opt : null; Console.WriteLine("Filtered opt: " + (filtered ?? "null")); string? emptyFiltered = (opt != null && opt.Length > 10) ? opt : null; Console.WriteLine("Empty Filtered opt: " + (emptyFiltered ?? "null")); // 14. stream() (Java 9+) // En C# usamos LINQ o simplemente comprobamos null if (opt != null) { Console.WriteLine(opt); } // 15. or() (Java 9+) string? anotherOpt = opt ?? "Another Value"; Console.WriteLine("Or opt: " + anotherOpt); string? anotherEmptyOpt = emptyOpt ?? "Another Value"; Console.WriteLine("Or emptyOpt: " + anotherEmptyOpt); // 16. equals() Console.WriteLine("opt equals anotherOpt: " + (opt == anotherOpt)); Console.WriteLine("emptyOpt equals anotherEmptyOpt: " + (emptyOpt == anotherEmptyOpt)); // 17. hashCode() Console.WriteLine("opt hashCode: " + (opt?.GetHashCode() ?? 0)); Console.WriteLine("emptyOpt hashCode: " + (emptyOpt?.GetHashCode() ?? 0)); // 18. toString() Console.WriteLine("opt toString: " + (opt ?? "null")); Console.WriteLine("emptyOpt toString: " + (emptyOpt ?? "null")); } static string GetDefault() { return "Default Value from Supplier"; } }
Ahora con su sintaxis larga:
Program.cs
using System; class Program { static void Main() { // 1. empty() Nullable<string> emptyOpt = null; Console.WriteLine("emptyOpt: " + (emptyOpt ?? "null")); // 2. of() Nullable<string> opt = "Hello"; Console.WriteLine("opt: " + opt); // 3. ofNullable() Nullable<string> nullableOpt = null; Console.WriteLine("nullableOpt: " + (nullableOpt ?? "null")); // 4. isPresent() Console.WriteLine("opt is present: " + opt.HasValue); Console.WriteLine("emptyOpt is present: " + emptyOpt.HasValue); // 5. ifPresent() if (opt.HasValue) { Console.WriteLine("Value in opt: " + opt.Value); } // 6. isEmpty() Console.WriteLine("emptyOpt is empty: " + !emptyOpt.HasValue); // 7. get() try { Console.WriteLine("Value from opt: " + opt.Value); Console.WriteLine("Value from emptyOpt: " + emptyOpt.Value); // Lanza excepción } catch (Exception e) { Console.WriteLine("Exception from emptyOpt.get(): " + e); } // 8. orElse() string value = opt.HasValue ? opt.Value : "Default Value"; Console.WriteLine("orElse value: " + value); string defaultValue = emptyOpt.HasValue ? emptyOpt.Value : "Default Value"; Console.WriteLine("orElse default value: " + defaultValue); // 9. orElseGet() string valueWithSupplier = opt.HasValue ? opt.Value : GetDefault(); Console.WriteLine("orElseGet value: " + valueWithSupplier); string defaultValueWithSupplier = emptyOpt.HasValue ? emptyOpt.Value : GetDefault(); Console.WriteLine("orElseGet default value: " + defaultValueWithSupplier); // 10. orElseThrow() try { string throwValue = emptyOpt.HasValue ? emptyOpt.Value : throw new ArgumentException("Value not present"); } catch (Exception e) { Console.WriteLine("Exception from orElseThrow: " + e); } // 11. map() Nullable<int> lengthOpt = opt.HasValue ? opt.Value.Length : (int?)null; Console.WriteLine("Mapped lengthOpt: " + (lengthOpt.HasValue ? lengthOpt.Value.ToString() : "null")); // 12. flatMap() Nullable<string> flatMapped = opt.HasValue ? opt.Value.ToUpper() : null; Console.WriteLine("FlatMapped opt: " + (flatMapped.HasValue ? flatMapped.Value : "null")); // 13. filter() Nullable<string> filtered = (opt.HasValue && opt.Value.Length > 3) ? opt.Value : null; Console.WriteLine("Filtered opt: " + (filtered.HasValue ? filtered.Value : "null")); Nullable<string> emptyFiltered = (opt.HasValue && opt.Value.Length > 10) ? opt.Value : null; Console.WriteLine("Empty Filtered opt: " + (emptyFiltered.HasValue ? emptyFiltered.Value : "null")); // 14. stream() (Java 9+) if (opt.HasValue) { Console.WriteLine(opt.Value); } // 15. or() Nullable<string> anotherOpt = opt.HasValue ? opt.Value : "Another Value"; Console.WriteLine("Or opt: " + anotherOpt); Nullable<string> anotherEmptyOpt = emptyOpt.HasValue ? emptyOpt.Value : "Another Value"; Console.WriteLine("Or emptyOpt: " + anotherEmptyOpt); // 16. equals() Console.WriteLine("opt equals anotherOpt: " + (opt.Equals(anotherOpt))); Console.WriteLine("emptyOpt equals anotherEmptyOpt: " + (emptyOpt.Equals(anotherEmptyOpt))); // 17. hashCode() Console.WriteLine("opt hashCode: " + (opt.HasValue ? opt.Value.GetHashCode() : 0)); Console.WriteLine("emptyOpt hashCode: " + (emptyOpt.HasValue ? emptyOpt.Value.GetHashCode() : 0)); // 18. toString() Console.WriteLine("opt toString: " + (opt.HasValue ? opt.Value : "null")); Console.WriteLine("emptyOpt toString: " + (emptyOpt.HasValue ? emptyOpt.Value : "null")); } static string GetDefault() { return "Default Value from Supplier"; } }
Concluyendo:
La utilidad de Nullable <T> es representar datos opcionales de manera segura y expresiva, evitando ambigüedades y errores comunes como el uso de valores ficticios o NullReferenceException. Es especialmente valioso en aplicaciones que interactúan con bases de datos, formularios y cálculos donde ciertos valores pueden estar ausentes.
Enlaces:
https://www.luisllamas.es/csharp-tipos-nullables/https://comunidadcityjavamex.blogspot.com/2025/11/java-tip-24-conociendo-la-clase-optional.html

No hay comentarios:
Publicar un comentario