lunedì 17 settembre 2012

Come evitare NullReferenceException (Extension Method)

Qualcuno potrebbe implementare la soluzione precedente, utilizzando un "Extension Method". Vediamo come rivedere il codice precedente:

   public static class ObjectExtensions
   {
      public static T Perform<T>(this T instance, Action<T> action) 
         where T : class
      {
         if (instance != null)
            action(instance);
         return instance;
      }
   }

Con questa "magia" qualsiasi oggetto risulta avere il metodo Perform disponibile e quindi sarà eseguita una azione su quell'oggetto se e solo se quell'oggetto è stato instanziato.

La classe Repository viene aggiornata come segue:

   public class Repository
   {
      private StudentsDatabase studentsDatabase;

      public Student StudentFoundByCode(int code)
      {
         return studentsDatabase.GetByCode(code);
      }
   }

e tramite l'extension method otteniamo una sintassi simile alla precedente:

   repository
      .StudentFoundByCode(1234)
      .Perform(new Output().Write);

la differenza sostanziale è che siamo ancora in balia della null reference exception, infatti ora restituiamo un oggetto Student che non è più incapsulato nella classe che può o non può eseguire operazioni sull'oggetto student. Ovvero, possiamo a questo punto fare le seguente operazione:

   repository
      .StudentFoundByCode(1234)
      .Name;

Student non è stato isolato. Questa soluzione è caldamente sconsigliabile. Invece possiamo implementare due classi Generic per incapsulare  qualsiasi oggetto ed eseguire operazioni safe sui comportamenti dell'oggetto.

Definiamo il contratto per eseguire le operazioni su un oggetto:

   public interface ISubstitute<out T>
      where T : class
   {
      ISubstitute<T> Perform(Action<T> action);
   }

Creiamo la classe che non esegue operazioni quando l'oggetto non è instanziato:

   public class NullSubstitute<T> : ISubstitute<T>
      where T : class
   {
      public ISubstitute<T> Perform(Action<T> action)
      {
         return this;
      }
   }

Implementiamo la classe che  esegue operazioni quando l'oggetto è instanziato:

   public class Substitute<T> : ISubstitute<T> 
      where T : class
   {
      private readonly T instance;

      public Substitute(T instance)
      {
         this.instance = instance;
      }

      public ISubstitute<T> Perform(Action<T> action)
      {
         action(instance);
         return this;
      }
   }

Aggiorniamo la classe repository:

   public class Repository
   {
      private StudentsDatabase studentsDatabase;

      public ISubstitute<Student> StudentFoundByCode(int code)
      {
         var student = studentsDatabase.GetByCode(code);
         if (student==null)
            return new NullSubstitute<Student>();
         return new Substitute<Student>(student);
      }
   }

Ora siamo nella condizione di non dover creare per tutti gli oggetti una specifica classe che si occupa di eseguire operazioni sull'oggetto, per chi ama l'utilizzo dell' extension method possiamo eventualemente creare una extension per centralizzare la factory che è implicita nella parte del codice che decide quale tipo di oggetto Substitute deve essere instanziato:

   public static class ObjectExtensions
   {
      public static ISubstitute<T> CreateSubstitute<T>(this T instance)
         where T : class
      {
         if (instance == null)
            return new NullSubstitute<T>();
        return new Substitute<T>(instance);
      }
   }

quindi, aggiorniamo il metodo StudentFoundByCode:

   public ISubstitute<Student> StudentFoundByCode(int code)
   {
      return _studentsDatabase
                .GetByCode(code)
                .CreateSubstitute();
   }