Hoje a ler a revista MSDN online reparei num artigo que fala sobre implementar I/O assíncrono usando delegados para os callbacks. Imediatamente lembrei-me do projecto que fiz na Inosat, em que para conseguir rede, GPS ou GRPS no dispositivo móvel, usei um sistema parecido com este.

A maior diferença é que, em vez de declarar dois métodos para callback – um em caso de sucesso e outro em caso de falha – usei apenas um, retornando sempre um valor do tipo Result<T>. Por exemplo, se virem no artigo, o autor para o método:

   1: public static string ReadAllText(string path);

define a seguinte assinatura:

   1: public static void ReadAllTextAsync(string path, Action<string> success, Action<Exception> failure);

em que o primeiro método é chamado se o método correr como esperado, o segundo é invocado se houver algum problema pelo caminho. No meu caso, a definição seria algo como:

   1: public static void ReadAllTextAsync(string path, Result<string> success);

em que a classe Result é:

   1: public class Result<T>
   2: {
   3:     private readonly T _value;
   4:     private readonly bool _valid;
   5:     private readonly string _error;
   6:  
   7:     private Result(T value)
   8:     {
   9:         _value = value;
  10:         _valid = true;
  11:     }
  12:  
  13:     private Result(string error) : this()
  14:     {
  15:         _error = error;
  16:     }
  17:  
  18:     private Result()
  19:     {
  20:         _valid = false;
  21:     }
  22:  
  23:     public static Result<T> Failed()
  24:     {
  25:         return new Result<T>();
  26:     }
  27:  
  28:     public static Result<T> Failed(string error)
  29:     {
  30:         return new Result<T>(error);
  31:     }
  32:  
  33:     public static Result<T> Success(T value)
  34:     {
  35:         return new Result<T>(value);
  36:     }
  37:  
  38:     public T Value
  39:     {
  40:         get { return _value; }
  41:     }
  42:  
  43:     public bool Valid
  44:     {
  45:         get { return _valid; }
  46:     }
  47:  
  48:     public string Error
  49:     {
  50:         get { return _error; }
  51:     }
  52: }

Isto permite que o Result seja usado para notificar se o método teve sucesso ou não e, caso não tenha, que se possa passar o erro que se encontrou.

A diferença que encontro nas duas abordagens é que, no meu caso, quem trata do resultado tem de verificar se a operação teve sucesso chamando o result.Valid. No caso da implementação que está na revista, cada método sabe se o resultado teve sucesso ou não. Para os puristas que defendem a abolição do uso de If’s (já trabalhei com alguns ;) ), isto é puro ouro!