2012-07-05 35 views
15

Me pregunto si hay algún enfoque para implementar el 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 quePatrón de objeto nulo genérico en C#

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

Pero no puede compilar y no tengo idea de cómo poner en práctica los métodos de T para proporcionar un comportamiento por defecto o lanzar una excepción. Aquí están algunos pensamientos:

  1. Use the reflection?
  2. ¿Usar árbol de expresiones al crear Nothing<T>? Quizás se parece a Moq. Y surge otra pregunta: ¿está bien utilizar el framework/biblioteca simulada en los códigos de producto?
  3. ¿Utiliza 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.

¿Alguna sugerencia? Gracias.

+0

Esto podría ser útil: http://stackoverflow.com/questions/ 2522928/how-can-i-implement-the-null-object-design-pattern-in-a-generic-form – Maarten

Respuesta

10

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

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

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

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

(quizá por accidente, después de pasar un objeto 3 niveles más abajo)

Francamente, creo que sólo debe comprobar si hay null.

+0

Gracias. No pretendo usar 'if (x es Nothing

+1

@KirinYao tendrías que hacer eso por tipo, o usar métodos de extensión (que luego tienen problemas separados con el polimorfismo) –

+0

¿Qué hay de la segunda solución que mencioné? ¿Puedo usar el árbol de expresiones para simular el comportamiento como Moq? ¿Es bueno usar frameworks simulados en el código de producto? –

8

¿Qué tal esto?

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

¿Y luego cómo puedo proporcionar el comportamiento predeterminado? –

+0

@KirinYao: describa qué comportamiento predeterminado esperaría? – abatishchev

+0

Depende de diferentes 'T'. Entonces enumero 3 enfoques posibles. Simplemente, solo puedo lanzar excepciones cuando el código del cliente llama a los miembros de 'T'. –

0

¿Qué hay de la implementación ya existente de Nullable en .NET Framework? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx

+4

'Nullable ' significa un tipo que no puede ser nulo ahora. OP pregunta sobre otra cosa. – abatishchev

+4

También se relaciona con * values ​​*, not * objects * –

+0

True, my bad. usando genéricos él debería ser capaz de hacer métodos, sin embargo, no estoy seguro de qué OP realmente quiere lograr con este objeto "base". – thmsn

1

Dado que hay clases selladas, no se puede hacer tal herencia en caso genérico. No se espera que se deriven muchas clases, por lo que puede no ser una buena idea si funciona.

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

1

utilizo algo como esto en mis proyectos:

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

Dos interfaz derivada de IEnumerable para la 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 justas y algún aviso

: La implementación de estas clases es muy similar, pero tiene una diferencia. Class Just deriva de la interfaz IMandatory y solo tiene un constructor, lo que garantiza que la instancia de la clase siempre tenga 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 de practicidad

Aviso: Método de extensión de ajuste requieren dos funciones y IMandatory regresan, después de esto, utilizar el método de extensión Fold .First() sin ninguna verificación.

Ahora podemos utilizar toda la potencia de LINQ y escribir código similar este (me refiero a las mónadas .SelectMany())

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() 
     ); 
+0

Tomé esta idea de Zoran Horvat [post] (http://www.codinghelmet.com/?path=howto/reduce-cyclomatic-complexity-option-functional-type) – kogoia