Archive for the 'C#3' Category


Delegates em C# (Ponteiros de Função) 4

Recentemente recordei uma das dificuldades que tive quando entrei no mundo .NET, aliás minha experiência era extremamente limitada se comparada a hoje, apesar de ter muita coisa para estudar.

Pretendo neste post abordar um tópico muito importante do .NET framework que poucas pessoas usam ou desconhecem.

Quem já programou em linguagens como C/C++ isso não deve ser um problema, a menos que tenha feito somente algoritmos básicos de estrutura de dados (pilhas, filas, listas e etc).

Delegates é nada mais nada menos que um ponteiro para função. Ponteiro para muitos é um pesadelo, eu ainda tenho dificuldade quando começo a escrever um programa em C, sempre me esqueço das notações, pois em linguagens de alto nível como Java e C# elas ficam escondidas.

Bom, entenda da seguinte maneira, digamos que você possua uma subrotina/função, se você souber onde ela se encontra em memória é possível invocá-la. Um ponteiro é um endereço para uma região da memória, no caso do delegate é ponteiro para uma subrotina/função.

A questão é que em C# as coisas são tipadas, quando você define um delegate você define um contrato com quem quer “disponibilizar” um rotina ou função.

A sintaxe é a seguinte:

delegate <return_type> MyDelegate(<params>);

Por exemplo:

delegate int Calcula(int a, int b);

Podemos referenciar um função qualquer que aceite dois inteiros e retorne um inteiro como resultado, poderiamos utilizar:

public int Soma(int num1, int num2)
{
    return num1 + num2;
}

Agora definimos que um ponteiro para essa rotina da seguinte forma:

Calcula calc = Soma;

Simples, nâo?

Antigamente para eventos usava-se a seguinte nomenclatura:

Calcula calc = new Calcula(Soma);

Com o C# 2.0 podemos fazer assim:

Calcula calc = delegate(int a, int b)
{
    return a * b;
};

Ou podemos fazer, neste caso estranho:

Calcula calc = delegate
{
    return 2; // magic number detected!
}

O exemplo acima é meramente ilustrativo.

