2012-07-05 5 views
15

Mi chiedo se ci sia un approccio per implementare un modello di oggetto null generico in C#. L'oggetto nullo generico è la sottoclasse di tutti i tipi di riferimento, proprio come Nothing in Scala. SembraMotivo di oggetti null generici in C#

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

Ma non può compilare e non ho idea come implementare i metodi di T per fornire il comportamento di default o un'eccezione. Ecco alcuni pensieri:

  1. Utilizzare la riflessione?
  2. Utilizzare albero di espressioni durante la creazione di Nothing<T>? Forse sembra Moq. E un'altra domanda arriva: è corretto usare mock framework/library nei codici prodotto?
  3. Utilizzare i tipi dinamici?

SO che forse dovrei implementare un oggetto nullo particolare per un tipo particolare. Sono solo curioso di sapere se c'è qualche soluzione.

Qualche suggerimento? Grazie.

+0

Questo potrebbe essere utile: http://stackoverflow.com/questions/ 2522928/how-can-i-implement-the-null-object-design-pattern-in-a-generic-form – Maarten

risposta

10

Con i generici, non è possibile definire l'ereditarietà da T. Se il tuo intento è quello di utilizzare if(x is Nothing<Foo>), allora non funzionerà. Non ultimo, dovresti pensare a tipi astratti, tipi sigillati e costruttori non predefiniti. Tuttavia, si potrebbe fare qualcosa del tipo:

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

Tuttavia, IMO che non riesce molte delle caratteristiche chiave di un oggetto nullo; in particolare, si potrebbe facilmente finire con qualcuno che fa:

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

(forse accidentalmente, dopo aver superato un oggetto 3 livelli verso il basso)

Francamente, penso che si dovrebbe solo verificare la presenza di null.

+0

Grazie. Non intendo usare 'if (x is Nothing

+1

@KirinYao dovresti farlo su una base per tipo, oppure usare i metodi di estensione (che quindi ha problemi separati con il polimorfismo) –

+0

E la seconda soluzione che ho menzionato? Posso usare l'albero delle espressioni per deridere il comportamento come Moq? È buono usare quadri mock nel codice del prodotto? –

8

Che ne dici di questo?

+0

E come posso fornire il comportamento predefinito? –

+0

@ KirinYao: Descrivi quale comportamento predefinito ti aspetteresti? – abatishchev

+0

Dipende da diversi 'T'. Quindi elencho 3 possibili approcci. Semplicemente, posso solo lanciare eccezioni quando il codice client chiama i membri di 'T'. –

0

E l'implementazione già esistente di Nullable in .NET Framework? http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx

+4

'Nullable ' significa che un tipo che non può essere nulla può essere ora. OP chiede di un'altra cosa. – abatishchev

+4

Si riferisce anche a * valori *, non * oggetti * –

+0

Vero, il mio male. usando i generici dovrebbe essere in grado di creare dei metodi, però, non sono sicuro di quale OP desideri effettivamente realizzare con questo oggetto "base". – thmsn

1

Dato che ci sono classi sigillate non è possibile effettuare tale ereditarietà in caso generico. Non ci si aspetta che molte classi siano derivate, quindi potrebbe non essere una buona idea se funzionasse.

L'utilizzo dell'operatore implicito come suggerito da @abatishchev suona come un possibile approccio.

1

io uso qualcosa di simile nei miei progetti:

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

Due un'interfaccia derivata dalla IEnumerable per la compatibilità 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(); 
    } 
} 

Attuazione delle classi soli e un po '

Avviso: L'implementazione di queste classi è molto simile, ma ha una differenza. Classe Derivata dall'interfaccia IMandatory e ha un solo costruttore, che garantisce quell'istanza di classe Ha sempre un valore all'interno.

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(); 
    } 
} 

Alcune estensioni per praticità

Avviso: Metodo di estensione Partita necessari due funzioni e ritornano IMandatory, dopo questo, metodo di estensione Fold utilizzare .Prima() senza alcun controllo.

Ora possiamo usare tutta la potenza di LINQ e scrivere codice simile questa (intendo monadi .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

Ho preso questa idea da Zoran Horvat [post] (http://www.codinghelmet.com/?path=howto/reduce-cyclomatic-complexity-option-functional-type) – kogoia