c# null-object-pattern

Patrón de objeto nulo genérico en C#



null-object-pattern (5)

¿Qué tal esto?

public class Nothing<T> where T : class { public static implicit operator T(Nothing<T> nothing) { // your logic here } }

Me pregunto si existe algún enfoque para implementar un patrón de objeto nulo genérico en C #. El objeto nulo genérico es la subclase de todos los tipos de referencia, al igual que Nothing en Scala. Parece que

public class Nothing<T> : T where T : class

Pero no se puede compilar y no tengo idea de cómo implementar los métodos de T para proporcionar un comportamiento predeterminado o lanzar una excepción . Aquí hay algunos pensamientos:

  1. ¿Usar la reflexión?
  2. ¿Usar el árbol de expresiones al crear Nothing<T> ? Tal vez se parece a Moq. Y surge otra pregunta: ¿está bien usar un marco / biblioteca simulado en los códigos de producto?
  3. ¿Usar tipos dinámicos?

Sé que tal vez debería implementar un objeto nulo particular para un tipo particular. Solo tengo curiosidad por saber si hay alguna solución.

¿Cualquier sugerencia? Gracias.



Como hay clases selladas, no se puede hacer tal herencia en un caso genérico. No se espera que muchas clases se deriven, por lo que puede que no sea una buena idea si funcionaría.

Usar un operador implícito como lo sugiere @abatishchev parece un posible enfoque.


Con los genéricos, no se puede definir la herencia de T Si tu intención es usar if(x is Nothing<Foo>) , entonces eso no va a funcionar. No menos importante, tendría que pensar en tipos abstractos, tipos sellados y constructores no predeterminados. Sin embargo, podrías hacer algo como:

public class Nothing<T> where T : class, new() { public static readonly T Instance = new T(); }

Sin embargo, IMO que falla la mayoría de las características clave de un objeto nulo; en particular, podría fácilmente terminar con alguien haciendo:

Nothing<Foo>.Instance.SomeProp = "abc";

(Tal vez accidentalmente, después de pasar un objeto 3 niveles abajo)

Francamente, creo que deberías comprobar si es null .


Uso algo como esto en mis proyectos:

public interface IOptional<T> : IEnumerable<T> { } public interface IMandatory<T> : IEnumerable<T> { }

Dos interfaces derivadas de IEnumerable para compatibilidad con LINQ

public class Some<T> : IOptional<T> { private readonly IEnumerable<T> _element; public Some(T element) : this(new T[1] { element }) { } public Some() : this(new T[0]) {} private Some(T[] element) { _element = element; } public IEnumerator<T> GetEnumerator() { return _element.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class Just<T> : IMandatory<T> { private readonly T _element; public Just(T element) { _element = element; } public IEnumerator<T> GetEnumerator() { yield return _element; } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }

Implementación de clases Just and Some

Aviso: la implementación de estas clases es muy similar, pero tiene una diferencia. La clase Just deriva de la interfaz IMandatory y tiene solo un constructor, lo que garantiza que la instancia de la clase Just siempre tiene un valor dentro.

public static class LinqExtensions { public static IMandatory<TOutput> Match<TInput, TOutput>( this IEnumerable<TInput> maybe, Func<TInput, TOutput> some, Func<TOutput> nothing) { if (maybe.Any()) { return new Just<TOutput>( some( maybe.First() ) ); } else { return new Just<TOutput>( nothing() ); } } public static T Fold<T>(this IMandatory<T> maybe) { return maybe.First(); } }

Algunas extensiones para practicidad.

Aviso: el método de extensión Match requirió dos funciones y devolvió IMandatory, después de esto, el método de extensión Fold use .First () sin ninguna verificación.

Ahora podemos usar todo el poder de LINQ y escribir código similar a este (me refiero a mónadas .SeleccionarMany ())

var five = new Just<int>(5); var @null = new Some<int>(); Console.WriteLine( five .SelectMany(f => @null.Select(n => f * n)) .Match( some: r => $"Result: {r}", nothing: () => "Ups" ) .Fold() );