manejo - Retorno opcional en C#.Net
null c# operator (9)
¿Hay una clase de Nullable / opcional en C #, que nos obliga a probar si el objeto existe antes de extraerlo y usarlo?
Las nullables fueron creadas para que los tipos primitivos pudieran ser nulos. Su valor predeterminado no tenía que ser un valor real (como int, sin nullables, su valor predeterminado es 0, por lo que un 0 significa algo 0 o no está configurado a nada 0)
No, no hay nada que pueda hacer para obligar a un programador a verificar si un objeto es nulo. Aunque eso es bueno. Hacerlo crearía una inmensa cantidad de gastos generales. Si se tratara de una función de idioma, ¿con qué frecuencia forzarías la verificación? ¿Lo requerirías cuando la variable sea asignada por primera vez? ¿Qué pasa si la variable apunta a otro objeto más tarde? ¿Lo obligaría a verificar antes de cada método y propiedad, y si falla, lanzaría una excepción? Obtienes eso ahora con una excepción de referencia nula. Obtendría muy poco beneficio al obligar a alguien a hacer esto más allá de lo que ya tiene.
Java 1.8 está recibiendo la clase opcional, que nos permite decir explícitamente cuándo un método puede devolver un valor nulo y "forzar" a su consumidor a verificar si no es nulo ( isPresent()
) antes de usarlo.
Veo que C # tiene Nullable, que hace algo similar, pero con tipos básicos. Parece que se usa para consultas de base de datos, para distinguir cuándo existe un valor y es 0 desde cuando no existe y es nulo.
Pero parece que Nullable de C # no funciona para objetos, solo para tipos básicos, mientras que la Opcional de Java solo funciona para objetos y no para tipos básicos.
¿Hay una clase de Nullable / opcional en C #, que nos obliga a probar si el objeto existe antes de extraerlo y usarlo?
Decidí implementar algún tipo de prototipo de clase <> Java opcional hace algún tiempo con una de las últimas versiones de C #.
Aquí está de hecho:
public sealed class Optional<T>
{
private static readonly Optional<T> EMPTY = new Optional<T>();
private readonly T value;
private Optional() => value = default;
private Optional(T arg) => value = arg.RequireNonNull("Value should be presented");
public static Optional<T> Empty() => EMPTY;
public static Optional<T> Of(T arg) => new Optional<T>(arg);
public static Optional<T> OfNullable(T arg) => arg != null ? Of(arg) : Empty();
public static Optional<T> OfNullable(Func<T> outputArg) => outputArg != null ? Of(outputArg()) : Empty();
public bool HasValue => value != null;
public void ForValuePresented(Action<T> action) => action.RequireNonNull()(value);
public IOption<T> Where(Predicate<T> predicate) => HasValue
? predicate.RequireNonNull()(value) ? this : Empty() : this;
public IOption<TOut> Select<TOut>(Func<T, TOut> select) => HasValue
? Optional<TOut>.OfNullable(select.RequireNonNull()(value))
: Optional<TOut>.Empty();
public IOption<IOption<TOut>> SelectMany<TOut>(Func<T, IOption<TOut>> select) => HasValue
? Optional<IOption<TOut>>.OfNullable(select.RequireNonNull()(value))
: Optional<IOption<TOut>>.Empty();
public T Get() => value;
public T GetCustomized(Func<T, T> getCustomized) => getCustomized.RequireNonNull()(value);
public U GetCustomized<U>(Func<T, U> getCustomized) => getCustomized.RequireNonNull()(value);
public T OrElse(T other) => HasValue ? value : other;
public T OrElseGet(Func<T> getOther) => HasValue ? value : getOther();
public T OrElseThrow<E>(Func<E> exceptionSupplier) where E : Exception => HasValue ? value : throw exceptionSupplier();
public static explicit operator T(Optional<T> optional) => OfNullable((T) optional).Get();
public static implicit operator Optional<T>(T optional) => OfNullable(optional);
public override bool Equals(object obj)
{
if (obj is Optional<T>) return true;
if (!(obj is Optional<T>)) return false;
return Equals(value, (obj as Optional<T>).value);
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => HasValue ? $"Optional has <{value}>" : $"Optional has no any value: <{value}>";
}
En el proyecto "Extensiones de lenguaje funcional C #" https://github.com/louthy/language-ext existe el objeto Option de F # entre otros patrones funcionales
En lugar de escribir su propia clase, puede usar Microsoft.FSharp.Core.FSharpOption<T>
del ensamblado FSharpCore.dll
. Desafortunadamente, los tipos F # son un poco torpes cuando se usan en C #.
//Create
var none = FSharpOption<string>.None;
var some1 = FSharpOption<string>.Some("some1");
var some2 = new FSharpOption<string>("some2");
//Does it have value?
var isNone1 = FSharpOption<string>.get_IsNone(none);
var isNone2 = OptionModule.IsNone(none);
var isNone3 = FSharpOption<string>.GetTag(none) == FSharpOption<string>.Tags.None;
var isSome1 = FSharpOption<string>.get_IsSome(some1);
var isSome2 = OptionModule.IsSome(some1);
var isSome3 = FSharpOption<string>.GetTag(some2) == FSharpOption<string>.Tags.Some;
//Access value
var value1 = some1.Value; //NullReferenceException when None
var value2 = OptionModule.GetValue(some1); //ArgumentException when None
En mi opinión, cualquier implementación de Option
que exponga la propiedad HasValue
es la derrota de toda la idea. El punto de los objetos opcionales es que puede hacer llamadas incondicionales a su contenido sin probar si el contenido está allí.
Si tiene que probar si el objeto opcional contiene un valor, entonces no ha hecho nada nuevo en comparación con las pruebas null
comunes.
Aquí está el artículo en el que explico los objetos opcionales con todo detalle: Implementación personalizada de la opción / Quizás tipo en C #
Y aquí está el repositorio de GitHub con código y ejemplos: https://github.com/zoran-horvat/option
Si se resiste a usar una solución opcional de peso pesado, entonces puede crear fácilmente una solución liviana. Puede crear su propio tipo de Option<T>
que implemente la IEnumerable<T>
, de modo que pueda aprovechar los métodos de extensión LINQ para convertir las llamadas en opcionales. Aquí está la implementación más simple posible:
public class Option<T> : IEnumerable<T>
{
private readonly T[] data;
private Option(T[] data)
{
this.data = data;
}
public static Option<T> Create(T element)
{
return new Option<T>(new[] { element });
}
public static Option<T> CreateEmpty()
{
return new Option<T>(new T[0]);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>) this.data).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
El uso de este tipo de Option<T>
se realiza a través de LINQ:
public class Option<T> : IEnumerable<T>
{
private readonly T[] data;
private Option(T[] data)
{
this.data = data;
}
public static Option<T> Create(T value)
{
return new Option<T>(new T[] { value });
}
public static Option<T> CreateEmpty()
{
return new Option<T>(new T[0]);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)this.data).GetEnumerator();
}
System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator()
{
return this.data.GetEnumerator();
}
}
Puede encontrar más información sobre los objetos opcionales en estos artículos:
- Implementación personalizada de la opción / Tal vez escriba C #
- Entendiendo la Opción (Tal vez) Tipo Funcional
- Cómo reducir la complejidad ciclomática: opción tipo funcional
Y puede consultar mis cursos de video para obtener más detalles sobre cómo simplificar el flujo de control usando el tipo de Option
y otros medios: Hacer que su código C # sea más funcional y los patrones de diseño táctico en .NET: Flujo de control
El primer curso de video ( Cómo hacer que su código C # sea más funcional ) brinda una introducción detallada a la programación orientada al ferrocarril, incluidos los tipos de Option
y Either
los Either
y cómo pueden usarse para administrar objetos opcionales y manejar casos y errores excepcionales.
Hay una mejor implementación del tipo de opción en C #. Puede encontrar esta implementación en patrones de diseño táctico en .NET por Zoran Horvat en pluralsight.com. Incluye una explicación de por qué y cómo usarlo. La idea básica es implementar la clase de opción como implementación de la interfaz IEnumerable <>.
Option<Car> optional = Option<Car>.Create(myCar);
string color = optional
.Select(car => car.Color.Name)
.DefaultIfEmpty("<no car>");
No en el idioma, no, pero puedes hacer el tuyo:
public struct Optional<T>
{
public bool HasValue { get; private set; }
private T value;
public T Value
{
get
{
if (HasValue)
return value;
else
throw new InvalidOperationException();
}
}
public Optional(T value)
{
this.value = value;
HasValue = true;
}
public static explicit operator T(Optional<T> optional)
{
return optional.Value;
}
public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public override bool Equals(object obj)
{
if (obj is Optional<T>)
return this.Equals((Optional<T>)obj);
else
return false;
}
public bool Equals(Optional<T> other)
{
if (HasValue && other.HasValue)
return object.Equals(value, other.value);
else
return HasValue == other.HasValue;
}
}
Tenga en cuenta que no podrá emular ciertos comportamientos de Nullable<T>
, como la capacidad de Nullable<T>
un valor que puede contener nulos sin valor en nulo, en lugar de un nullable en caja, ya que tiene un soporte especial del compilador para eso (y un algún otro) comportamiento.
No hay nada incorporado, pero puedes definir el tuyo. Tenga en cuenta que una implementación de la Option<T>
no tiene sentido sin definir los operadores map / bind.
public struct Option<T>
{
private bool hasValue;
private T value;
public Option(T value)
{
if (value == null) throw new ArgumentNullException("value");
this.hasValue = true;
this.value = value;
}
public Option<TOut> Select<TOut>(Func<T, TOut> selector)
{
return this.hasValue ? new Option<TOut>(selector(this.value)) : new Option<TOut>();
}
public Option<TOut> SelectMany<TOut>(Func<T, Option<TOut>> bind)
{
return this.hasValue ? bind(this.value) : new Option<TOut>();
}
public bool HasValue
{
get { return this.hasValue; }
}
public T GetOr(T @default)
{
return this.hasValue ? this.value : @default;
}
}
Quizás esto está más cerca del tipo de opción F #
public struct Option<T>
{
private T value;
private readonly bool hasValue;
public bool IsSome => hasValue;
public bool IsNone => !hasValue;
public T Value
{
get
{
if (!hasValue) throw new NullReferenceException();
return value;
}
}
public static Option<T> None => new Option<T>();
public static Option<T> Some(T value) => new Option<T>(value);
private Option(T value)
{
this.value = value;
hasValue = true;
}
public TResult Match<TResult>(Func<T, TResult> someFunc, Func<TResult> noneFunc) =>
hasValue ? someFunc(value) : noneFunc();
public override bool Equals(object obj)
{
if (obj is Option<T>)
{
var opt = (Option<T>)obj;
return hasValue ? opt.IsSome && opt.Value.Equals(value) : opt.IsNone;
}
return false;
}
public override int GetHashCode() =>
hasValue ? value.GetHashCode() : 0;
}