Ok, agora que conhecemos as sintaxes vamos conhecer alguns delegates muito úteis já existentes no C#:

  • Action (C#2)
  • Predicate (C#2)
  • Func (C#3)

Action

Action corresponde a uma ação a ser executada sobre um valor, onde T define o tipo de valor. Se você nâo conhece generics, sugiro que estude um pouco, não é nada complicado, só não entrarei em detalhes por que não é o escopo deste texto.

O action recebe um parametro do tipo T, por exemplo:

Action<string> printText = delegate(string s)
{
    Console.WriteLine(s);
};

Invocando:

printText("Hello World");

Simples não? No caso, podemos alterar o valor de ‘s’, pois ‘s’ é um reference type, ou seja, poderiamos tratar o texto, retirando por exemplo, todas as vírgulas do texto:

Action<string> removeVirgula = delegate(string s)
{
    s.Replace(",", string.Empty);
};

removeVirgula(“Hello, World”); // produz Hello World

Predicate

A predicate is the portion of a clause, excluding the subject, that expresses something about the subject.

Predicate ou predicado corresponde a uma clausula a qual expressa alguma coisa.

Digamos que desejamos saber se um número é par (even):

Predicate<int> even = delegate(int number)
{
    return number % 2 == 0;
};

even(2); // true
even(3); // false

Veja que retornamos um booleano para predicates, pois desejamos saber se o ‘number’ que foi passado a rotina expressa o que meu predicado quer, no caso saber se o número é par.

Func (C#3)

E por último, existente apenas no C# 3.0 o Func. Diferente do Action e Predicate, o Fund possui 5 variações:

Func<TResult>
Func<T, TResult>
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
Func<T1, T2, T3, T4, TResult>

Bom, as assinaturas são muito claras e são lidas da seguinte forma: o tipo de retorno e nenhum parâmetro ou o tipo de retorno com 1 a 4 parâmetros.

Acredito que o número de parâmetros são mais que suficientes, se a rotina começar a ter mais que esse número de parâmetros é possível que ela esteja fazendo mais coisas do que precisaria e podendo ser sub-divida em menores rotinas, facilitando tanto a leitura como reuso. Se você ainda assim achar que precisa de mais, bastaria criar uma outra assinatura com N parâmetros.

Ok, você poderia pensar em fazer uma Func que tratasse a string e não retornasse nada por exemplo. Entretanto, esse tipo de retorno não é possível. Se você pretende usar Func<> você deve fato deseja retornar um valor.

Quando lembro de function e subroutine me recordo de um trecho do texto do livro Code Complete 2nd Edition onde diferenciava o uso das duas palavras.

Function: é como definido na matemática, y = f(x), onde f é uma função. Ou seja, a função recebe um parâmetro e retorna um valor por definição.

Subrountine: já sub-rotina corresponde a uma tarefa a ser realizada, e não retorna valor. Se você já programou em Visual Basic deve ter notado a distinção delas.

Bom, voltando. O correto se deseja usar um delegate com retorno void seria Action, porém ele só possui 1 parâmetro. Mas segundo este link me parece que futuramente será extendido para mais parâmetros como o Func.

O uso é praticamente o mesmo como mostrado para predicate e action, sendo um mix de ambos.

Func<int, int, int> sum = (a, b) => a + b;

sum(1, 2) // return 3

Se não tivermos parâmetro algum, podemos usar:

Func<object> CreateObject = () => { return new object(); }

object o = CreateObject();

Observe que as delegates predefinidas como Action, Predicate e Func não precisam ser obrigatóriamente usadas, você pode criar suas próprias assinaturas como foi feito no início deste post, mas se for possível usar as já existentes use, pois é uma linguagem comum para todos os programadores em C#, poderíamos ter um delegate Process<T> igual a Action<T>, se usasse Action, seria muito claro o que pretende-se fazer. Não é uma regra, mas sempre que possível use as já predefinidas.

Bom, o post ficou bem grande, tentei ser o mais detalhado e claro possível. Até um próximo post.

Banco de Dados e Null values 0

É comum na modelagem de banco de dados termos colunas com valores nulos. Para tipos como string, que são “reference type”, valor null é extremamente comum, mas e quanto a valores primitivos como: int, float, double, DateTime (struct no C#) e entre outros?

Na versão 2.0 do .NET, sim numa versão já adotada no mercado (.NET 1.1 é bem pouco, espero) apresentou o conceito de Nullable Types que permite você inferir valores nulos à built-in types (ou tipos primitos). Não irei entrar em detalhes muito profundos, pois pretendo ser breve nest post.

A sintaxe é bem simples:

   1: int? a = null;
   2:  
   3: // dois properties para Nullable Types, HasValue e Value
   4: if (a.HasValue) Console.WriteLine(a.Value);

Assim podemos atribuir valores para nulos e não mais valores default para estes tipos de dados.

Ok, isso é bonito mas o IDataReader não possui um método para tratar quando valores são nulos, o que fazemos? Realizamos uma checagem com o método IsDbNull, e se for podemos retornar nulo ou o valor caso OK. Veja abaixo:

   1: int? coluna = reader.IsDBNull(i) ? (int?)null : (int?)reader.GetInt32(i);

Isso resolveria o nosso problema, mas e se tivermos 10 colunas na tabela, esse código polui um pouco, não? Pena que o GetInt32(int i) não lida com campos nulos.

Como podemos melhorar isso?

Podemos usar Extension Methods!

Num post anterior eu cheguei a falar bem básicamente sobre extension methods, veja como resolveríamos este problema:

   1: static class DataReaderTypeHelper
   2: {
   3:     public static int? GetNullOrInt32(this IDataReader reader, int index)
   4:     {
   5:         return reader.IsDBNull(index) ? (int?)null : (int?)reader.GetInt32(index);
   6:     }
   7:  
   8:     public static string GetNullOrString(this IDataReader reader, int index)
   9:     {
  10:         return reader.IsDBNull(index) ? null : reader.GetString(index);
  11:     }
  12:  
  13:     public static DateTime? GetNullOrDateTime(this IDataReader reader, int index)
  14:     {
  15:         return reader.IsDBNull(index) ? (DateTime?)null : (DateTime?)reader.GetDateTime(index);
  16:     }
  17: }

Agora podemos este método e tornar a coisa mais bonita e direta:

   1: int? coluna = reader.GetNullOrInt32(index);

Muito mais simples, fácil e direto de usar. Podemos fazer também para inserção, remoção ou atualização onde valores nulos encrencam no banco de dados também. Normalmente teríamos que fazer:

   1: cmd.Parameters.AddWithValue("@param1", param1);
   2: cmd.Parameters.AddWithValue("@param2", param2);

Se usassemos o código acima, se @param2 pudesse ser nulo no banco de dados, este código geraria uma SqlException caso o valor de param2 fosse nulo, pois o null do c# é diferente do null para o banco de dados, então teríamos que verificar o valor e assim retornar DBNull.Value.

Com Nullable Types, isso ficaria extremamente simples, nada de if‘s:

   1: cmd.Parameters.AddWithValue("@param2", (object)param2 ?? DBNull.Value);

Observe que temos de fazer um cast para object em param2, pois para o compilador o operador ?? deve garantir que param2 e DBNull.Value sejam do mesmo tipo, por que Nullable usa Generics para inferir tipos, embora mascare isso.

Usando novamente Extension Methods podemos fazer uma classe que nos ajude nisso:

   1: static class DbHelper
   2: {
   3:     public static void AddWithValueOrNull(this SqlParameterCollection collection, string param, object value)
   4:     {
   5:         collection.AddWithValue(param, (object)value ?? DBNull.Value);
   6:     }
   7: }

Agora temos um método AddWithValueOrNull para o Parameters que é do tipo SqlParametersCollection. Assim encapsulamos tudo numa sintaxe mais limpa:

   1: cmd.Parameters.AddWithValueOrNull("@param2", param2);

Nota usamos este method apenas para valores que possam ser nulos e não para todos, no caso de chaves primárias devemos usar o AddWithValue mesmo por que devemos reforçar a necessidade de valores não nulos.