c# avançado

81
C# 2008 - Módulo II 1 Treinar Cursos e Treinamentos 1 Índice Acesso a Dados com ADO.NET ..................................................................................................... 3 Ado.net ............................................................................................................................................ 3 Principais classes do ADO.NET ................................................................................................. 4 DataSource .............................................................................................................................. 4 SQLConnection ....................................................................................................................... 5 SQLCommand ......................................................................................................................... 6 SQLDataReader ...................................................................................................................... 6 Parâmetros ............................................................................................................................... 7 SQLDataAdapter , DataSet e DataTable ................................................................................. 8 SQLTransaction ...................................................................................................................... 9 Generics ......................................................................................................................................... 10 Nullable Types .......................................................................................................................... 11 Métodos Anônimos ................................................................................................................... 12 Actions ...................................................................................................................................... 12 Predicates .................................................................................................................................. 13 Comparison ............................................................................................................................... 14 Converter ................................................................................................................................... 15 Inference Types ......................................................................................................................... 16 Object Initializers ...................................................................................................................... 17 Anonymous Types..................................................................................................................... 18 Extension Methods .................................................................................................................... 18 Implicitly Typed Arrays ............................................................................................................ 19 Lambda Expressions ................................................................................................................. 19 LINQ ............................................................................................................................................. 22 Usando LINQ em Collections ................................................................................................... 22 Usando o operador ORDER BY ............................................................................................... 23 Querys em Tipos Estruturados .................................................................................................. 23 Escrevendo consultas em LINQ ................................................................................................ 24 Filtrando pela média: ............................................................................................................. 25 Para Ordemenar os resultados ............................................................................................... 25 Para criar um campo calcumado ........................................................................................... 26 LINQ To SQL ............................................................................................................................... 27 Incluindo um novo registro ....................................................................................................... 27 Alterando o registro ................................................................................................................... 28 Excluindo o registro .................................................................................................................. 28 Threading ...................................................................................................................................... 29 CPU vs. I/O Bound.................................................................................................................... 29 A classe Thread ......................................................................................................................... 29 ThreadPool ................................................................................................................................ 31 Fazendo chamadas thread-safe para controles Windows Forms ............................................... 33 Chamadas para um Controle do Windows que não são thread-safe ..................................... 33 Chamadas thread-safe para um Controle de formulários do Windows ................................. 34 Chamadas thread-safe com BackgroundWorker ................................................................... 35 BackgroundWorker ............................................................................................................... 35 Manipulando XML com C# .......................................................................................................... 38 EXEMPLO DE XML ................................................................................................................ 38 Criação do Arquivo xml ............................................................................................................ 38

Upload: amanda

Post on 17-Jan-2016

105 views

Category:

Documents


5 download

DESCRIPTION

Livro didático sobre a linguagem C#.

TRANSCRIPT

C# 2008 - Módulo II 1

Treinar – Cursos e Treinamentos 1

Índice

Acesso a Dados com ADO.NET ..................................................................................................... 3

Ado.net ............................................................................................................................................ 3

Principais classes do ADO.NET ................................................................................................. 4

DataSource .............................................................................................................................. 4

SQLConnection ....................................................................................................................... 5

SQLCommand ......................................................................................................................... 6

SQLDataReader ...................................................................................................................... 6

Parâmetros ............................................................................................................................... 7

SQLDataAdapter , DataSet e DataTable ................................................................................. 8

SQLTransaction ...................................................................................................................... 9

Generics ......................................................................................................................................... 10

Nullable Types .......................................................................................................................... 11

Métodos Anônimos ................................................................................................................... 12

Actions ...................................................................................................................................... 12

Predicates .................................................................................................................................. 13

Comparison ............................................................................................................................... 14

Converter ................................................................................................................................... 15

Inference Types ......................................................................................................................... 16

Object Initializers ...................................................................................................................... 17

Anonymous Types ..................................................................................................................... 18

Extension Methods .................................................................................................................... 18

Implicitly Typed Arrays ............................................................................................................ 19

Lambda Expressions ................................................................................................................. 19

LINQ ............................................................................................................................................. 22

Usando LINQ em Collections ................................................................................................... 22

Usando o operador ORDER BY ............................................................................................... 23

Querys em Tipos Estruturados .................................................................................................. 23

Escrevendo consultas em LINQ ................................................................................................ 24

Filtrando pela média: ............................................................................................................. 25

Para Ordemenar os resultados ............................................................................................... 25

Para criar um campo calcumado ........................................................................................... 26

LINQ To SQL ............................................................................................................................... 27

Incluindo um novo registro ....................................................................................................... 27

Alterando o registro ................................................................................................................... 28

Excluindo o registro .................................................................................................................. 28

Threading ...................................................................................................................................... 29

CPU vs. I/O Bound.................................................................................................................... 29

A classe Thread ......................................................................................................................... 29

ThreadPool ................................................................................................................................ 31

Fazendo chamadas thread-safe para controles Windows Forms ............................................... 33

Chamadas para um Controle do Windows que não são thread-safe ..................................... 33

Chamadas thread-safe para um Controle de formulários do Windows ................................. 34

Chamadas thread-safe com BackgroundWorker ................................................................... 35

BackgroundWorker ............................................................................................................... 35

Manipulando XML com C# .......................................................................................................... 38

EXEMPLO DE XML ................................................................................................................ 38

Criação do Arquivo xml ............................................................................................................ 38

C# 2008 - Módulo II 2

Treinar – Cursos e Treinamentos 2

Inserir Registro: ......................................................................................................................... 38

Alterar Registro: ........................................................................................................................ 39

Deletar Registro: ....................................................................................................................... 39

Reflection ...................................................................................................................................... 40

AppDomains .............................................................................................................................. 40

Assemblies ................................................................................................................................ 41

Nomenclatura dos Assemblies .............................................................................................. 41

Carregamento manual de Assemblies ................................................................................... 41

Metadados ............................................................................................................................. 42

System.Reflection.MemberInfo ............................................................................................ 43

System.Type .......................................................................................................................... 44

Criando uma DLL ................................................................................................................. 47

Criando um cliente para a DLL ............................................................................................. 48

System.Reflection.FieldInfo .................................................................................................. 51

System.Reflection.MethodBase ............................................................................................ 51

System.Reflection.PropertyInfo ............................................................................................ 52

System.Reflection.EventInfo ................................................................................................ 53

Invocando os membros em runtime ...................................................................................... 53

Criação dinâmica de Assemblies ........................................................................................... 57

Globalization ................................................................................................................................. 59

Utilizando Culturas ................................................................................................................... 59

DateTimeFormatInfo ............................................................................................................. 62

NumberFormatInfo ................................................................................................................ 63

Encoding ................................................................................................................................ 65

UserControl ................................................................................................................................... 68

WCF .............................................................................................................................................. 71

Estrutura ........................................................................................................................................ 71

Hosting .......................................................................................................................................... 73

Endpoints ....................................................................................................................................... 73

Configuração do Cliente ................................................................................................................ 74

Criando seu primeiro serviço com WCF ....................................................................................... 75

Criando o cliente ....................................................................................................................... 78

C# 2008 - Módulo II 3

Treinar – Cursos e Treinamentos 3

Acesso a Dados com ADO.NET

O ADO.NET, sucessor do ADO, surgiu com a promessa de ser um modelo desconectado para um mundo conectado. Sua arquitetura era preparada para a Internet, onde as aplicações poderiam abrir uma conexão e recuperar dados, fechar a conexão, trabalhar desconectados e só voltar a abrir a conexão no momento de atualizar a fonte de dados.

O modelo de acesso a dados proposto pelo ADO.NET embora poderoso, é extramente complexo: para executar operações em uma fonte de dados o desenvolvedor teria que utilizar uma grande quantidade de classes como Connection, Transaction, Command, DataReader, DataAdapter, DataSet, DataTable etc. e entender como elas se inter-relacionam. Na verdade isso não mudou desde a versão 1.1: A estrutura do ADO.NET é basicamente a mesma. Porém uma novidade no .NET 2.0 foram algumas classes denominadas DataSource, entre eles o SQLDataSource.

Ado.net

O ADO.NET foi criado para ser um modelo de dados desconectado, pronto para Web. É formando por uma série de objetos, alguns independentes de fonte de dados, outros não. Um objeto independe de fonte pode abrigar ao mesmo tempo dados de origens diversas, como por exemplo, uma tabela de um banco de dados Oracle e outra de um arquivo Access.

O Objeto de conexão para o SQLSever é o SQLConnection. Havendo uma conexão os dados podem ser recuperados para um SQLDatareader, SQLCommand ou mesmo um DataSet.

O SQLCommand permite executar um comando SQL contra uma fonte de dados. Pode trabalhar apenas com um SQLConnection ou em conjunto com outras classes ADO.NET.

Um SQLDataReader é um cursor unidirecional e somente leitura. É ótimo para leitura de dados ou geração de relatórios, pois é veloz. Porém não permite qualquer alteração nos dados, além de monopolizar a conexão com o banco de dados enquanto esta operando.

Um DataSet é um pequeno banco de dados em memória. Você não precisa de uma conexão permanente com o banco de dados enquanto estiver utilizando os dados. Você pode abrir a conexão, recuperar os dados, executar alterações, exclusões ou inclusões, abrir novamente a conexão e atualizar a fonte de dados. Dentro de uma DataSet podemos ter uma ou mais DataTables, que são tabelas, que podem ter origens de fontes de dados independentes. Podemos ainda ter DataRelations, que representam relações entre tabelas. Uma DataTable é formado por DataRows, que representam colunas. Outra classe importante é DataView, que é exatamente o que parece: Uma visão de dados. Um DataTable possui um DataView padrão, outros podem ser criados. Todos os objetos descritos neste parágrafo são independentes da fonte dos dados, pois são armazenados em memória no formato XML.

C# 2008 - Módulo II 4

Treinar – Cursos e Treinamentos 4

Agora como transformar dados de uma fonte especifica em um DataSet? Esta tarefa é do SQLDataAdapter , que faz a transformação dos dados entre a sua origem, através da conexão, até o DataSet e vice-versa. O SQLDataAdapter devolve a conexão no estado que a encontrou: Se estava aberta, mantém aberta. Se estava fechado, abre, executa a operação e fecha a conexão.

Principais classes do ADO.NET

DataSource

Embora existam provedores para acessar as mais variadas fontes de dados, iremos demostrar as funcionalidades do ADO.NET com classes de acesso ao SQLServer. A funcionalidade das demais classes é bem semelhante.

A classe DataSource é uma classe que nos permite acessar diversos bancos de dados relacionais ou não, e que internamente utiliza diversos objetos do ADO.NET, tornando o seu uso mais simples através de um único objeto. Por exemplo, para retornar um conjunto de dados de um SQLServer, você precisar no mínimo de um SQLConnection, um SQLCommand e um SQLDataReader. Por outro lado, você pode obter o mesmo

C# 2008 - Módulo II 5

Treinar – Cursos e Treinamentos 5

conjunto de dados utilizando um único SQLDataSource, que internamente fará uso destes objetos.

Apesar do SQLDataSource fornecer um modelo simplificado e mais amigável, você deve conhecer os componentes do ADO.NET, pois a verdadeira funcionalidade de acesso a dados está nestas classes.

Cache: O SQLDataSource possui suporte a cache. Isto significa que após a execução de uma consulta, o conjunto de resultados será armazenados na memória do servidor. Na próxima consulta ao invés de recuperar as informações do banco de dados, o SQLDataSource irá recuperá-los do cache. Para habilitar cache no SQLDataSource, basta definir a propriedade EnableCache como True. A propriedade CacheDurantion deve conter o tempo, em segundos, que os dados serão mantidos em cache antes de nova consulta.

DataSourceMode: Esta propriedade indica como os dados serão carregados: Em um objeto DataSet ou um objeto DataReader. No modo DataSet o controle que estiver ligado ao SQLDataSource terá mais funcionalidades, como paginação e ordenação. No modo DataReader estas funcionalidades não estarão disponíveis, porém por se tratar de um cursor unidirecional e somente leitura, seu desempenho é superior.

ConnectionString: Através desta propriedade é definida a string de conexão, que contem as informações para conexão como sistema gerenciador de banco de dados.

SQLConnection

Para utilizar as classes ADO.NET para SQL Server, você deve importar o namespace system.data.sqlclient. Para as classes independentes de fonte de dados, como o DataSet, o namespace é sytem.data

O SQLConnection representa uma conexão com um banco de dados SQLServer. Suas principais propriedades são ConnectionString, que é a string com os parâmetros de conexão com o servidor. Entre os métodos temos Open, que abre a conexão, e close, que fecha a mesma. A propriedade State retorna o estado atual da conexão. Vejamos o exemplo abaixo:

SqlConnection Conexao = new SqlConnection();

Conexao.ConnectionString = "Data Source=LOCALHOST\\SQLEXPRESS; Initial

Catalog=AdventureWorks; Integrated Security=True";

Conexao.Open();

//Executa algum código

Conexao.Close();

A abertura da conexão deve ser protegida em um bloco try...catch, já que ao abrir uma conexão, o servidor pode não estar disponível, a string de conexão pode estar errada, o usuário pode não ter permissão de acesso, etc.

C# 2008 - Módulo II 6

Treinar – Cursos e Treinamentos 6

SQLCommand

A classe SQLCommand permite que seja executado qualquer comando SQL.

propriedade CommandText: instrução SQL, de um nome de tabela ou procedure.

propriedade Connection: Conexão válida com o banco de dados.

A execução do comando pode ser feito através de três métodos distintos:

• ExecuteNonQuery: Ideal para execução de instruções que não retornam um conjunto de dados. Retorna um valor inteiro informando o número de linhas afetadas;

• ExecuteReader: Para utilização com um SQLDataReader, a ser estudado na próxima sessão;

• ExecuteScalar: Retorna apenas a primeira coluna da primeira linha, o restante dos valores são ignorados. Ideal para sumarização de valores.

O exemplo abaixo retorna o total de registros de uma tabela e exibe em um textbox, para funcionar perfeitamente basta encaixá-lo no comentário do exemplo anterior, de demonstração da conexão:

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT COUNT(*) FROM Sales.Currency";

int a = Convert.ToInt32(comando.ExecuteScalar());

TextBox1.Text = Convert.ToString(a);

SQLDataReader

O SQLDataReader é um cursor unidirecional e somente leitura, porém muito veloz. Você pode ligar seu resultado diretamente a um controle:

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT CurrencyCode,Name FROM Sales.Currency";

SqlDataReader reader = comando.ExecuteReader();

GridView1.DataSource = reader;

Neste outro exemplo, ele é ligado a um DropDownList. Note que nome da moeda é ligado a propriedade TextField, enquanto o código a propriedade Valuefield, desta forma será exibida a nome da moeda para o usuário, mas programaticamente poderemos facilmente recuperar o código da moeda selecionada:

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT CurrencyCode, Name FROM Sales.Currency";

SqlDataReader reader = comando.ExecuteReader();

DropDownList1.DataSource = reader;

DropDownList1.DataValueField = "CurrencyCode";

DropDownList1.DataTextField = "Name";

No próximo exemplo, temos apenas uma pequena alteração: É passado um argumento

C# 2008 - Módulo II 7

Treinar – Cursos e Treinamentos 7

para a sobrecarga do construtor do ExecuteReader: CommandBehavior, que é um enumerador que vai determinar o comportamento do SQLDataReader. Neste exemplo, CommandBehavior.CloseConnection fecha a conexão com o banco de dados após a utlização:

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT CurrencyCode,Name FROM Sales.Currency";

SqlDataReader reader =

comando.ExecuteReader(CommandBehavior.CloseConnection);

DropDownList1.DataSource = reader;

DropDownList1.DataValueField = "CurrencyCode";

DropDownList1.DataTextField = "Name";

Para percorrer os itens de um SQLDataReader, podemos utilizar o método Read, que retorna verdadeiro e avança um registro, enquanto houverem linhas disponíveis. Neste exemplo as linhas são adicionadas a um ListBox:

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT CurrencyCode,Name FROM Sales.Currency";

SqlDataReader reader =

comando.ExecuteReader(CommandBehavior.CloseConnection);

while (reader.Read())

{

ListBox1.Items.Add(Convert.ToString(reader["Name"]));

}

Parâmetros

A utilização das classes de parametros do ADO.NET é um ponto importante no quesito segurança, pois eliminam o risco de injeção de SQL. A classe parâmetros permite que sejam criados parâmetros para a execução de instruções sql. Na instrução o parâmetro deve ser identificado por @ mais um identificador único. O objeto parâmetro deverá ter o mesmo nome o qual vai passar o valor.

SqlCommand comando = new SqlCommand();

comando.Connection = Conexao;

comando.CommandText = "SELECT CurrencyCode,Name FROM Sales.Currency";

SqlParameter parametro = new SqlParameter("@ContactID", SqlDbType.Int);

parametro.Value = Convert.ToInt32("1");

comando.Parameters.Add(parametro);

SqlDataReader reader =

comando.ExecuteReader(CommandBehavior.CloseConnection);

GridView1.DataSource = reader;

Primeiro instanciamos o objeto SQLParameter utilizando um de seus construtores. No exemplo, é passado o nome do parâmetro e o tipo. Em seguida atribuímos um valor ao parâmetro, e finalmente o adicionamos a coleção de parâmetros do comando.

O objeto parametro não precisa ter o mesmo nome do campo da tabela

Se você estiver usando outro provider, como o OleDb, os parametros devem ser identificados por “?”. Os valores devem ser fornecidos na ordem em que eles estão no SQL, pois não há como nomealos

C# 2008 - Módulo II 8

Treinar – Cursos e Treinamentos 8

SQLDataAdapter , DataSet e DataTable

A unica função do SQLDataAdapter é fazer a ligação entre a fonte de dados e o DataSet. Um DataAdapter, assim como um SQLDataSource, pode receber até quatro instruções SQL: Para consulta, inclusão, exlcusão e alteração.

Existem quatro sobrecargas do contrutor de um DataAdapter. Se você não quizer instanciar objetos connection e command, pode utilizar uma sobrecarga que recebe o comando sql de seleção e a string de conexão, que estes objetos são criados implicitamente.

O preenchimento de um DataSet por um DataAdapter é feito através do método fill:

SqlDataAdapter Adp = new SqlDataAdapter(

"SELECT * FROM Person.Contact", Conexao);

DataSet Ds = new DataSet();

Adp.Fill(Ds);

GridView1.DataSource = Ds;

A atualização dos dados é feita através do método update. Apenas quando o método é executado que qualquer alteração nos dados serão replicados ao banco de dados.

Outro aspecto importante é que o para fazer uma atualização, seja exclusão, inclusão ou alteração, o Adapter precisa obter os comando SQL para tais operações. Através da classe CommadBuilder podemos gerar automaticamente estas instruções para o SQLDataAdapter.

SqlDataAdapter Adp = new SqlDataAdapter

("SELECT * FROM Person.Contact ", Conexao);

SqlCommandBuilder CmdBuilder = new SqlCommandBuilder;

DataSet Ds = new DataSet();

Ds.Tables[0].Rows[0]["FistName"] = "Frederico";

Adp.Update(Ds);

Como o DataSet é um pequeno banco de dados em memória, podemos alterar várias tabelas, inclusive de fontes diferentes.

SqlDataAdapter Adp1 = new SqlDataAdapter

("SELECT * FROM Person.Contact ", Conexao);

DataTable Dt1 = new DataTable();

Adp1.Fill(Dt1);

SqlDataAdapter Adp2 = new SqlDataAdapter

("SELECT * FROM Person.ContactType ", Conexao);

DataTable Dt2 = new DataTable();

Adp2.Fill(Dt2);

DataSet Ds = new DataSet();

Ds.Tables.Add(Dt1);

Ds.Tables.Add(Dt2);

Neste exemplo adicionamos uma linha a uma tabela. Primeiramente preechemos a tabela através de um DataAdapter. Em seguida instanciamos um objeto DataRow a partir da tabela, ou seja, com sua definição de dados, preenchemos os valores dos campos, adicionamos a coleção de dados do DataTable e finalmente invocamos o método Update do Adapter para atualizar a fonte de dados:

SqlDataAdapter Adp = new SqlDataAdapter

("SELECT * FROM Sales.Currency", Conexao);

C# 2008 - Módulo II 9

Treinar – Cursos e Treinamentos 9

SqlCommandBuilder CmdBuilder = new SqlCommandBuilder(Adp);

DataTable Dt = new DataTable ();

Adp.Fill(Dt);

DataRow Dr = Dt.NewRow();

Dr["CurrencyCode"] = "BRU";

Dr["Name"] = "Brazilian UNReal";

Dr["ModifiedDate"] = "01/01/2006";

Dt.Rows.Add(Dr);

Adp.Update(Dt);

SQLTransaction

O ADO.NET também possui classes que dão suporte a controle de transações. Devemos declarar uma transação e iniciá-la pelo objeto de conexão e ainda atribuí-la aos objetos que farão parte da operação. Para confirmar as operações chamamos o método commit do objeto Transaction, para desfazer o método é rollback.

SqlTransaction Trans = Conexao.BeginTransaction();

SqlDataAdapter Adp = new SqlDataAdapter

("SELECT * FROM Sales.Currency ", Conexao);

Adp.InsertCommand.Transaction = Trans;

SqlCommandBuilder CmdBuilder = new SqlCommandBuilder(Adp);

DataTable Dt = new DataTable();

Adp.Fill(Dt);

DataRow Dr = Dt.NewRow();

Dr["CurrencyCode"] = "BRU";

Dr["Name"] = "Brazilian UNReal";

Dr["ModifiedDate"] = "01/01/2006";

Dt.Rows.Add(Dr);

try {

Adp.Update(Dt);

Trans.Commit();

}

catch (Exception s)

{

Trans.Rollback();

}

C# 2008 - Módulo II 10

Treinar – Cursos e Treinamentos 10

Generics Generics é um novo conceito introduzido na versão 2.0 do .NET Framework, como parte integrante do CLS (Common Language Specification). Generics permitem termos classes, métodos, propriedades, delegates e Interfaces que trabalhem com um tipo não especificado. Esse tipo "não especificado" quer dizer que estes membros trabalharão com os tipos que você especificar em sua construção.

Como já sabemos, o ArrayList permite adicionarmos qualquer tipo dentro dele. Mas e se quiséssemos apenas adicionar valores inteiros, ou somente strings? Isso não seria possível pois o método Add aceita um System.Object. Com Generics, é possível criar coleções de um determinado tipo, o que permitirá que o usuário somente adicione objetos do mesmo tipo e, se por acaso ele quiser adicionar algo incompatível, o erro já é detectado em design-time. Classes genéricas oferecem várias vantagens, entre elas:

Reusabilidade: Um simples tipo genérico pode ser utilizado em diversos cenários. Um exemplo é uma classe que fornece um método para somar dois números. Só que estes números podem ser do tipo Integer, Double ou Decimal. Com o Generics, não precisaríamos de overloads do método Somar(...). Bastaria criar um único método com parâmetros genéricos e a especificação do tipo a ser somado fica a cargo do consumidor.

Type-safety: Generics fornecem uma melhor segurança, mais especificamente em coleções. Quando criamos uma coleção genérica e especificamos em seu tipo uma string, somente podemos adicionar strings, ao contrário do ArrayList.

Performance: Fornecem uma melhor performance, já que não há mais o boxing e unboxing. Além disso, o número de conversões cai drasticamente, já que tudo passa a trabalhar com um tipo especificado, o que evita transformarmos em outro tipo para termos acesso às suas propriedades, métodos e eventos.

Uma classe que aceita um tipo em sua declaração pode trabalhar internamente com este tipo. Isso quer dizer que os métodos podem aceitar em seus parâmetros objetos do tipo especificado na criação da classe, retornar esses tipos em propriedades, etc.

Para exemplificarmos, vejamos uma classe que aceita um valor genérico, o que quer dizer que ela pode trabalhar com qualquer tipo: através do exemplo abaixo "T" que identifica o tipo genérico que já pode ser acessado internamente pela classe.

public class ClasseGenerica<T>

{

private T _valor;

public T Valor

{

get

{

return this._valor;

}

set

{

this._valor = value;

}

}

C# 2008 - Módulo II 11

Treinar – Cursos e Treinamentos 11

}

//Utilização:

ClasseGenerica<string> classe1 = new ClasseGenerica<string>();

classe1.Valor = ".NET";

ClasseGenerica<int> classe2 = new ClasseGenerica<int>();

classe2.Valor = 123;

O que diferencia uma classe normal de uma classe genérica é o tipo que devemos especificar durante a criação da mesma. O valor "T" pode ser substituído por qualquer palavra que você achar mais conveniente para a situação e, quando quiser referenciar o tipo genérico em qualquer parte da classe, poderá acessá-lo como um tipo qualquer, como um Integer e felizmente, o Intellisense dá suporte completo a Generics. Digamos que sua classe somente trabalhará com Streams, então poderia definir "T" como "TStream" (se assim desejar):

public class ClasseGenerica<TStream>

{

//....

}

Como podemos notar no exemplo, a classe1 trabalha somente com valores do tipo string. Já a classe2 somente trabalha com valores do tipo inteiro. Mesmo que quiser adicionar um tipo incompatível, o erro já é informado em design-time. Mas os Generics não param por aqui. Existem muitas outras possibilidades para tornarmos os Generics ainda mais poderosos, como por exemplo critérios (constraints), criação de métodos genéricos, delegates, Interfaces, entre outros.

Nullable Types

Qualquer tipo-valor em .NET possui sempre um valor padrão. Isso quer dizer que ele nunca poderá ter um valor nulo. Um exemplo é a estrutura DateTime. Ela tem um valor padrão que é 01/01/0001 00:00:00. Apesar de estranha, é uma data válida.

Muitas vezes podemos ter uma data não informada, como por exemplo um objeto Funcionario que tem uma propriedade chamada DataDemissao. Se este funcionário não foi demitido, como conseguiremos distinguir se essa data é válida ou não?

System.Nullable<T> é uma estrutura de dados genérica que aceita como tipo qualquer outro tipo, desde que esse tipo seja outra estrutura (tipo-valor), como por exemplo: Int32, Double, DateTime, etc.. Através deste tipo especial podemos definir valores nulos para ele, como por exemplo:

Nullable<DateTime> dataDemissao = null;

A estrutura genérica Nullable<T> fornece também uma propriedade chamada HasValue do tipo booleana que retorna um valor indicando se existe ou não um valor definido. E note que a estrutura Nullable<T> trata de uma estrutura genérica, onde o tipo a ser definido obrigatoriamente deve também ser uma estrutura, devido à constraint que obriga isso. Esses tipos são ideais para utilizá-los junto com registros retornados de uma base de dados qualquer. Apesar de não ter uma grande integração, ajuda imensamente para conseguirmos mapear as colunas do resultset para as propriedades dos objetos da aplicação que permitem valores nulos.

C# 2008 - Módulo II 12

Treinar – Cursos e Treinamentos 12

Métodos Anônimos

Os métodos anônimos nos permitem omitir o método que será executado quando o delegate for invocado. Depois de compilado, o compilador se encarrega de criar o método e, se preocupa em definir todas as referências para invocá-lo corretamente.

Isso evitará a criação de um método exclusivo para a realização desta tarefa. Se o método for utilizado somente naquele local, então podemos omitir a criação do método através da seguinte sintaxe:

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

users.ForEach(new Action<Usuario>(delegate(Usuario user)

{

Console.WriteLine(user.Nome);

}));

}

}

Actions

Action trata-se de um delegate que foi introduzido dentro da versão 2.0 do .NET Framework que representa um método que executa uma ação em um objeto específico. Trata-se de um delegate genérico que não possui nenhum retorno. Esse delegate e os delegates que veremos a seguir são comumente utilizandos em conjunto com arrays, evitando a necessidade da criação de um método exclusivo para iterar pela respectiva coleção efetuando alguma tarefa para cada um dos seus itens que estão contidos dentro dela. Se analisarmos a classe Array veremos vários métodos que recebem como parâmetros delegates do tipo Actions, Predicates, Comparison e Converter.

Para exemplificar, utilizamos o método ForEach da classe List que aceita como parâmetro um delegate do tipo Action que deve referenciar o método que será executado para cada um dos itens da coleção. É bom lembrar que o delegate Action trata-se de um delegate genérico e o tipo a especificar neste cenário deve, obrigatoriamente, ser o mesmo tipo que foi especificado na criação do objeto List. Como exemplo criarei também uma classe chamada Usuario que será utilizada pelos exemplos durante esta seção:

public class Usuario

{

private string _nome;

public Usuario(string nome)

{

this._nome = nome;

}

C# 2008 - Módulo II 13

Treinar – Cursos e Treinamentos 13

public string Nome

{

get

{

return this._nome;

}

set

{

this._nome = value;

}

}

}

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

users.ForEach(new Action<Usuario>(Write));

}

private static void Write(Usuario user)

{

Console.WriteLine(user.Nome);

}

}

Predicates Os Predicates trabalham de forma semelhante ao Action, com a exceção de que ao invés de ser um void ele retorna um valor booleano. O Predicate representa um método que define alguns critérios, determinando se um objeto atende ou não a estes critérios estabelecidos pelo Predicate. Através do exemplo abaixo, utilizamos o método Find da classe List que, dado um Predicate, ele analisa se o usuário está ou não contido dentro da listagem e, se estiver, a instância deste usuário será atribuída a variável busca.

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

Usuario busca = users.Find(new Predicate<Usuario>(Search));

if (busca != null)

Console.WriteLine(busca.Nome);

}

private static bool Search(Usuario user)

C# 2008 - Módulo II 14

Treinar – Cursos e Treinamentos 14

{

return user.Nome == "Adilson";

}

}

Sintaxe in-line:

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

Usuario busca = users.Find(new Predicate<Usuario>

(delegate(Usuario user)

{

return user.Nome == "Adilson";

}));

if (busca != null)

Console.WriteLine(busca.Nome);

}

}

Comparison Este delegate representa um método que é utilizado para comparar dois tipos. Ele é geralmente utilizado em conjunto com o método Sort da classe fornecida pelo array, que permite ordenar uma coleção de forma ascendente ou descendente. É importante lembrar que o método Sort não retorna nenhum valor, é apenas um void que manipula os itens internos ao array. Através do exemplo abaixo, utilizamos um Comparer padrão para ordenar os usuários que estão contidos dentro da coleção:

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

users.Sort(new Comparison<Usuario>(Sort));

users.ForEach(new Action<Usuario>(Write));

}

private static int Sort(Usuario u1, Usuario u2)

{

return Comparer<string>.Default.Compare(u1.Nome, u2.Nome);

}

private static void Write(Usuario user)

{

C# 2008 - Módulo II 15

Treinar – Cursos e Treinamentos 15

Console.WriteLine(user.Nome);

}

}

Sintaxe in-line:

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

users.Sort(new Comparison<Usuario>(

delegate(Usuario u1, Usuario u2)

{

return Comparer<string>.Default.Compare(

u1.Nome, u2.Nome);

}));

users.ForEach(new Action<Usuario>(delegate(Usuario u)

{

Console.WriteLine(u.Nome);

}));

}

}

Converter

A classe List possui um método chamado ConvertAll que recebe como parâmetro um delegate do tipo Converter. Este é um delegate genérico que devemos especificar qual será o tipo de dado de entrada e de saída em que o método ConvertAll deverá converter cada um dos elementos contidos dentro da coleção. Esse método retornará um novo objeto List onde cada um dos elementos desta coleção será do tipo especificado na criação do delegate Converter. O exemplo abaixo ilustra esse processo, onde especificamos que a coleção de objetos Usuarios será convertida para uma coleção de strings, contendo apenas o nome de cada um deles.

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

List<string> nomes = users.ConvertAll<string>(

new Converter<Usuario, string>(Conversao));

nomes.ForEach(new Action<string>(Write));

}

C# 2008 - Módulo II 16

Treinar – Cursos e Treinamentos 16

private static string Conversao(Usuario user)

{

return user.Nome;

}

private static void Write(string nome)

{

Console.WriteLine(nome);

}

}

Sintaxe in-line:

class Program

{

static void Main(string[] args)

{

List<Usuario> users = new List<Usuario>();

users.Add(new Usuario("Adilson"));

users.Add(new Usuario("Rubens"));

users.Add(new Usuario("Sheila "));

users.Add(new Usuario("Silvia"));

List<string> nomes = users.ConvertAll<string>(

new Converter<Usuario, string>(delegate(Usuario user)

{

return user.Nome;

}));

nomes.ForEach(new Action<string>(delegate(string nome)

{

Console.WriteLine(nome);

}));

}

}

Ao compilar a aplicação, os métodos são explicitamente criados dentro do Assembly e são automaticamente vinculados a algum dos delegates que vimos acima.

Inference Types

As inferências de tipo ou tipos implícitos são uma exclusividade da versão 3.5 do .NET Framework. Esta funcionalidade permite ocultarmos a declaração do tipo da variável durante a escrita do código. Mas não pense que isso irá tornar a variável do tipo System.Object; ao invés disso, o tipo que a variável terá será definido a partir do valor com o qual você a inicializa. Sendo assim, podemos passar a declarar as variáveis da seguinte forma:

var id = 123;

var nomes = new string[] {"Israel", "Claudia", "Juliano"};

var valor = 12.0;

Ao declarar as variáveis desta forma, em design-time você já terá suporte aos membros do tipo que você inicializou a mesma. Para comprovar que o tipo da variável irá depender exclusivamente do valor a ela definida, podemos visualizar o código compilado, que será algo mais ou menos como o que é mostrado logo abaixo:

C# 2008 - Módulo II 17

Treinar – Cursos e Treinamentos 17

int id = 123;

string[] nomes = new string[] {"Israel", "Claudia", "Juliano"};

double valor = 12.0;

Entretanto, esse tipo de declaração possui algumas restrições:

Toda variável deve ser inicializada;

O inicializador não pode ser um inicializador de objeto ou de coleção, mas pode ser uma nova expressão que inclui um inicializador de objeto ou coleção;

Não é possível inicializar a variável com um tipo nulo ou com ela mesma;

Não é possível incluir múltiplas variáveis na mesma declaração.

Além de utilizar a inferência de tipos na declaração das variáveis, podemos fazer o mesmo na inicialização de um laço for ou até mesmo dentro do um bloco using. No primeiro caso, dentro do laço For, a variável é automaticamente inicializada com o mesmo tipo dos elementos da coleção; já no caso do bloco using, a variável terá o mesmo tipo da instância que é a ela atribuída. Só é importante lembrar que, neste caso, o objeto deve obrigatoriamente implementar a Interface IDisposable. O código abaixo ilustra esses dois casos:

//Utilização no For

var nomes = new string[] { "Israel", "Claudia", "Juliano" };

foreach (var nome in nomes)

Console.WriteLine(nome);

//Utilização no bloco using

using (var t = new Teste())

{

//...

}

public class Teste : IDisposable

{

//Implementação...

}

Object Initializers

Os inicializadores de objetos consiste em uma seqüência de membros inicializada, que devem estar entre { } (chaves), onde cada uma das propriedades são separadas por vírgulas. Para que cada propriedade seja inicializada você deve especificar o nome da mesma e, depois do sinal de igual, definir o seu respectivo valor. É importante dizer que você pode inicializar os membros independente de qual tipo ele seja. O exemplo abaixo ilustra como devemos proceder para utilizar esse recurso:

public class Usuario

{

public int Id;

public string Nome;

}

//Utilização

Usuario user = new Usuario() { Id = 1, Nome = "Israel" };

C# 2008 - Módulo II 18

Treinar – Cursos e Treinamentos 18

Os inicializadores de objetos não tem nenhuma relação com o construtor da classe, ou seja, eles tem funcionalidades diferentes pois, os inicializadores são úteis para definirmos valores para as propriedades públicas no momento da criação do objeto.

Anonymous Types

Os tipos anônimos nos permitem criar um tipo, sem explicitamente criar uma classe formal para o mesmo. Esse recurso é extremamente importante na utilização do LINQ, O código abaixo mostra a sintaxe de como podemos criar um objeto com duas propriedades e, logo após a sua criação, já podemos acessar as propriedades recém definidas para o tipo anônimo:

var u = new { Nome = "Israel", Idade = 25 };

Console.WriteLine(u.Nome);

Ao compilar o projeto, automaticamente uma classe com as respectivas propriedades é criada. Os tipos anônimos é uma facilidade que o .Net Framework oferece para diminuir o trabalho. Não precisamos perder tempo para criar uma "classe temporária".

Extension Methods

Os extension methods são métodos estáticos que nos permitem “incluir” estes métodos a tipos pré-existentes e que podem ser invocados a partir das instâncias desses tipos. Para exemplificar, podemos adicionar um método rotineiro ao tipo string e assim utilizarmos por toda a aplicação.

A declaração destes métodos deve ser dentro de uma classe estática, sem nenhum atributo definido. O que vai definir explicitamente o tipo onde este método será "incluído" é o primeiro parâmetro deste método que está criando. Os parâmetros que vem a seguir são utilizados para informar um parâmetro qualquer que é necessário para que este extension method possa realizar o seu trabalho. O código abaixo ilustra como criá-los:

public static class Helper

{

public static string RetStrFmt(this string s, string label)

{

return label + ": " + s;

}

public static string RetIntFmt(this int i, string label)

{

return label + ": " + i.ToString();

}

}

//Utilização

string nome = "Fred";

nome.RetStrFmt("Nome");

int Val = 3;

Val.RetIntFmt("Valor");

O primeiro parâmetro (que especifica o tipo em que o método será "incluído") deve estar

C# 2008 - Módulo II 19

Treinar – Cursos e Treinamentos 19

pré-fixado com a keyword this. Como podemos notar no exemplo acima, ao definir o extension method você já o tem disponível no Intellisense, com um ícone diferente em relação ao ícone padrão de um método qualquer do tipo.

Implicitly Typed Arrays

A criação implícita de arrays fortemente tipados também é uma exclusividade dessas novas versões das linguagens. Neste caso, o tipo de cada elemento é automaticamente deduzido de acordo com o tipo dos elementos onde, para isso, é necessário que no momento da inicialização do array, todos os elementos sejam, obrigatoriamente, do mesmo tipo ou que possam ser implicitamente convertidos. O exemplo abaixo ilustra como devemos proceder para criar este tipo de array:

var ids = new[] { 1.2, 2, 3, 4, 5 };

var nomes = new[] { "Israel", "Claudia", "Juliano", "Decio", "Leandro" };

Podemos combinar essa funcionalidade com algumas das funcionalidades acima, que são os tipos anônimos e os inicializadores de objetos e tornar o código um pouco mais performático. Em um primeiro momento, talvez um pouco estranho, mas com a utilização em nosso dia a dia veremos a produtividade e eficácia que isso nos proporciona. O exemplo abaixo cria um array onde em cada um dos elementos são adicionados um tipo anônimo:

var pessoas = new[]{

new {

Nome = "Israel",

Idade = 25

},

new {

Nome = "Claudia",

Idade = 23

}

};

Lambda Expressions

Na versão 2.0 do Visual C# a Microsoft introduziu um conceito chamado métodos anônimos. Os métodos anônimos permitem escrevermos uma "expressão" de forma "in-line" em locais onde uma instância de um delegate é esperada. Apesar de citarmos alguns exemplos acima, vamos analisar um exemplo de método anônimo. Supondo que temos um delegate que recebe dois números inteiros e retorna um valor, também inteiro, temos as seguintes formas de proceder para executar o delegate nas versões .NET 1.x e 2.0 do .NET:

delegate int Operacao(int a, int b);

//Utilizando a versão 1.x

static void Main(string[] args)

{

Operacao op = new Operacao(Soma);

Console.WriteLine(op(2, 4));

C# 2008 - Módulo II 20

Treinar – Cursos e Treinamentos 20

}

public static int Soma(int a, int b)

{

return (a + b) * 3;

}

//Utilizando a versão 2.0

static void Main(string[] args)

{

Operacao op = new Operacao(delegate(int a, int b)

{

return (a + b) * 3;

});

Console.WriteLine(op(2, 4));

}

Como podemos notar, com a versão 2.0 do Visual C#, o método para qual aponta o delegate não existe mais. Muitas vezes, criávamos um método exclusivamente para utilizar em conjunto com o delegate, poluindo a classe.

Já as Lambda Expressions tornam os métodos anônimos muito mais concisos mas, pode parecer confuso em um primeiro momento. Essa é uma das principais funcionalidades que o LINQ utiliza em suas query expressions, fornecendo uma forma compacta e fortemente tipada de escrever funções, passar seus respectivos argumentos e, efetivamente, a sua avaliação (implementação).

Para exemplificar, vamos analisar como fica o código acima utilizando as Lambda Expressions:

delegate int Operacao(int a, int b);

static void Main(string[] args)

{

Operacao op = (a, b) => (a + b) * 3;

Console.WriteLine(op(2, 4));

}

Como podemos ver, ao invés de atribuirmos a instância propriamente dita do delegate Operacao para a variável op especificamos uma Lambda Expression. Da esquerda para a direita, (a, b) é a lista de parâmetros que foram especificados pelo delegate e, como já devem perceber, os tipos são inferidos, ou seja, na ordem em que eles aparecem na assinatura do delegate é que os seus respectivos tipos são atribuídos; em seguida temos o símbolo => que aparece sempre e, finalmente, uma expressão ou um bloco que será executado quando a expressão for invocada. É importante dizer que o nome dos parâmetros são completamente irrelevantes assim como qualquer método ou evento dentro da plataforma .NET. Como exemplo eu nomeei de a e b, mas poderia ser numero1 e numero2 ou algo de sua preferência.

O fato de podermos utilizar as Lambda Expressions impõe algumas regras que devem ser seguidas. Algumas dessas regras são exibidas através da listagem abaixo:

Parênteses: você pode omitir parênteses da lista de argumento somente quando existe apenas um único argumento (salvo quando quiser explicitamente definir o tipo). Quando o delegate possuir mais que um argumento ou quando o delegate for do tipo void, os parênteses devem ser especificados e, no caso do void, você

C# 2008 - Módulo II 21

Treinar – Cursos e Treinamentos 21

deve definir um par de parênteses vazio.

Múltiplas linhas: é possível criar uma Lambda Expression que possui mais de uma linha. Para isso é necessário que você a envolva entre chanves { }.

Através dos códigos abaixo conseguimos ter uma idéia do poder das Lambda Expressions e, ao longo de sua utilização, a sintaxe fica amigável e rapidamente você já estará confortável em ler e entender o código.

delegate int Operacao(int a, int b);

delegate void WriteText(string text);

delegate void Teste();

static void Main(string[] args)

{

Operacao op1 = (int a, int b) => a + b;

Operacao op2 = (a, b) => a + b;

WriteText wt1 = s => Console.WriteLine(s);

WriteText wt2 = s =>

{

if (!string.IsNullOrEmpty(s))

Console.WriteLine(s);

else

Console.WriteLine("Valor não definido.");

};

Teste t1 = () => Console.WriteLine("Teste");

Console.WriteLine(op1(2, 3));

Console.WriteLine(op2(3, 9));

wt1("Adilson - 1");

wt2("Silvia - 2");

wt2(string.Empty);

t1();

}

C# 2008 - Módulo II 22

Treinar – Cursos e Treinamentos 22

LINQ

LINQ (Language Integrated Query) é uma nova forma de realizar pesquisas em conjuntos de dados, sejam relacionais ou não. A idéia é utilizar uma sintaxe muito semelhante à sintaxe SQL para realizar pesquisas em bloco de informações, filtrando os dados quando necessário através da associação de operadores.

Além da possibilidade de execução de querys em blocos de informação, objetos e bancos de dados, uma extensão denominada XLinq foi criada para uso em objectos XML. A facilidade de uso, permite que instruções XPath e XQuery sejam executadas de maneira semelhante às instruções SQL.

Usando LINQ em Collections

Crie um novo projeto Windows Forms e adicione um botão com o texto ordenar e um TextBox no formulário, como pode ser visto na figura abaixo:

Adicione o seguinte trecho de código no evento click do botão.

private void button1_Click(object sender, EventArgs e)

{

var numbers = new int[] { 468, 7, 1, 4, 6, 3, 5, 8, 74, 81, 138 };

var pares = from p in numbers

where (p % 2) == 0

select p;

foreach (var val in pares)

{

textBox1.Text += val + " ";

}

}

Um simples array de inteiros foi criado e alguns números foram atribuídos como valores. Note que existem números pares e ímpares. Nosso objetivo é separar os números

C# 2008 - Módulo II 23

Treinar – Cursos e Treinamentos 23

pares e mostra-los na caixa de texto.

Troque apenas a definição de IGUAL por DIFERENTE na expressão WHERE e obteremos os números ímpares:

Usando o operador ORDER BY

Podemos trabalhar com o operador ORDER BY da mesma forma que com os demais.

var numbers = new int[] { 468, 7, 1, 4, 6, 3, 5, 8, 74, 81, 138 };

var impares = from p in numbers

where (p % 2) != 0

orderby p

select p;

foreach (var val in impares)

{

textBox1.Text += val + " ";

}

Querys em Tipos Estruturados

Crie um novo projeto como o criado no exemplo anterior e chame-o de ExLinq. Adicione novamente um Button com o texto Filtrar e um textBox com a propriedade Multiline = True.

Adicione uma nova classe chamada Pessoa.cs using System;

using System.Collections.Generic;

using System.Linq;

C# 2008 - Módulo II 24

Treinar – Cursos e Treinamentos 24

using System.Text;

namespace ExLinq

{

class Pessoa

{

private string _nome = "";

private int _idade = 0;

public string Nome

{

get { return _nome; }

set { _nome = value; }

}

public int Idade

{

get { return _idade; }

set { _idade = value; }

}

}

}

Escreva o seguinte código no evento click do botão.

var pessoas = new List<Pessoa>(){

new Pessoa {Idade=34,Nome="Frederico"},

new Pessoa {Idade=25,Nome="Rubia"},

new Pessoa {Idade=5,Nome="João"},

new Pessoa {Idade=9,Nome="Nataly"}

};

var jovens = from p in pessoas

where p.Idade >= 5 && p.Idade < 18

select p;

jovens.ToList<Pessoa>().ForEach(

new Action<Pessoa>(delegate(Pessoa p)

{

textBox1.Text += p.Nome + "\r\n";

}));

Escrevendo consultas em LINQ

Crie um novo projeto Console e dê o nome ExLinqConsole.

A Origem de dados para as consultas é uma lista simples de Estudantes.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace ExLinqConsole

{

public class Estudante

{

public string Nome { get; set; }

C# 2008 - Módulo II 25

Treinar – Cursos e Treinamentos 25

public string SobreNome { get; set; }

public int Codigo { get; set; }

public List<int> Notas;

}

class Program

{

static void Main(string[] args)

{

List<Estudante> classe = new List<Estudante>()

{

new Estudante(){ Codigo=1,

Nome="Frederico",

SobreNome="Almeida",

Notas=new List<int>(){9,8,9,8}},

new Estudante(){ Codigo = 2,

Nome="Rubia",

SobreNome="Goulart",

Notas=new List<int>(){7,6,8,10}},

new Estudante(){ Codigo = 3,

Nome="João",

SobreNome="Almeida",

Notas=new List<int>(){5,6,4,7}}

};

}

}

}

Filtrando pela média:

var estudantes = from p in classe

where p.Notas.Average() > 6

select p;

estudantes.ToList<Estudante>().ForEach(

new Action<Estudante>(delegate(Estudante e)

{

Console.WriteLine(e.Nome);

}));

Console.ReadLine();

Para Ordemenar os resultados

Altere a consulta acima para a seguinte:

var estudantes = from p in classe

where p.Notas.Average() > 6

orderby p.Notas.Average() descending

select p;

C# 2008 - Módulo II 26

Treinar – Cursos e Treinamentos 26

Para criar um campo calcumado

Você pode usar a palavra-chave de let para criar um campo calculado, armazenando os resultados de uma expressão para que ele não tenha a ser calculado várias vezes.

Altere o código acima para que fique assim:

var estudantes2 = from p in classe

let media = (p.Notas[0] + p.Notas[1] +

p.Notas[2] + p.Notas[3])/4

where media > 6

orderby media descending

select p;

C# 2008 - Módulo II 27

Treinar – Cursos e Treinamentos 27

LINQ To SQL

LINQ to SQL é uma implementação específica do LINQ para o SQL Server que converte consultas escritas em C# ou Visual Basic em SQL dinâmico , provendo uma interface que permite mapear os objetos do banco de dados gerando as classes para realizar as operações usando a sintaxe LINQ; também permite realizar alterações nos objetos e atualizar o banco de dados.

Crie um novo projeto Windows Forms

Adicione uma classe LINQ TO SQL

Mova as tabelas do banco ExCadastro como feito com o DataSet

No formulário, coloque um DataGridView e escreva o seguinte código no evento Load do formulário

CadastroDataContext db = new CadastroDataContext();

this.dataGridView1.DataSource =

from clientes in db.tb_clientes

orderby clientes.nom_cliente

select clientes;

Incluindo um novo registro

Coloque um botão no formulário e escreva o seguinte código no evento click.

CadastroDataContext db = new CadastroDataContext();

tb_cliente c = new tb_cliente(){

cod_cliente = 2,

nom_cliente = "João Pedro"};

db.tb_clientes.InsertOnSubmit(c);

db.SubmitChanges();

this.dataGridView1.DataSource =

from clientes in db.tb_clientes

C# 2008 - Módulo II 28

Treinar – Cursos e Treinamentos 28

orderby clientes.nom_cliente

select clientes;

Alterando o registro

Coloque um botão no formulário e escreva o seguinte código no evento click.

CadastroDataContext db = new CadastroDataContext();

var c = (from cli in db.tb_clientes

where cli.cod_cliente = 1

select cli).Single<tb_cliente>();

c.nom_cliente = "Novo nome";

db.SubmitChanges();

this.dataGridView1.DataSource =

from clientes in db.tb_clientes

orderby clientes.nom_cliente

select clientes;

Excluindo o registro

Coloque um botão no formulário e escreva o seguinte código no evento click.

CadastroDataContext db = new CadastroDataContext();

var c = (from cli in db.tb_clientes

where cli.cod_cliente = 1

select cli).Single<tb_cliente>();

db.tb_clientes.DeleteOnSubmit(c);

db.SubmitChanges();

this.dataGridView1.DataSource =

from clientes in db.tb_clientes

orderby clientes.nom_cliente

select clientes;

C# 2008 - Módulo II 29

Treinar – Cursos e Treinamentos 29

Threading

Threads permitem aumentar consideravelmente a performance das aplicações delegando os processamentos em diversas unidades de execução, aumentando a capacidade de processamento de uma aplicação. No entanto, utilizando de forma errada, poderá piorar ao invés de melhorar, consumindo mais recursos do que o necessário, tendo um comportamento inesperado e retornando valores diferentes do esperado.

O .NET Framework fornece várias classes que podemos utilizar para criação e gerenciamento de threads, bloqueio de recursos em um ambiente multi-threading e sincronização. Vale dizer que cada tecnologia (Windows Forms, ASP.NET, WCF, etc.) tem suas peculiaridades em relação a execução dos mesmos. Essas peculiaridades definem a característica das aplicações desenvolvidas sob alguma dessas tecnologias, adotando uma melhor estratégia para a execução das mesmas, tirando o mesmo proveito possível em um ambiente multi-threading.

CPU vs. I/O Bound

Toda operação pode ser considerada CPU bound ou I/O bound.

CPU bound indica que dependem da velocidade do processador e quantidade de memória disponível para ser executada.

I/O bound são o oposto, ou seja, a CPU muitas vezes deverá aguardar o processamento de outros dispositivos (serviços, bancos de dados, acesso à sistema de arquivos, etc.) para dar execução à aplicação e, sendo assim, não importa o quanto o processador é rápido ou o quanto de memória temos disponível, já que essa operação não faz uso destes recursos.

A classe Thread

A classe Thread refere-se a uma unidade lógica de execução. Essa classe fornece diversas propriedades para que o desenvolvedor possa iniciar, suspender, pausar e controlar o status, prioridade, etc. Estas classes estão dentro do namespace System.Threading.

Ao criar uma instância da classe Thread, você obrigatoriamente precisa informar qual o método que ela deverá processar, ou melhor, qual tarefa ele deverá executar. Esse vínculo se faz através de um dos seguintes delegates:

ThreadStart: Não recebe parâmetros;

ParameterizedThreadStart. Recebe uma instância de Object como parâmetro.

using System;

using System.Threading;

C# 2008 - Módulo II 30

Treinar – Cursos e Treinamentos 30

namespace ExThread

{

class Program

{

static void Main(string[] args)

{

Thread t1 = new Thread(

new ThreadStart(IniciaProcesso));

t1.Name = "Proc 1";

Thread t2 = new Thread(

new ParameterizedThreadStart(IniciaProcesso));

t2.Name = "Proc 2";

Console.WriteLine("Iniciando");

t1.Start();

t2.Start("Teste");

Console.WriteLine("Aguardando");

Console.ReadLine();

}

public static void IniciaProcesso()

{

for (int i = 0; i < 10; i++)

{

Thread.Sleep(500);

Console.WriteLine("Thread 1 " + " " +

Thread.CurrentThread.Name + " " +

DateTime.Now.ToString("HH:mm:ss"));

}

}

public static void IniciaProcesso(object value_)

{

for (int i = 0; i < 10; i++)

{

Thread.Sleep(1000);

Console.WriteLine("Thread 2 " + value_ + " " +

Thread.CurrentThread.Name + " " +

DateTime.Now.ToString("HH:mm:ss"));

}

}

}

}

Propriedades:

Name onde podemos especificar uma string contendo o nome da Thread. Esse nome ajuda muito durante a depuração do código.

Priority recebe uma das opções fornecidas pelo enumerador ThreadPriority (Highest, AboveNormal, Normal, BelowNormal e Lowest). Os itens fornecidos por esse enumerador definem a prioridade que a Thread

C# 2008 - Módulo II 31

Treinar – Cursos e Treinamentos 31

terá durante a execução, permitindo que uma determinada Thread tenha maior prioridade em relação à outra.

IsBackground As threads podem ser de dois tipos: foreground ou background. Os dois tipos são idênticos em todos os aspectos, com exceção de um: um processo não pode ser encerrado até que threads definidos como foreground sejam finalizadas. Ao criar uma thread a partir da classe Thread, por padrão, ela será do tipo foreground. Você poderá controlar o tipo da thread a partir da propriedade IsBackground que recebe um valor booleano indicando se ela é ou não do tipo background.

ThreadState Durante a vida/execução de uma thread ela pode passar por diversos estados (rodando, suspensa, parada, etc.) e, podemos recorrer a uma propriedade de somente leitura chamada ThreadState que, por sua vez, retorna uma das opções do enumerador ThreadState contendo a estado atual em que a thread se encontra.

Métodos

Start agenda o processamento do método indicado no construtor para ser executado.

Abort Uma vez que o processo inicia você pode abortá-lo através do método Abort da instância da classe Thread.

Sleep que suspende a execução da thread corrente por N milissegundos.

Join Quando invocamos este método, a thread corrente aguardará até que esta segunda thread finalize. Alternativamente podemos informar um timeout, especificando o tempo máximo que devemos aguardar até que a mesma retorne; caso isso não aconteça, então a execução do sistema continuará normalmente, enquanto essa segunda thread ocorrerá em paralelo.

ThreadPool

A Microsoft disponibilizou um pool de threads, que basicamente é um repositório de threads que permite ao desenvolvedor ao invés de criar as threads manualmente, delegar ao pool a tarefa a ser executada que ele, por sua vez, se encarrega de criar ou reutilizar alguma thread para executar a tarefa desejada. O grande benefício de utilizar o pool é que o runtime do .NET dimensiona a quantidade de threads no pool de acordo com o workload que é realizado. Isso garantirá que ao delegar uma tarefa para o pool, muito provavelmente ele utilizará uma thread que ele já criou e, ao finalizar a tarefa, a thread não será descartada, apenas reciclada e devolvida ao pool para uso posterior.

Há dentro do .NET Framework uma classe estática chamada ThreadPool, que tem a responsabilidade de gerenciar o pool de threads. Todas as threads que o pool armazena internamente são do tipo background (a propriedade IsBackground está definida como True), o que significa que se a aplicação (processo) finalizar, não aguardará que as tarefas que estão sendo executadas finalizem.

C# 2008 - Módulo II 32

Treinar – Cursos e Treinamentos 32

Para cada processo há um ThreadPool correspondente. O ThreadPool tem inicialmente 25 worker threads e 1000 I/O threads por processador, podendo estes valores serem alterados.

GetMaxThreads - Retorna o número máximo das threads ativas no pool.

GetAvailableThreads- Retorna a diferença entre o número máximo de threads do pool e o número atual de threads ativas.

GetMinThreads - Retorna o número de threads ociosas que o pool mantém e espera de novas requisições.

SetMinThreads - Altera o valor mínimo de threads disponíveis no pool. Se você diminuir muito o número de threads ociosas, pode afetar o desempenho do sistema;

SetMaxThreads - Altera o valor máximo de threads disponíveis no pool;

QueueUserWorkItem - Enfileira um método para execução. O método será executado quando uma thread do pool estiver disponível. Geralmente você usa este método para executar um processo em outra thread. Pode ser usado da seguinte forma:

o QueueUserWorkItem(WaitCallBack):Enfileira um método para execução onde o método executa quando a thread estiver ativa.

o QueueUserWorkItem(WaitCallBack, Object) :Enfileira um método para execução e especifica um objeto contendo os dados para ser usado pelo método. O método executa quando a thread estiver disponível.

using System;

using System.Threading;

namespace ExThreadPool

{

class Program

{

static void Main(string[] args)

{

ThreadPool.QueueUserWorkItem(

new WaitCallback(IniciaProcesso));

ThreadPool.QueueUserWorkItem(

new WaitCallback(IniciaProcesso), "Teste");

Console.WriteLine("Iniciando");

Console.WriteLine("Aguardando");

Console.ReadLine();

}

public static void IniciaProcesso()

{

for (int i = 0; i < 10; i++)

{

Thread.Sleep(500);

Console.WriteLine("Thread 1 " +

C# 2008 - Módulo II 33

Treinar – Cursos e Treinamentos 33

DateTime.Now.ToString("HH:mm:ss"));

}

}

public static void IniciaProcesso(object value_)

{

for (int i = 0; i < 10; i++)

{

Thread.Sleep(1000);

Console.WriteLine("Thread 2 " + value_ +

DateTime.Now.ToString("HH:mm:ss"));

}

}

}

}

As threads criadas manualmente bem como as threads que são geridas pela classe ThreadPool, são executadas em paralelo à aplicação. Isso quer dizer que ao chamar o método Start de uma Thread ou o método QueueUserWorkItem da classe ThreadPool, o controle da execução voltará para a aplicação, não aguardando o processamento desta thread.

Fazendo chamadas thread-safe para controles Windows Forms

Se você usar multithreading para melhorar o desempenho seus aplicativos de Windows Forms, você deve ter cuidado para fazer chamadas para os controles de uma forma thread-safe.

Acessar controles Windows Forms inerentemente não é thread-safe. Se você tiver dois ou mais segmentos para manipular o estado de um Controle, é possível forçar o Controle em um estado inconsistente. Outros erros relacionados ao thread são possíveis, incluindo as condições de corrida e travamentos. É importante garantir que o Acesso aos seus controles seja feito de uma forma thread-safe.

O .NET Framework ajuda a detectar quando você está acessando os controles de uma maneira que não é thread-safe. Quando você estiver executando seu aplicativo no depurador e um segmento além daquele que criou um Controle tenta chamar esse Controle, o depurador gera um InvalidOperationException com a mensagem " Controle <control name> acessado de uma thread diferente do thread que foi criado. "

Essa exceção ocorre durante o debug e, em algumas circunstâncias, em tempo de execução. É altamente recomendável corrigir esse problema quando você encontrá-lo.

Chamadas para um Controle do Windows que não são thread-safe

A maneira para chamar um Controle Windows Forms que não é thread-safe é chamar diretamente a partir de um thread de trabalho. Quando você está depurando o aplicativo, o depurador gera um InvalidOperationException para avisá-lo sobre chamadas para seus controles que não são thread-safe.

C# 2008 - Módulo II 34

Treinar – Cursos e Treinamentos 34

private void setTextUnsafeBtn_Click(object sender, EventArgs e)

{

this.demoThread =

new Thread(new ThreadStart(this.ThreadProcUnsafe));

this.demoThread.Start();

}

private void ThreadProcUnsafe()

{

this.textBox1.Text = "Texto alterado unsafely.";

}

Chamadas thread-safe para um Controle de formulários do Windows

Para fazer uma chamada thread-safe a um Controle Windows Forms

Consulta a propriedade do Controle InvokeRequired.

Se InvokeRequired retorna true, chame Invoke com um delegate que faz a chamada real para o Controle.

Se InvokeRequired retorna false, chame o Controle diretamente.

No exemplo a seguir, essa lógica é implementada em um método chamado SetText. Um tipo de delegate chamado SetTextDelegate encapsula o método SetText. Quando o InvokeRequired do controle TextBox retorna true, o método SetText cria uma instância de SetTextDelegate e chama o método do formulário Invoke. Isso faz com que o método SetText ser chamado na thread que criou o Controle TextBox e nesta thread, a propriedade Text pode ser chamada diretamente.

private void setTextSafeBtn_Click(object sender, EventArgs e)

{

this.demoThread =

new Thread(new ThreadStart(this.ThreadProcSafe));

this.demoThread.Start();

}

// This method is executed on the worker thread and makes

// a thread-safe call on the TextBox control.

private void ThreadProcSafe()

{

this.SetText("Texto alterado safely.");

}

private void SetText(string text)

{

if (this.textBox1.InvokeRequired)

{

SetTextCallback d = new SetTextCallback(SetText);

this.Invoke(d, new object[] { text });

}

else

{

this.textBox1.Text = text;

C# 2008 - Módulo II 35

Treinar – Cursos e Treinamentos 35

}

}

Chamadas thread-safe com BackgroundWorker

A melhor maneira de implementar multithreading em seu aplicativo é usar o componente de BackgroundWorker. O componente de BackgroundWorker usa um modelo orientado a eventos de multithreading. O thread de trabalho executa o manipulador de eventos DoWork e o thread que cria os controles executa seu ProgressChanged e RunWorkerCompleted manipuladores de eventos. Tenha cuidado para não chamar qualquer um dos seus controles de seu manipulador de eventos DoWork.

No exemplo de código a seguir, não há nenhum trabalho para executar de forma assíncrona, portanto não há nenhuma implementação do manipulador de evento DoWork. TextBox propriedade do Controle Text é definida diretamente no manipulador de eventos RunWorkerCompleted.

private void setTextBackgroundWorkerBtn_Click(

object sender, EventArgs e)

{

this.backgroundWorker1.RunWorkerAsync();

}

private void backgroundWorker1_RunWorkerCompleted(

object sender, RunWorkerCompletedEventArgs e)

{

this.textBox1.Text =

"Texto alterado safely com BackgroundWorker.";

}

BackgroundWorker

Com o formulário ativo no Windows Forms Designer, arraste dois controles de Button da Toolbox para o formulário .

No seu formulário, importe os namespaces Sistema.ComponentModel e Sistema.Threading.

using System;

using System.ComponentModel;

using System.Drawing;

using System.Threading;

using System.Windows.Forms;

Na Toolbox, clique na guia de componentes e arraste o componente de BackgroundWorker para seu formulário. O componente de backgroundWorker1 aparece no Component Tray. Na janela de propriedades, clique no botão eventos e, em seguida, clique Duplo nos eventos DoWork e RunWorkerCompleted para criar manipuladores de eventos. Insira o código demorado no evento DoWork.

Extraia qualquer parâmetro necessário para a operação da propriedade Argument do parâmetro DoWorkEventArgs, e atribua o resultado do cálculo para a propriedade Result do DoWorkEventArgs. Isso é, será disponível para o manipulador de eventos

C# 2008 - Módulo II 36

Treinar – Cursos e Treinamentos 36

RunWorkerCompleted.

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

{

// Não referencie o BackgroundWorker diretamente.

BackgroundWorker bw = sender as BackgroundWorker;

// Extraia o arqumento

int arg = (int)e.Argument;

// Inicia a operação

e.Result = TimeConsumingOperation(bw, arg);

// se a Operação foi cancelada…

if (bw.CancellationPending)

{

e.Cancel = true;

}

}

Inserir código para recuperar o resultado de sua operação no manipulador de eventos RunWorkerCompleted.

private void backgroundWorker1_RunWorkerCompleted(

object sender, RunWorkerCompletedEventArgs e)

{

if (e.Cancelled)

{

O usuário cancelou a operação.

MessageBox.Show("Operation was canceled");

}

else if (e.Error != null)

{

// Houve um erro durante a operação

string msg = String.Format("Error: {0}",

e.Error.Message);

MessageBox.Show(msg);

}

else

{

// A Operação finalizou com sucesso.

string msg = String.Format("Resultado = {0}", e.Result);

MessageBox.Show(msg);

}

}

Implemente o método TimeConsumingOperation.

private int TimeConsumingOperation(

BackgroundWorker bw, int sleepPeriod )

{

int result = 0;

Random rand = new Random();

while (!bw.CancellationPending)

{

bool exit = false;

switch (rand.Next(3))

{

// Dispara uma exception.

case 0:

C# 2008 - Módulo II 37

Treinar – Cursos e Treinamentos 37

{

throw new Exception("Ocorreu erro.");

break;

}

// Pausa por um tempo determinado no sleepPeriod.

case 1:

{

Thread.Sleep(sleepPeriod);

break;

}

// Finaliza com sucesso.

case 2:

{

result = 23;

exit = true;

break;

}

default:

{

break;

}

}

if( exit )

{

break;

}

}

return result;

}

No Windows Forms Designer, dê um duplo clique no startButton para criar o evento Click. Chame o método de RunWorkerAsync .

private void startBtn_Click(object sender, EventArgs e)

{

this.backgroundWorker1.RunWorkerAsync(2000);

}

No Windows Forms Designer, dê um duplo clique no cancelButton para criar o evento Click. Chame o método de CancelAsync.

private void cancelBtn_Click(object sender, EventArgs e)

{

this.backgroundWorker1.CancelAsync();

}

Se você pressionar F5 para executar o aplicativo sob o depurador, a exceção gerada na Método TimeConsumingOperation é detectado e exibido pelo depurador. Quando você executar o aplicativo fora do depurador, O BackgroundWorker guarda a exceção em cache na propriedade Error do RunWorkerCompletedEventArgs.

C# 2008 - Módulo II 38

Treinar – Cursos e Treinamentos 38

Manipulando XML com C#

Abaixo vamos demonstrar um exemplo simples para a manipulação do XML: Primeiramente para usarmos a função temos que importar o namespace:

using System.Xml;

O próximo passo para manipularmos um documento XML é carregarmos ele em memória. Para isso podemos utilizar a classe XmlDocument.

EXEMPLO DE XML

<Usuarios>

<Usuario>

<id>10</id>

<nome>Frederico Almeida</nome>

<idade>34</idade>

<cargo>Analista de sistemas</cargo>

</Usuario>

</Usuarios>

Para carregar o Xml que está em um arquivo fisico "Artigo_XML.xml" utilizamos o método Load(arquivo.xml).

Criação do Arquivo xml string caminho = @"C:\Artigo_XML.xml";

if (!File.Exists(caminho))

{

XmlDocument doc = new XmlDocument();

XmlNode raiz = doc.CreateElement("Usuarios");

doc.AppendChild(raiz);

doc.Save(caminho);

}

Se o arquivo não existir ele cria o arquivo xml, sendo necessário a inserção de um elemento Root neste exemplo representado pelo elemento "Usuarios".

Inserir Registro:

Depois de criado precisamos muitas vezes inserir as informações no arquivo xml já existente, com isso precisamos inserir novos elementos utilizando o Método AppendChild.

XmlDocument doc = new XmlDocument();

string caminho = @"C:\Artigo_XML.xml";

doc.Load(caminho);

XmlNode linha = doc.CreateElement("Usuario");

C# 2008 - Módulo II 39

Treinar – Cursos e Treinamentos 39

XmlNode Id = doc.CreateElement("id");

XmlNode Nome = doc.CreateElement("nome");

XmlNode Idade = doc.CreateElement("idade");

XmlNode Cargo = doc.CreateElement("cargo");

Id.InnerText = "11";

Nome.InnerText = "João Pedro";

Idade.InnerText = "35";

Cargo.InnerText = "Financeiro";

linha.AppendChild(Id);

linha.AppendChild(Nome);

linha.AppendChild(Idade);

linha.AppendChild(Cargo);

doc.SelectSingleNode("/Usuarios").AppendChild(linha);

doc.Save(caminho);

Alterar Registro:

Para a alteração de um elemento do xml podemos utilizar:

XmlDocument doc = new XmlDocument();

string caminho = @"C:\Artigo_XML.xml";

doc.Load(caminho);

XmlNode no;

no = doc.SelectSingleNode(String.Format("/Usuarios/Usuario[id={0}]", 11));

no.SelectSingleNode("./cargo").InnerText = "Gerente Administrativo";

doc.Save(caminho);

Altera as informações do usuário João Pedro baseado no seu id.

Deletar Registro:

Para Deletar um elemento do xml baseado em uma condição podemos utilizar:

XmlDocument doc = new XmlDocument();

string caminho = @"C:\Artigo_XML.xml";

doc.Load(caminho);

foreach (XmlNode no in doc.DocumentElement.ChildNodes)

{

if (int.Parse(no.ChildNodes.Item(0).InnerText) == 10)

{

doc.DocumentElement.RemoveChild(no);

doc.Save(caminho);

return;

}

}

O item(0) representa a primeira coluna da tabela funcionário que está representado pelo nó "id".

C# 2008 - Módulo II 40

Treinar – Cursos e Treinamentos 40

Reflection

Reflection (ou Reflexão) é a habilidade que temos em descobrir e extrair informações de metadados de um determinado Assembly. Os metadados descrevem os campos (propriedades, membros e eventos) de um tipo juntamente com seus métodos e, durante a compilação, o compilador gerará e armazenará metadados dentro do Assembly. São os metadados que permitem uma das maiores façanhas dentro da plataforma .NET, ou seja, escrevermos um componente em Visual C# e consumí-lo em uma aplicação em Visual Basic .NET. O Reflection não permite apenas extrair informações em runtime, mas também permitirá que se carregue Assemblies, instancie classes, invoque seus métodos, etc.. Reflection é algo muito poderoso que existe e possibilita dar uma grande flexibilidade para a aplicação. O próprio .NET Framework utiliza Reflection internamente em diversos cenários, como por exemplo o Garbage Collector examina os objetos que um determinado objeto referencia para saber se o mesmo está ou não sendo utilizado. Além disso, quando serializamos um objeto, o .NET Framework utiliza Reflection para extrair todos os valores do membros internos do objeto para persistí-los. O próprio Visual Studio .NET utiliza informações extraídas via Reflection para habilitar o Intellisense e mais, quando está desenvolvendo um formalário e vai até a janela de propriedades de um determinado controle, o Visual Studio .NET extrai os membros do controle via Reflection para exibir e, conseqüentemente, alterar de acordo com a necessidade. Todas as classes que utilizaremos para Reflection estão dentro do namespace System.Reflection

AppDomains

O código gerenciado passa por um processo de verificação que deve ser executado antes de rodar. Esse processo determina se o código pode acessar endereço de memória inválidos ou executar alguma outra ação que poderia causar alguma falha no processo. O código que passa por essa verificação é chamado de type-safe e permite ao CLR fornecer um grande nível de isolamento, como um processo, só que com muito mais performance. Um AppDomain é criado para servir de container para uma aplicação gerenciada. A inicialização de um AppDomain consiste em algumas tarefas, como por exemplo, a criação da memória heap, onde todos os reference-types são alocados e de onde o lixo é coletado e a criação de um pool de threads, que pode ser utilizado por qualquer um dos tipos gerenciados que estão carregados dentro do processo.

O Windows não fornece uma forma de rodar aplicação .NET. Isso é feito a partir de uma CLR Host, que é uma aplicação responsável por carregar o CLR dentro de um processo, criando AppDomains dentro do mesmo e executando o código das aplicações que desenvolvemos dentro destes AppDomains.

O .NET Framework disponibiliza uma classe chamada AppDomain que representa e permite manipular AppDomains. Essa classe fornece vários métodos (alguns estáticos) que auxiliam desde a criação até o término de um AppDomain. Entre esses principais métodos, temos:

Método Descrição

C# 2008 - Módulo II 41

Treinar – Cursos e Treinamentos 41

CreateDomain Método estático que permite a criação de uma nova AppDomain.

CurrentDomain Retorna um objeto do tipo AppDomain representando o AppDomain da thread corrente.

DoCallback Executa um código em outra aplicação a partir de um delegate.

GetAssemblies Retorna um array de objetos do tipo Assembly, onde cada elemento foi carregado dentro do AppDomain.

IsDefaultAppDomain Valor boolano indicando se o AppDomain é o AppDomain padrão.

Load Carrega um determinado Assembly dentro do AppDomain.

Unload Descarrega um determinado AppDomain.

Assemblies

Nomenclatura dos Assemblies

A nomenclatura de um Assembly (conhecida como display name ou identidade do Assembly) consiste em 4 informações: nome (sem “.exe” ou “.dll”), versão, cultura e a public key token (que será nula se uma strong name não for definida). Para exemplificar isso, vamos analisar o Assembly System.Data.dll da versão 2.0 do .NET Framework que é responsável pelas classes de acesso a dados e também um Assembly customizado chamado ExDLL, onde não foi criado uma strong name:

System.Data, Version=2.0.0.0,

Culture=neutral, PublicKeyToken=b77a5c561934e089

ExDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

O .NET Framework fornece uma classe chamada que descreve a identidade do Assembly, chamada AssemblyName. Entre as várias propriedades que essa classe possui, podemos destacar as mais importantes: CodeBase, CultureInfo, FullName, KeyPair, Name e Version.

Carregamento manual de Assemblies

Quando referenciamos um Assembly dentro de uma aplicação, o CLR decide quando carregá-lo. Quando você chama um método, o CLR verifica o código IL para ver quais tipos estão sendo referenciados e, finalmente, carrega os Assemblies onde os tais tipos estão sendo referenciados. Se um Assembly já estiver contido dentro do AppDomain, o CLR é inteligente ao ponto de conseguir identificar e não carregará o mesmo Assembly novamente. Mas quando você quer extrair informações de metadados de um determinado Assembly, é necessário carregá-lo para dentro do seu AppDomain e,

C# 2008 - Módulo II 42

Treinar – Cursos e Treinamentos 42

depois disso, poder extrair os tipos que ele expõe. Para que isso seja possível, o namespace System.Reflection possui uma classe chamada Assembly que possui, além de seus métodos de instância, alguns métodos estáticos que são utilizados para carregar um determinado Assembly. Entre esses métodos estáticos para carregamento do Assembly temos:

Método Descrição

GetAssembly Esse método consegue extrair o Assembly em qual o tipo informado está contido.

GetCallingAssembly Retorna um objeto do tipo Assembly que representa o Assembly onde o método está sendo invocado.

GetEntryAssembly Retorna um objeto do tipo Assembly que está contido no AppDomain padrão.

GetExecutingAssembly Retorna uma instância do Assembly onde o código está sendo executado.

Load Retorna um determinado Assembly. Se o Assembly especificado conter uma strong name, o CLR então procura dentro do GAC, seguido pelo diretório base da aplicação e dos diretórios privados da mesma. E se não conter uma strong name, ele não procurará dentro do GAC e, para ambos os casos, se o Assembly não for encontrado, uma exceção do tipo System.IO.FileNotFoundException.

LoadFile Permite carregar um Assembly a partir do caminho fiísico até o mesmo, podendo carregar um Assembly de qualquer local que ele esteja, não resolvendo as dependências.

LoadFrom Carrega um Assembly baseando-se no caminho físico, resolvendo todas as suas dependências.

LoadWithPartialName Método obsoleto, ao invés dele, utilize o método Load.

ReflectionOnlyLoad Carrega um Assembly em um contexto reflection-only, ou seja, o Assembly poderá apenas ser examinado, mas não executado.

ReflectionOnlyLoadFrom Dado o caminho físico até o Assembly, o mesmo é carregado dentro do domínio do chamador, também em um contexto reflection-only.

using System;

using System.Reflection;

Assembly asb = Assembly.LoadFrom("C:\\Comp.dll");

Metadados

Os metadados são muito importantes dentro da plataforma .NET. Uma vez que um

C# 2008 - Módulo II 43

Treinar – Cursos e Treinamentos 43

assembly encontra-se carregado, é perfeitamente possível extrair informações de metadados dos tipos que estão contidos dentro Assembly e, para isso, utilizaremos várias classes que estão contidas dentro do namespace System.Reflection. A partir de agora analisaremos essas classes que estão disponíveis para a criação e manipulação de tipos, como por exemplo, invocar métodos, recuperar ou definir valores para propriedades, etc..

Antes de mais nada, precisamos entender qual a hierarquia dos objetos que estão disponíveis para a manipulação dos metadados e para invocá-los dinamicamente. A imagem abaixo exibe tal hierarquia onde, como já era de se esperar, o ancestral comum é o System.Object.

System.Reflection.MemberInfo

MemberInfo é uma classe abstrata que é base para todas as classes utilizadas para resgatar informações sobre os membros sejam eles construtores, eventos, campos, métodos ou propriedades de uma determinada classe. Basicamente essa classe fornece as funcionalidades básicas para todas as classes que dela derivarem. Os membros desta classe abstrata são:

Membro Descrição

Name Retorna uma string representando o membro.

MemberType Propriedade somente leitura que retorna um item do enumerador MemberTypes indicando qual tipo de membro ele é. Entre os itens deste

C# 2008 - Módulo II 44

Treinar – Cursos e Treinamentos 44

enumerador, temos:

All – Especifica todos os tipos

Constructor – Membro é um construtor, representado pelo tipo ConstructorInfo.

Custom – O membro é um membro customimzado.

Event – O membro é um evento, representado pelo tipo EventInfo.

Field – O membro é um campo, representado pelo tipo FieldInfo.

Method – O membro é um método, representado pelo tipo MethodInfo.

NestedType – O membro é um tipo aninhado, representado pelo tipo MemberInfo.

Property – O membro é uma propriedade, representada pelo tipo PropertyInfo.

TypeInfo – O membro é um tipo, representado pelo tipo TypeInfo.

DeclaringType Propriedade somente leitura que retorna um objeto do tipo Type indicando de qual tipo é o objeto.

ReflectedType Propriedade somente leitura que retorna um objeto do tipo Type indicando o tipo que foi utilizado para obter a instância deste membro.

GetCustomAttributes Retorna um array contendo atributos customizados que o membro contém. Cada um dos elementos é representado por um System.Object.

IsDefined Retorna um valor booleano indicando se existe ou não um determinado atributo aplicado no membro.

System.Type

Essa é uma das mais importantes classes da plataforma .NET. Essa é uma das principais classes utilizadas para você poder extrair informações de um tipo. System.Object possui um método chamado GetType que retorna o tipo da instância corrente, sendo representado por um objeto do tipo Type. Sendo assim, todo e qualquer objeto possui esse método, já que todo tipo herda direta ou indiretamente dessa classe.

O método GetType acima utilizamos quando já temos a instância do objeto. Mas e quando não a temos? Para esse caso, a classe Type fornece um método estático, também chamado de GetType que, dado um tipo (através de uma string contendo o AssemblyQualifiedName, que inclui o namespace até o nome do tipo que deve ser carregado), ele retorna o objeto Type que o representa e, conseqüentemente, conseguirá extrair as informações de metadados do mesmo. Além desse método, a classe Type ainda possui um método interessante chamado GetArrayType, que retorna um array de objetos do tipo Type, onde cada elemento representa o tipo correspondente do elemento do array. Entre todos os membros da classe Type, podemos destacar:

Membro Descrição

C# 2008 - Módulo II 45

Treinar – Cursos e Treinamentos 45

Assembly Propriedade somente leitura que retorna um objeto do tipo Assembly em que o tipo é declarado.

BaseType Propriedade somente leitura que retorna um objeto do tipo Type que representa o tipo qual o tipo corrente foi herdado.

DeclaringType Propriedade somente leitura que retorna um objeto do tipo Type que representa o tipo do membro.

IsAbstract Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não abstrato.

IsArray Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não um array.

IsByRef Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não passado por referência.

IsClass Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não uma classe.

IsEnum Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não um enumerador.

IsGenericParameter Propriedade somente leitura que retorna um valor booleano indicando se o tipo representa um type parameter de um tipo genérico ou uma definição de método genérico.

IsGenericType Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não um tipo genérico.

IsInterface Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não uma Interface.

IsNested Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não aninhado a outro tipo.

IsValueType Propriedade somente leitura que retorna um valor booleano indicando se o tipo é ou não um tipo-valor.

FindInterfaces Retorna um array de objetos do tipo Type com todas as Interfaces implementadas ou herdadas pelo tipo.

FindMembers Retorna um array de objetos do tipo MemberInfo com todos os membros de um determinado tipo.

GetConstructor Método sobrecarregado que procura por um construtor de instância público. A busca é feita baseando-se em um array de objetos do tipo Type que é passado para esse método, que procurará pelo construtor que atender exatamente esses parâmetros. Se encontrado, uma instância da classe ConstrutorInfo é retornada.

GetConstructors Retorna um array de objetos do tipo ConstructorInfo, onde cada

C# 2008 - Módulo II 46

Treinar – Cursos e Treinamentos 46

elemento representa um construtor do tipo.

GetCustomAttributes Retorna um array de objetos do tipo System.Object, onde cada elemento representa um atributo que foi aplicado ao membro.

GetDefaultMembers Procura por membros que aplicam o atributo DefaultMemberAttribute. Se encontrado, um array de elementos do tipo MemberInfo é retornado, onde cada elemento representará um membro que aplica o atributo acima especificado.

O atributo DefaultMemberAttribute define um determinado membro como sendo um membro padrão, que é invocado pelo método InvokeMember da classe Type.

GetEvent Dado uma string com o nome de um evento existente no tipo, esse método retorna um objeto do tipo EventInfo representando o evento.

GetEvents Retorna um array de objetos do tipo EventInfo, onde cada elemento representa um evento existente no tipo.

GetField Dado uma string com o nome de um campo existente no tipo, esse método retorna um objeto do tipo FieldInfo representando o campo.

GetFields Retorna um array de objetos do tipo FieldInfo, onde cada elemento representa um campo público existente no tipo.

GetInterface Dado uma string com o nome de uma Interface, ele retornará um objeto do tipo Type que represente a mesma, desde que ela esteja implementada no tipo.

GetInterfaces Retorna um array de objetos do tipo Type com todas as Interfaces implementadas ou herdadas pelo tipo.

GetMember Dado uma string com o nome de um membro existente no tipo, esse método retorna um objeto do tipo MemberInfo representando o membro.

GetMembers Retorna um array de objetos do tipo MemberInfo, onde cada elemento representa um membro (propriedades, métodos, campos e eventos) existente no tipo.

GetMethod Dado uma string com o nome de um método existente no tipo, esse método retorna um objeto do tipo MethodInfo representando o método.

GetMethods Retorna um array de objetos do tipo MethodInfo, onde cada elemento representa um método existente no tipo.

GetNestedType Dado uma string com o nome de um tipo aninhado existente no tipo, esse método retorna um objeto do tipo Type representando o tipo aninhado.

GetNestedTypes Retorna um array de objetos do tipo Tipo, onde cada elemento representa um tipo aninhado existente no tipo.

GetProperties Retorna um array de objetos do tipo PropertyInfo, onde cada elemento

C# 2008 - Módulo II 47

Treinar – Cursos e Treinamentos 47

representa uma propriedade existente no tipo.

GetProperty Dado uma string com o nome de uma propriedade existente no tipo, esse método retorna um objeto do tipo PropertyInfo representando a propriedade.

using System;

int id = 123;

Type forma1 = id.GetType();

Type forma2 = Type.GetType("System.Int32");

Console.WriteLine(forma1.FullName);

Console.WriteLine(forma2.FullName);

Cada um dos métodos retornam objetos específicos para cada um dos membros que existem dentro de um determinado objeto. A partir daqui, como já somos capazes de extrair o tipo de um objeto, iremos analisar esses objetos específicos, responsáveis por representar os membros do tipo, onde poderemos extrair informações de metadados referentes a cada um deles.

Mas para que podemos entender o grande potencial do Reflection, vamos criar uma classe customizada, que possua membros, propriedades, métodos e eventos para que possamos extrair as informações de metadados da mesma. Isso não quer dizer que não seja possível extrair as mesmas informações de uma classe de dentro do .NET Framework.

Criando uma DLL

Para criar uma DLL, devemos criar um novo projeto do tipo Class Library.

Remova a Class1 criada automaticamente e crie uma nova classe chamada cCliente.cs

C# 2008 - Módulo II 48

Treinar – Cursos e Treinamentos 48

using System;

namespace ExDLL

{

public class cCliente

{

public int X;

private int _id;

private string _nome;

public event EventHandler AlterouDados;

public cCliente() { }

public cCliente(int id, string nome)

{

this._id = id;

this._nome = nome;

}

public int Id

{

get { return _id; }

set

{

_id = value;

if (AlterouDados != null)

AlterouDados(this, EventArgs.Empty);

}

}

public string Nome

{

get { return _nome; }

set

{

_nome = value;

if (AlterouDados != null)

AlterouDados(this, EventArgs.Empty);

}

}

public string ExibeDados()

{

string msg = string.Format("{0} - {1}", Id, Nome);

return msg;

}

}

}

Criando um cliente para a DLL

Crie um projeto Windows Forms e altere o Form1 para que fique assim:

C# 2008 - Módulo II 49

Treinar – Cursos e Treinamentos 49

Para adicionar uma referência para a DLL, clique com o botão direito do mouse sobre o projeto e escolha a opção “Add Reference”.

Na janela que se abre, escolha a aba “Browse” e procure pela sua DLL.

C# 2008 - Módulo II 50

Treinar – Cursos e Treinamentos 50

No Form_Load do Form1, escreva o seguinte código:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Reflection;

namespace ExReflection

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}

ExDLL.cCliente _cli = new ExDLL.cCliente();

private void Form1_Load(object sender, EventArgs e)

{

_cli = new ExDLL.cCliente();

_cli.Id = 1;

_cli.Nome = "Frederico";

}

}

}

C# 2008 - Módulo II 51

Treinar – Cursos e Treinamentos 51

System.Reflection.FieldInfo

Essa classe representa um atributo público de um determinado tipo, fornecendo informações de metadata e atributos que possam estar aplicados ao membro. Essa classe não possui um construtor público e, instâncias da mesma, são retornadas a partir dos métodos GetField e GetFields da classe Type. No botão btnFieldInfo, escreva o seguinte código:

private void btnFieldInfo_Click(object sender, EventArgs e)

{

textBox1.Text = "";

foreach (FieldInfo fi in _tipo.GetFields())

{

textBox1.Text += string.Format("Nome: {0} - Tipo: {1}\r\n",

fi.Name, fi.FieldType);

}

}

A propriedade FieldType retorna um objeto do tipo Type representando o tipo do membro. Além destas propriedades existem algumas outras que merecem ser comentadas, como por exemplo, IsPublic, IsPrivate e IsStatic.

System.Reflection.MethodBase

A classe MethodBase é uma classe abstrata que fornece informações a respeito de métodos e construtores de um determinado tipo, atributos que são aplicados aos mesmos e métodos e propriedades para manipulação de métodos genéricos. Ela fornece também um método chamado GetParameters, que retorna uma coleção de objetos do tipo ParameterInfo, onde cada um deles representa um parâmetro que pode existir no método.

A classe ParameterInfo possui uma propriedade chamada ParameterType que retorna um objeto do tipo Type representando o tipo do parâmetro e, além dela, a classe ParameterInfo possui algumas outras propriedades úteis para extrairmos informações relacionadas a cada parâmetro e, entre elas, temos a propriedade IsOut, que retorna um valor booleano indicando se o parâmetro trata-se de um parâmetro de output.

A classe MethodBase ainda possui o método GetMethodBody. Esse método retorna um objeto do tipo MethodBody, contendo o MSIL stream, variáveis locais (declaradas dentro do método) e estruturas de exceções.

A classe MethodBase classe serve como base para as classes concretas ConstructorInfo e MethodInfo, que trazem informações customizadas para cada um dos tipos.

O primeiro deles, ConstructorInfo, trata-se de uma classe que representa um determinado construtor público de um tipo, fornecendo informações de metadados à respeito do construtor e também descobrindo os atributos que o construtor pode ter. Essa classe não possui um construtor público e, instâncias da mesma, são retornadas a partir dos métodos GetConstructor e GetConstructos da classe Type.

No botão btnMethodInfo, escreva o seguinte código:

C# 2008 - Módulo II 52

Treinar – Cursos e Treinamentos 52

private void btnMethodInfo_Click(object sender, EventArgs e)

{

textBox1.Text = "Construtores:\r\n";

foreach (ConstructorInfo ci in _tipo.GetConstructors())

{

textBox1.Text += String.Format("Construtor: {0}\r\n", ci.Name);

foreach (ParameterInfo pi in ci.GetParameters())

{

textBox1.Text += string.Format

(" Parâmetro: {0} - Tipo: {1}\r\n",

pi.Name, pi.ParameterType);

}

}

textBox1.Text = "Métodos:\r\n";

foreach (MethodInfo mi in _tipo.GetMethods())

{

textBox1.Text += String.Format("Método: {0} - Retorno: {1}\r\n",

mi.Name, mi.ReturnType);

foreach (ParameterInfo pi in mi.GetParameters())

{

textBox1.Text +=

string.Format(" Parâmetro: {0} - Tipo: {1}",

pi.Name, pi.ParameterType);

}

}

}

Como podemos ver, temos um laço For aninhado que é utilizado para recuperar os parâmetros de um determinado construtor a partir de uma instância de um objeto ConstructorInfo.

A segunda e última classe que deriva de MethodBase, a classe MethodInfo, tem um comportamento muito parecido com a classe ConstructorInfo, só que neste caso, cada objeto deste representa um método público de um tipo. A classe MethodInfo também pode receber parâmetros e ter atributos. A única exceção é que a classe MethodInfo pode ter um tipo de retorno, ou seja, uma função que retorna um valor qualquer e, para isso, a classe MethodInfo fornece uma propriedade chamado ReturnType que retorna um objeto do tipo Type representando o tipo que o método retorna. Essa classe não possui um construtor público e, instâncias da mesma, são retornadas a partir dos métodos GetMethod e GetMethods da classe Type.

System.Reflection.PropertyInfo

Essa classe representa uma propriedade pública de um determinado tipo, fornecendo informações de metadados e atributos que possam estar aplicados à propriedade. Essa classe não possui um construtor público e, instâncias da mesma, são retornadas a partir dos métodos GetProperty e GetProperties da classe Type. A classe PropertyInfo possui uma propriedade chamada PropertyType, que retorna um objeto do tipo Type representando o tipo da propriedade e ainda, possui duas propriedades chamadas CanRead e CanWrite, que retornam um valor booleano indicando se a propriedade

C# 2008 - Módulo II 53

Treinar – Cursos e Treinamentos 53

pode ser lida e escrita, respectivamente. No botão btnPropertyInfo, escreva o seguinte código:

private void btnPropertyInfo_Click(object sender, EventArgs e)

{

textBox1.Text = "";

foreach (PropertyInfo pi in _tipo.GetProperties())

{

textBox1.Text +=

string.Format("Propriedade: {0} - Tipo: {1}\r\n",

pi.Name, pi.PropertyType);

}

}

System.Reflection.EventInfo

Essa classe representa um evento público de um determinado tipo, fornecendo informações de metadata e atributos que possam estar aplicados ao evento. Essa classe não possui um construtor público e, instâncias da mesma, são retornadas a partir dos métodos GetEvent e GetEvents da classe Type. A classe EventInfo possui uma propriedade chamada EventHandlerType, que retorna um objeto do tipo Type representando o tipo do delegate utilizado para declarar o evento. No botão btnEventInfo, escreva o seguinte código:

private void btnEventInfo_Click(object sender, EventArgs e)

{

textBox1.Text = "";

foreach (EventInfo ei in _tipo.GetEvents())

{

textBox1.Text +=

string.Format("Evento: {0} - Tipo: {1}\r\n",

ei.Name, ei.EventHandlerType);

}

}

Invocando os membros em runtime

Binding é o processo de localizar a implementação de um determinado tipo e existem dois tipos: Early Binding e Late Binding. O Early Binding ocorre quando você declara uma variável já especificando um tipo ao invés de um System.Object (que é a base), fortemente tipando e já tendo conhecimento do objeto em tempo de desenvolvimento e, além disso, você terá a checagem de tipos e conversões sendo feitas em compile-time, ou seja, não precisará esperar a aplicação ser executada para detectar possíveis problemas. Isso também irá permitir que o compilador trabalhe de forma mais eficiente, fazendo otimizações antes da aplicação efetivamente ser executada.

Já o Late Binding é o processo inverso, ou seja, você somente irá conhecer o tipo em

C# 2008 - Módulo II 54

Treinar – Cursos e Treinamentos 54

runtime. O Late Binding é menos performático que o Early Binding, mas te dará uma maior flexibilidade, flexibilidade qual é necessária quando trabalhamos com Reflection para criar e instanciar os membros de um determinado tipo. É justamente esse tipo de binding que vamos utilizar aqui.

Antes de executar qualquer método ou propriedade, temos primeiramente que instanciar a classe em runtime e, para isso, existem duas formas. A primeira delas é utilizando o método de instância chamado CreateInstance da classe Assembly ou o método estático, também chamado CreateInstance, da classe Activator. A diferença entre eles é que, no caso da classe Assembly, o tipo informado será procurado dentro do Assembly que a instância da classe Assembly está referenciado; já no caso da classe Activator, recebe o nome (identidade) de um Assembly de qual ela efetivamente deverá criar a instância da classe. Internamente, o método CreateInstance da classe Assembly, invoca o método CreateInstance da classe Activator. Ambos os métodos retornam um objeto do tipo System.Object com a instância da classe especifica criada.

Ainda utilizando o exemplo da classe Cliente que construímos acima, vamos analisar como devemos procede para criar a instância da mesma em runtime. A classe Cliente está agora em um outro Assembly (uma DLL) em local físico diferente. Para fins de exemplo, a instância será criada a partir do método CreateInstance da instância da classe Assembly, qual foi criada com o retorno do método estático LoadFrom, também da classe Assembly, qual analisamos anteriormente. O código abaixo exemplifica como instanciar a classe Cliente:

using System.Reflection;

Assembly asb = Assembly.LoadFrom(@"C:\ExDLL.dll");

object cliente = asb.CreateInstance("ExDLL.Cliente",

false, BindingFlags.CreateInstance, null, null, null, null);

O método CreateInstance da classe Assembly é sobrecarregado e, em um dos seus overloads, é possível passar vários parâmetros para que o método cria a instância do objeto. Entre esses parâmetros, temos:

Parâmetro Descrição

typeName Uma string representando o tipo que deverá ser instanciado.

ignoreCase Valor booleano indicando se a busca pelo tipo será ou não case-sensitive.

bindingAttr Uma combinação de valores disponibilizados no enumerador BindingFlags que afetará na forma que a busca pelo tipo será efetuada. Entre os valores disponibilizados por esse enumerador, temos os principais deles descritos abaixo:

CreateInstance – O Reflection deverá criar uma instância do tipo especificado e chama o construtor que se enquandra com o array de System.Objects que pode ser passado para o método CreateInstance.

DeclaredOnly – Somente membros declarados no mesmo nível da hierarquia do tipo será considerado na busca por membros e tipos.

Default – Especifica o flag de no-binding.

C# 2008 - Módulo II 55

Treinar – Cursos e Treinamentos 55

ExactBinding – Os tipos dos argumentos fornecidos devem exatamente coincidir com os tipos correspondentes dos parâmetros. Uma Exception é lançada se o chamador informar um Binder.

FlattenHierarchy – Membros estáticos públicos e protegidos devem ser retornados. Membros estáticos privados não são retornados. Membros estáticos incluem campos, métodos, eventos e propriedades. Tipos aninhados não são retornados.

GetField – O valor de um campo especifíco deve ser retornado.

GetProperty – O valor de uma propriedade específica deve ser retornada.

IgnoreCase – O case-sensitive deve ser ignorado quando efetuar o binding.

IgnoreReturn – Usada em interoperabilidade com COM, ignora o valor de retorno do método.

Instance – Membros de instância são incluídos na pesquisa do membro.

InvokeMethod – Invoca o método.

NonPublic – Membros não-públicos serão incluídos na pesquisa do membro.

OptionalParamBinding – Esta opção é utilizada quando o método possui parâmetros default.

Public – Membros públicos serão incluídos na pesquisa de membros e tipos.

SetField – O valor de um membro específico deve ser definido.

SetProperty – O valor de uma propriedade específica deve ser definido.

Static – Membros estáticos serão incluídos na pesquisa do membro.

binder Um objeto que habilita o binding, coerção de tipos do argumentos, invocação dos membros e recuperar a instância de objetos MemberInfo via Reflection. Se nenhum binder for informado, um binder padrão é utilizado.

args Um array de System.Object contendo os argumentos que devem ser passados para o construtor. Se o construtor for sobrecarregado, a escolha do qual utilizar será feito a partir do número de tipo dos parâmetros informados quando invocar o objeto.

culture Uma instância da classe CultureInfo que é utilizada para a coerção de tipos. Se uma referência nula for passado para este parâmetro, a cultura da thread corrente será utilizada.

activationAttributes Um array de elementos do tipo System.Object contendo um ou mais atributos que podem ser utilizados durante a ativação do objeto.

C# 2008 - Módulo II 56

Treinar – Cursos e Treinamentos 56

Se adicionarmos um construtor na classe Cliente, então devemos passar ao parâmetro args um array com os valores para satisfazer o overload, onde é necessário estar com o mesmo número de parâmetros, ordem e tipos. Para fins de exemplo, vamos, através do código abaixo, invocar a classe Cliente passando os objetos para o construtor que aceita um inteiro e uma string. O código muda ligeiramente:

using System.Reflection;

Assembly asb = Assembly.LoadFrom(@"C:\ExDLL.dll");

object[] constrArgs = { 123, "José Torres" };

object cliente = asb.CreateInstance("ExDLL.Cliente",

false, BindingFlags.CreateInstance, null, constrArgs, null, null);

O método CreateInstance retorna um System.Object representando a instância da classe informada. Se caso o tipo não for encontrado, o método retorna um valor nulo e, sendo assim, é necessário testar essa condição para não deixar a aplicação falhar. Em seguida, depois do objeto devidamente instanciado, vamos analisar como fazer para invocar os métodos e propriedades que o objeto possui. Para que possamos invocar os tipos do objeto, é necessário extrairmos o Type do mesmo como já vimos acima e, dando seqüência ao exemplo, as próximas linhas de código ilustram como extrair o objeto Type, através do método GetType da variável cliente:

Type tipo = cliente.GetType();

Essa classe tem um método denominado ExibeDados, que retorna uma string com o Id e o Nome do cliente concatenados. Como definimos no construtor da classe os valores 123 e “José Torres”, ao invocar o método ExibeDados, esses valores deverão ser exibidos. Para que seja possível invocar o método em runtime, necessitamos utilizar o método InvokeMember da classe Type, que retorna um System.Object com o valor retornado pelo método. O exemplo abaixo ilustra como utilizá-lo:

Console.WriteLine(tipo.InvokeMember("ExibeDados",

BindingFlags.InvokeMethod, null, cliente, null));

Entre os parâmetros que esse método utiliza, temos (na ordem em que aparecem no código acima): uma string contendo o nome do construtor, método, propriedade ou campo a ser invocado (se informar uma string vazia, o membro padrão será invocado); uma combinação das opções fornecidas pelo enumerador BindingFlags (detalhado mais acima) indicando como a busca pelo membro será efetuada; binder a ser utilizado para a pesquisa do membro; a instância do objeto de onde o método será pesquisado e executado e, finalmente, um array de elementos do tipo System.Object, contendo os parâmetros necessários para ser passado para o método a ser executado.

Além do método, ainda há a possibilidade de invocarmos propriedades em runtime. Podemos além de ler as informações de cada uma delas, podemos definir os valores a elas. Para isso, utilizamos o método GetProperty da classe Type, que retorna uma instância da classe PropertyInfo, que representa a propriedade e, através dela, definimos os valores e extraimos para escrevê-los, assim como é mostrado no trecho de código abaixo:

PropertyInfo nome = tipo.GetProperty("Nome");

PropertyInfo id = tipo.GetProperty("Id");

nome.SetValue(cliente, "Mario Oliveira", null);

id.SetValue(cliente, 456, null);

C# 2008 - Módulo II 57

Treinar – Cursos e Treinamentos 57

Console.WriteLine(nome.GetValue(cliente, null));

Console.WriteLine(id.GetValue(cliente, null));

Basicamente, quando chamamos o método SetValue, passamos a instância do objeto onde as propriedades serão manipuladas; o novo valor a ser definido para a propriedade e, finalmente, um objeto do tipo System.Object, quando a propriedade se tratar de uma propriedade indexada. Já o método GetValue é quase idêntico, apenas não temos o valor a ser definido, pois como o próprio nome do método diz, ele é utilizado para ler o conteúdo da propriedade.

Finalmente, se quisermos extrair os eventos que a classe possui e vincularmos dinamicamente para que o mesmo seja disparado, podemos fazer isso através do método AddEventHandler fornecido pelo classe EventInfo. Como sabemos, a classe Cliente fornece um evento chamado AlterouDados, qual podemos utilizar para que quando um valor for alterado em uma das propriedades, esse evento seja disparado. O código abaixo ilustra como configurar para vincular dinamicamente o evento:

EventInfo ev = tipo.GetEvent("AlterouDados");

ev.AddEventHandler(cliente, new EventHandler(Teste));

//...

private void Teste(object sender, EventArgs e)

{

Console.WriteLine("Alterou...");

}

Criação dinâmica de Assemblies

Há um namespace dentro de System.Reflection chamado de System.Reflection.Emit. Dentro deste namespace existem várias classes que são utilizadas para criarmos dinamicamente um Assembly e seus respectivos tipos.

Essas classes são também conhecidas como builder classes, ou seja, para cada um dos membros que vimos anteriormente, como por exemplo, Assembly, Type, Constructor, Event, Property, etc., existem uma classe correspodente com um sufixo em seu nome, chamado XXXBuilder, indicando que é um construtor de um dos itens citados. Para a criação de um Assembly dinâmico, temos as seguintes classes:

Classe Descrição

ModuleBuilder Cria e representa um módulo.

EnumBuilder Representa um enumerador.

TypeBuilder Fornece um conjunto de rotinas que são utilizados para criar classes, podendo adicionar métodos e campos.

ConstructorBuilder Define um construtor para uma classe.

EventBuilder Define um evento para uma classe.

C# 2008 - Módulo II 58

Treinar – Cursos e Treinamentos 58

FieldBuilder Define um campo.

PropertyBuilder Define uma propriedade para uma determinada classe.

MethodBuilder Define um método para uma classe.

ParameterBuilder Define um parâmetro.

GenericTypeParameterBuilder Define um parâmetro genérico para classes e métodos.

LocalBuilder Cria uma variável dentro de um método ou construtor.

ILGenerator Gera código MSIL (Microsoft Intermediate Language).

C# 2008 - Módulo II 59

Treinar – Cursos e Treinamentos 59

Globalization

As funcionalidades de globalização estão no namespace System.Globalization.

Quando estamos falando de aplicações para múltiplos idiomas e países, temos que entender dois aspectos muito importantes que são a globalização e localização:

Globalização: O processo de globalização é a habilidade de construir aplicações/websites que são suportadas e adaptáveis para as mais diferentes culturas.

Localização: É a habilidade de localizar a aplicação para uma cultura e região específica, criando traduções para os recursos que a aplicação utiliza em seu interior. Um exemplo típico é a localização de uma aplicação/website para o português para várias regiões, como o Brasil (pt-BR) e Portugal (pt-PT).

As culturas são identificadas por um padrão universal, contendo duas partes, sendo a primeira delas dois caracteres minúsculos que identificam o idioma. Já na segunda parte, existem mais dois caracteres maiúsculos que representam o país.

Temos dois conceitos importantes que devemos levar em consideração.

Cultura corrente: é utilizada para operarmos com formatação de datas e números e, além disso, utilizada durante a escrita do código

Cultura de interface (uiculture): É utilizada pelo Resource Manager para analisar uma cultura específica e recuperar os recursos em tempo de execução.

Para definirmos cada uma dessas culturas, utilizamos as propriedades CurrentCulture e CurrentUICulture, respectivamente. Essas propriedades são estáticas e estão contidas dentro da classe Thread que, por sua vez, está dentro do namespace System.Threading.

Utilizando Culturas

Cada instância da classe CultureInfo representa uma cultura específica, contendo informações específicas da cultura que ela representa e, entre essas informações, temos o nome da cultura, sistema de escrita, calendários, como formatar datas, etc.

Membro Descrição

Calendar Propriedade somente leitura que retorna um objeto do tipo Calendar. O objeto Calendar representa as divisões do tempo, como semanas, meses e anos.

ComparerInfo Propriedade somente leitura que retorna um objeto do tipo ComparerInfo que define como comparar as strings para a cultura corrente.

CultureTypes Propriedade somente leitura que retorna uma combinação do enumerador CultureTypes, indicando à que tipo a cultura

C# 2008 - Módulo II 60

Treinar – Cursos e Treinamentos 60

corrente pertence. Entre as opções fornecidas pelo enumerador CultureTypes, temos:

AllCultures – Todas as culturas, incluindo as culturas que fazem parte do .NET Framework (neutras e específicas), culturas instaladas no Windows e as culturas customizadas, criadas pelo usuário.

FrameworkCultures – Culturas específicas e neutras que fazem parte do .NET Framework.

InstalledWin32Cultures – Todas as culturas instaladas dentro do Windows.

NeutralCultures – Culturas que estão associadas com um idioma mas não com uma região/país específico.

ReplacementCultures – Culturas customizadas pelo usuário que substituem as culturas disponibilizadas pelo .NET Framework.

SpecificCultures – Culturas que não são específicas para uma região/país.

UserCustomCulture – Culturas customizadas, criadas pelo usuário.

WindowsOnlyCultures – Somente as culturas instaladas dentro do Windows e não fazem parte do .NET Framework.

CurrentCulture Propriedade estática somente leitura que retorna um objeto do tipo CultureInfo que está sendo utilizada pela thread corrente.

CurrentUICulture Propriedade estática somente leitura que retorna um objeto do tipo CultureInfo.

DateTimeFormat Propriedade somente leitura que retorna um objeto do tipo DateTimeFormatInfo que define as formas apropriadas para exibir e formatar datas e horas para a cultura corrente.

DisplayName Propriedade somente leitura que retorna uma string com o nome da cultura no formato full, exemplo: en-US.

EnglishName Propriedade somente leitura que retorna uma string com o nome da cultura em inglês.

InstalledUICulture Propriedade estática somente leitura que retorna um objeto do tipo CultureInfo que representa a cultura instalada com o sistema operacional.

IsNeutralCulture Propriedade somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente representa uma cultura neutra.

IsReadOnly Propriedade somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente é ou não somente leitura.

Name Propriedade somente leitura que retorna uma string contendo o

C# 2008 - Módulo II 61

Treinar – Cursos e Treinamentos 61

nome da cultura corrente.

NativeName Propriedade somente leitura que retorna uma string contendo o nome da cultura corrente em seu idioma atual.

NumberFormat Propriedade somente leitura que retorna um objeto do tipo NumberFormatInfo que define as formas apropriadas para exibir e formatar números para a cultura corrente.

Parent Propriedade somente leitura que retorna um objeto do tipo CultureInfo que representa a cultura “pai” da cultura corrente.

TextInfo Propriedade somente leitura que retorna um objeto do tipo TextInfo que define a forma de escrita associada com a cultura corrente.

UseUserOverride Propriedade somente leitura que retorna um valor booleano indicando se o objeto CultureInfo corrente utiliza as opções de culturas definidas pelo usuário através das Configurações Regionais do Painel de Controle do Windows.

CreateSpecificCulture Método estático que, dado uma cultura específica, cria e retorna um objeto do tipo CultureInfo associado com a cultura informada.

GetCultureInfo Método estático que, dado uma cultura específica, retorna uma instância do objeto CultureInfo (read-only) associado com a cultura informada.

GetCultures Método estático que retorna um array de culturas, onde cada um dos elementos é representado por um objeto do tipo CultureInfo.

GetFormat Este método, através de um objeto do tipo Type, retorna uma instância de um formatador associadao com a cultura corrente.

Esse método somente aceita como parâmetro um objeto Type que representa a classe NumberFormatInfo ou a classe DateTimeFormatInfo. Do contrário, esse método retornará nulo.

O código abaixo exibe a forma de criação e a exibição de algumas das propriedades do objeto CultureInfo:

using System;

using System.Globalization;

using System.Threading;

namespace ExGlob

{

class Program

{

static void Main(string[] args)

{

CultureInfo pt = new CultureInfo("pt-BR");

CultureInfo en = CultureInfo.CreateSpecificCulture("en-US");

Show(new CultureInfo[] { pt, en });

Console.ReadLine();

C# 2008 - Módulo II 62

Treinar – Cursos e Treinamentos 62

}

static void Show(CultureInfo[] cultures)

{

Console.WriteLine(Thread.CurrentThread.CurrentCulture.Name);

foreach (CultureInfo ci in cultures)

{

Console.WriteLine("------------------------");

Console.WriteLine(

ci.DisplayName);

Console.WriteLine(

ci.DateTimeFormat.DateSeparator);

Console.WriteLine(

ci.DateTimeFormat.FirstDayOfWeek.ToString());

Console.WriteLine(

ci.NumberFormat.CurrencyDecimalSeparator);

Console.WriteLine(

new DateTime(2009, 07, 26).ToString(

ci.DateTimeFormat));

Console.WriteLine(

double.Parse("9,52").ToString(ci.NumberFormat));

}

}

}

}

DateTimeFormatInfo

A classe DateTimeFormatInfo irá auxiliar na formatação de data e hora de acordo com a cultura selecionada. Todas as propriedades que essa classe possui já refletem as informações da cultura específica quando você extrai a instância dessa classe a partir do método GetFormat ou da propriedade DateTimeFormat da classe CultureInfo.

Especificador Descrição

d Especifica um padrão para a exibição de uma data de uma forma reduzida. É um atalho para a propriedade ShortDatePattern da classe DateTimeFormatInfo.

D Especifica um padrão para a exibição de uma data de uma forma mais completa. É um atalho para a propriedade LongDatePattern da classe DateTimeFormatInfo.

f Especifica um padrão para a exibição de uma hora de uma forma reduzida. É um atalho para a propriedade ShortTimePattern da classe DateTimeFormatInfo.

F Especifica um padrão para a exibição de uma hora de uma forma mais completa. É um atalho para a propriedade LongTimePattern da classe DateTimeFormatInfo.

t Exibe uma combinação da data em seu formato completo e a hora em sua forma reduzida.

C# 2008 - Módulo II 63

Treinar – Cursos e Treinamentos 63

T Exibe uma combinação da data em seu formato completo e a hora em sua forma completa. É um atalho para a propriedade FullDateTimePattern da classe DateTimeFormatInfo.

A estrutura DateTime fornece alguns métodos que serve como wrapper para as propriedades que citamos acima e, além disso, o método ToString desta estrutura possui alguns overloads que permitem customizarmos a formatação. Para testarmos as formatações acima, vamos utilizá-la no exemplo a seguir:

DateTime hoje = DateTime.Now;

Console.WriteLine(hoje.ToString("d"));

Console.WriteLine(hoje.ToString("D"));

Console.WriteLine(hoje.ToString("f"));

Console.WriteLine(hoje.ToString("F"));

Console.WriteLine(hoje.ToString("t"));

Console.WriteLine(hoje.ToString("T"));

Console.WriteLine(hoje.ToString("dd/MM/yyyy"));

Na última linha do exemplo, utilizamos um overload do método ToString que permite passarmos uma combinação de alguns caracteres que permite-nos customizar a formatação da data/hora. No exemplo MM significa que se trata de mês com duas casas. Atente-se, pois mm (minúsculos) trata-se de minutos.

NumberFormatInfo

Essa classe também fornece alguns caracteres, com padrões pré-determinados que podemos utilizar para customizar a formatação de um determinado valor. Além disso, ela fornece também propriedades que podemos definir os símbolos, separadores decimais, etc. Através da tabela abaixo, vamos analisar os caracteres que temos disponíveis para a formatação de valores numéricos:

Caracter Descrição

c, C Formato monetário.

d, D Formato decimal.

e, E Formato científico (exponencial).

f, F Formato fixo.

g, G Formato padrão.

n, N Formato numérico.

r, R Formato roundtrip. Esse formato assegura que os números convertidos em strings terão o mesmo valor quando eles forem convertidos de volta para números.

x, X Formato hexadecimal.

C# 2008 - Módulo II 64

Treinar – Cursos e Treinamentos 64

Com o conhecimento desses caracteres, já é possível utilizá-los no overload do método ToString da estrutura Double, onde podemos determinar qual a forma que o valor contido dentro dela será exibido. Além do caracter, ainda é possível determinar a quantidade de casas decimais que será exibido, usando em conjunto com o caracter de formatação um número que irá informar quantas casas decimais deverá ter:

Double valor = 123.2723;

Console.WriteLine(valor.ToString("C2"));

Console.WriteLine(valor.ToString("N3"));

Mais uma vez, é importante dizer que quando utilizamos as formatações dessa forma, apesar de explicitamente não estarmos utilizando a classe NumberFormatInfo, ela é extraída automaticamente da thread corrente e, conseqüentemente, formatando os valores baseando-se nessas informações.

Propriedade Descrição

CurrencyDecimalDigits Propriedade que recebe um número inteiro indicando quantas casas decimais é utilizada em valores monetários.

CurrencyDecimalSeparator Propriedade que recebe uma string contendo o caracter que será utilizado como separador de casas decimais.

CurrencyGroupSeparator Propriedade que recebe uma string contendo o caracter que será utilizado como separador dos grupos de números. É utilizado entre os grupos de números.

CurrencyGroupSizes Propriedade que recebe um array de números inteiros que representam qual será o comprimento de cada grupo.

CurrentInfo Propriedade estática somente leitura que retorna um objeto do tipo NumberFormatInfo da thread atual.

NegativeSign Propriedade que recebe uma string contendo o caracter que será associado ao número quando ele for negativo.

NumberDecimalDigits Propriedade que recebe um número inteiro indicando quantas casas decimais é utilizada em números em geral.

NumberDecimalSeparator Propriedade que recebe uma string contendo o caracter que será utilizado como separador de casas decimais para números em geral.

NumberGroupSeparator Propriedade que recebe uma string contendo o caracter que será utilizado como separador dos grupos de números. É utilizado entre os grupos de números.

NumberGroupSizes Propriedade que recebe um array de números inteiros que representam qual será o comprimento de cada grupo.

PositiveSign Propriedade que recebe uma string contendo o caracter que será associado ao número quando ele for positivo.

C# 2008 - Módulo II 65

Treinar – Cursos e Treinamentos 65

Encoding

Existem vários padrões de codificação disponíveis para representar os caracteres nos mais diversos idiomas. Inicialmente o padrão ASCII, que é baseado no alfabeto inglês foi desenvolvido pela IBM. Esse padrão utiliza 7 bits mas como há idiomas que possuem vários outros caracteres, esse padrão não suporta todos os idiomas. Neste momento é introduzido o padrão Unicode, que possibilita 8 bits para os caracteres. Além de suportar os caracteres definidos pelo ASCII, ele também suporta todos os caracteres usados nos mais diversos idiomas. Atualmente, temos 3 “versões” do padrão Unicode: UTF-8, UTF-16 e UTF-32. O que diferem nestes padrões, é a quantidade de bytes utilizados para armazenar os caracteres: o primeiro utiliza 1 byte, o segundo 2 bytes e o último 4 bytes.

O .NET Framework suporta esses padrões que podemos, através de classes, utilizá-los em nossas aplicações. As classes para isso estão contidas dentro do namespace System.Text e, uma das princpais delas é a classe Encoding.

Classe Descrição

ASCIIEncoding Codifica os caracteres baseando-se no padrão ASCII. Essa classe corresponde ao code page 20127.

Para criá-la basta criar uma instância da mesma ou chamar a propriedade estática ASCII da classe Encoding que já retornará uma instância dessa classe.

UTF7Encoding Codifica os caracteres baseando-se no padrão UTF-7. Essa classe corresponde ao code page 65000.

Para criá-la basta criar uma instância da mesma ou chamar a propriedade estática UTF7 da classe Encoding que já retornará uma instância dessa classe.

UTF8Encoding Codifica os caracteres baseando-se no padrão UTF-8. Essa classe corresponde ao code page 65001.

Para criá-la basta criar uma instância da mesma ou chamar a propriedade estática UTF8 da classe Encoding que já retornará uma instância dessa classe.

UnicodeEncoding Codifica os caracteres baseando-se no padrão UTF-16. Esse padrão podem utilizar uma espécie de ordenação (little-endian e big-endian), onde cada uma delas representam um code page diferente. A primeira deles equivale ao code page 1200 e a segunda ao code page 1201.

Para criá-la basta criar uma instância da mesma ou chamar a propriedade estática Unicode da classe Encoding que já retornará uma instância dessa classe.

UTF32Encoding Codifica os caracteres baseando-se no padrão UTF-32. Esse padrão podem utilizar uma espécie de ordenação (little-endian e big-endian), onde cada uma delas representam um code page diferente. A

C# 2008 - Módulo II 66

Treinar – Cursos e Treinamentos 66

primeira deles equivale ao code page 65005 e a segunda ao code page 65006.

Para criá-la basta criar uma instância da mesma ou chamar a propriedade estática UTF32 da classe Encoding que já retornará uma instância dessa classe.

Além das propriedades estáticas que vimos acima que a classe Encoding fornece, ainda existem algumas outras propriedades e métodos importantes.

Membro Descrição

BodyName Propriedade somente leitura que retorna uma string contendo o nome do codificador corrente.

CodePage Propriedade somente leitura que retorna um número inteiro contendo o identificar do codificar corrente.

Default Propriedade estática somente leitura que retorna um objeto do tipo Encoding representando o codificador corrente do sistema.

EncodingName Propriedade somente leitura que retorna uma string contendo a descrição (em forma legível) do codificador corrente.

Convert Método estático que converte um array de bytes de um codificador para outro.

GetBytes Método que retorna um array de bytes contendo o resultado da codificação dos caracteres passado para o método.

GetDecoder Método que retorna um objeto do tipo Decoder, que é responsável por converter uma determinada seqüencia de bytes em uma seqüencia de caracteres.

GetEncoder Método que retorna um objeto do tipo Encoder, que é responsável por converter uma determinada seqüencia de caracteres em uma seqüencia de bytes.

GetPreamble Método que retorna um array de bytes que irá identificar se o codificador suporta ordenação dos bytes em um formato big-endian ou little-endian.

GetString Método que, dado um array de bytes, ele decodifica e retorna uma string contendo o seu valor legível.

Para demonstrar a utilização de duas dessas classes de codificação fornecidas pelo .NET Framework (ASCIIEncoding e UnicodeEncoding), vamos analisar o código abaixo que, dado uma mensagem, ele recupera os bytes do mesma e, em seguida, traz em seu formato original.

using System;

using System.Text;

namespace ExEncoding

{

class Program

C# 2008 - Módulo II 67

Treinar – Cursos e Treinamentos 67

{

static void Main(string[] args)

{

string msg = "Codificação - .NET Framework";

ASCIIEncoding ascii = new ASCIIEncoding();

UnicodeEncoding unicode = new UnicodeEncoding();

byte[] asciiBytes = ascii.GetBytes(msg);

byte[] unicodeBytes = unicode.GetBytes(msg);

ShowBytes(asciiBytes);

ShowBytes(unicodeBytes);

string asciiMsg = ascii.GetString(asciiBytes);

string unicodeMsg = unicode.GetString(unicodeBytes);

Console.WriteLine(Environment.NewLine);

Console.WriteLine(asciiMsg);

Console.WriteLine(unicodeMsg);

Console.ReadLine();

}

static void ShowBytes(byte[] msg)

{

Console.WriteLine(Environment.NewLine);

foreach (byte b in msg)

{

Console.Write("[{0}]", b);

}

}

}

}

C# 2008 - Módulo II 68

Treinar – Cursos e Treinamentos 68

UserControl

Para criarmos um user control, devemos criar uma classe que herde de System.Windows.Forms. UserControl. Crie um novo projeto Windows Forms e adicione um novo “controle do usuário” (UserControl)

Se seu controle não estiver em “modo de desenho” (Design Mode), alterne para este modo; você deve estar vendo algo como a figura abaixo:

Adicione um TextBox, um Label e um Button ao controle e configure-o para ficar assim:

Quando formos usar nosso controle (adicionando à um formulário) queremos que, ao alterar o tamanho do controle:

– a largura do TextBox acompanhe a mudança, aumentando ou diminuindo,

C# 2008 - Módulo II 69

Treinar – Cursos e Treinamentos 69

conforme a situação;

– o Button permaneça alinhado à direita, mantendo seu tamanho. Para atingir esse objetivo, altere as propriedades indicadas abaixo:

TextBox: Anchor = Left, Rigth, Top

Button: Anchor = Rigth, Top

Escreva o código abaixo no seu controle.

namespace ExUserControl

{

public partial class UserControl1 : UserControl

{

public delegate void SelecionouHandler(string arquivo_);

public event SelecionouHandler Selecionou;

public string Filter

{

get; set;

}

public string Label

{

get{return label1.Text;}

set { label1.Text = value; }

}

public override string Text

{

get { return textBox1.Text; }

set { textBox1.Text = value; }

}

public string InitialDir

{

get; set;

}

public bool CanType

{

get { return textBox1.ReadOnly; }

set { textBox1.ReadOnly = value; }

}

public UserControl1()

{

InitializeComponent();

Filter = "Todos os arquivos (*.*)|*.*";

Label = "Selecione o arquivo:";

}

private void button1_Click(object sender, EventArgs e)

{

OpenFileDialog openDlg = new OpenFileDialog();

openDlg.Title = Text;

openDlg.Filter = Filter;

openDlg.FilterIndex = 0;

openDlg.InitialDirectory = InitialDir;

if (openDlg.ShowDialog() == DialogResult.OK)

{

C# 2008 - Módulo II 70

Treinar – Cursos e Treinamentos 70

textBox1.Text = openDlg.FileName;

if (Selecionou != null)

Selecionou(textBox1.Text);

}

}

}

}

Compile o projeto e visualize o formulário em “modo de desenho” (Design Mode).

Na “caixa de ferramentas” (ToolBox), localize nosso controle e adicione-o ao formulário

Visualize as propriedades do controle, e veja que nossas propriedades (Filter, InitialDir & CanType) já estão lá.

Agora vamos codificar o evento Selecionou. Procure o evento na lista de eventos:

Escreva o seguinte código para o evento:

private void userControl11_Selecionou(string arquivo_)

{

MessageBox.Show(arquivo_);

}

C# 2008 - Módulo II 71

Treinar – Cursos e Treinamentos 71

WCF

O WCF unificou as várias tecnologias de programação distribuídas na plataforma Microsoft em um único modelo, baseando-se na arquitetura orientada à serviços (SOA). Essa nova API facilita consideravelmente o aprendizado e desenvolvimento, já que o WCF está totalmente desacoplado das regras de negócios que serão expostas pelo serviço.

Estrutura A estrutura de um serviço WCF não é muito complexa, pois devemos utilizar conceitos puros de programação .NET para a criação do contrato e da classe que representará o serviço. Além disso, o WCF também suporta a utilização de tipos complexos, como classes que criamos para atender uma determinada necessidade.

O primeiro passo na criação do serviço é a definição do contrato. É o contrato que determinará quais operações estarão expostas, quais informações essas operações necessitam para ser executadas e também qual será o tipo de retorno. O contrato nada mais é que uma Interface que, por sua vez, deverá possuir os métodos (apenas a sua assinatura) que serão expostos. A Interface que servirá como contrato deverá ser obrigatoriamente decorada com o atributo ServiceContractAttribute pois, caso contrário, uma exceção do tipo InvalidOperationException será disparada antes da abertura do host.

Nem sempre todos os membros expostos pela Interface devem ser expostos para o serviço e, justamente por isso, todas as operações que serão disponibilizadas devem ser decoradas com o atributo OperationContractAttribute. Vale lembrar que o WCF obriga a termos no mínimo uma operação definida com este atributo, já que não faz sentido publicar um serviço que não tenha nenhuma operação a ser executada. Caso a Interface não possua nenhuma operação definida com este atributo, uma exceção do tipo InvalidOperationException também será disparada antes da abertura do host.

using System;

using System.ServiceModel;

[ServiceContract]

public interface IContrato

{

[OperationContract]

Usuario RecuperarUsuario(string nome);

}

Neste momento dois novos atributos entram em cena: DataContractAttribute e DataMemberAttribute, ambos contidos no namespace System.Runtime.Serialization

Os data contracts são uma forma que se tem de publicar possíveis estruturas de dados que podem ser trocadas durante o envio e recebimento de uma mensagem. A utilização do atributo DataContractAttribute determina que uma classe poderá ser exposta através de um serviço WCF, e deve ser aplicado a todas as classes que estão referenciadas,

C# 2008 - Módulo II 72

Treinar – Cursos e Treinamentos 72

como parâmetro ou tipo de retorno, em um contrato (Interface). Já os tipos primitivos, como String, DateTime, Int32, não precisam disso, já que podem ser serializados diretamente.

Já o atributo DataMemberAttribute deve ser aplicado nos campos e propriedades que o tipo possui e que devem ser expostos através do serviço. Esse atributo irá controlar a visibilidade do campo ou da propriedade para os clientes que consomem o serviço, não importando o modificador de acesso (public, private, etc.) que possui. O código abaixo define a classe Usuario:

using System;

using System.Runtime.Serialization;

[DataContract] //Opcional com .NET 3.5 + SP1

public class Usuario

{

[DataMember] //Opcional com .NET 3.5 + SP1

public string Nome { get; set; }

}

A partir do Service Pack 1 do .NET Framework 3.5 esse comportamento foi mudado. Visando o suporte ao POCO (Plain Old C# Objects), a Microsoft tornou mais flexível a utilização de data contracts em serviços WCF, não obrigando às classes, propriedades e campos serem decorados com os atributos acima citados. Com isso, apenas propriedades do tipo escrita/leitura serão serializadas. A partir do momento que você decora a classe com o atributo DataContractAttribute, você também deverá especificar, via DataMemberAttribute, quais campos deverão ser serializados.

Uma vez que o contrato do serviço esteja definido e os possíveis tipos que ele expõe também estejam devidamente configurados, o próximo passo é a criação da classe que representa o serviço. Esta classe deverá implementar todos os membros expostos pela Interface que define o contrato do serviço, inclusive aqueles que não estão marcados com o atributo OperationContractAttribute, lembrando que a implementação de uma Interface em uma classe é uma imposição da linguagem, e não do WCF.

A implementação dos métodos poderá conter a própria regra de negócio, bem como pode servir de wrapper para algum outro componente ou serviço. Além disso, as classes que representam o serviço também podem configurar alguns outros recursos fornecidos pelo WCF e que estão acessíveis através de behaviors, como por exemplo: transações, sessões, segurança, etc.

using System;

public class Servico : IContrato

{

public Usuario RecuperarUsuario(string nome)

{

return new Usuario() { Nome = nome };

}

}

Por si só esta classe não trabalha, pois deverá ser consumida pelo WCF. Mas afinal, como se determina que é esta classe responsável por atender as requisições? Isso é realizado através do host, ou melhor, da classe ServiceHost. Logo no construtor desta classe, você deverá passar uma instância da classe Type, apontando para a classe que

C# 2008 - Módulo II 73

Treinar – Cursos e Treinamentos 73

representa o serviço e, obrigatoriamente, deverá implementar todos os possíveis contratos que são expostos através dos endpoints. A configuração do host para este exemplo será abordada na seção seguinte. Grande parte dos atributos que vimos nesta seção disponibilizam várias propriedades que nos permitem interagir com o serializador/deserializador da mensagem e, além disso, permitem especificarmos algumas regras que serão validadas antes da abertura do host e, caso não sejam atendidas, uma exceção será disparada.

Hosting

Uma das grandiosidades do WCF é a possibilidade de utilizar qualquer tipo de aplicação como host, ou seja, ele não tem uma dependência de algum software, como o IIS (Internet Information Services), como acontecia com os ASP.NET Web Services. O WCF pode expor serviços para serem acessados através dos mais diversos tipos de protocolos, como por exemplo: HTTP, TCP, IPC e MSMQ. O host é representado dentro do WCF pela classe ServiceHost ou uma de suas variações e, é através dela que efetuamos várias configurações, como endpoints, segurança, etc. Em seu construtor, ela espera a classe que representa o serviço, podendo ser definida através de seu tipo (classe Type) ou através de uma instância desta mesma classe anteriormente criada (Singleton). Para o exemplo utilizado neste artigo, a configuração parcial do host fica da seguinte forma:

using System;

using System.ServiceModel;

using (ServiceHost host = new ServiceHost(typeof(Servico),

new Uri[] { new Uri("net.tcp://localhost:9393") }))

{

//endpoints

host.Open();

Console.ReadLine();

}

Endpoints

Os endpoints são uma das características mais importantes de um serviço, pois é por onde toda a comunicação é realizada, pois fornece o acesso aos clientes do serviço WCF que está sendo disponibilizado. Para compor um endpoint, basicamente precisamos definir três propriedades que obrigatoriamente precisamos para poder trabalhar: address (A), binding (B) e contract (C) e, opcionalmente, definir alguns behaviors. A figura abaixo ilustra a estrutura dos endpoints e onde eles estão situados:

C# 2008 - Módulo II 74

Treinar – Cursos e Treinamentos 74

Figura 1 - Estrutura de um endpoint.

O address consiste em definir um endereço único que permitirá aos clientes saber onde o serviço está publicado. O endereço geralmente é definido através de uma instância da classe Uri. Essa classe fornece um construtor que recebe uma string, contendo o protocolo, o servidor, a porta e o endereço do serviço (usado para diferenciar entre muitos serviços no mesmo local), tendo a seguinte forma: scheme://host[:port]/[path]. Cada uma dessas configurações são representadas respectivamente pelas seguintes propriedades da classe Uri: Scheme, Host, Port e AbsolutePath.

O protocolo indica sob qual dos protocolos suportados pelo WCF o serviço será exposto. Atualmente temos os seguintes protocolos: HTTP (http://), TCP (net.tcp://), IPC (net.pipe://) e MSMQ (net.msmq://). O host refere-se à máquina onde o serviço irá ser executado, podendo inclusive referenciar o localhost. A porta permite especificarmos uma porta diferente do valor padrão e, quando omitida, ela sempre assumirá a porta padrão especificada pelo protocolo. E, finalmente, temos o path, que é utilizado quando desejamos diferenciar entre vários serviços expostos sob um mesmo protocolo, host e porta.

O binding indica como a comunicação será realizada com aquele endpoint, como por exemplo, qual transporte será utilizado (HTTP, TCP, etc), qual a codificação utilizada (Binary ou Text) para serializar a mensagem, segurança, suporte à transações, etc.

Finalmente, a última característica de um endpoint é o contrato. Como já vimos acima, o contrato é representado por uma Interface e, uma vez que ele é definido como um contrato de serviço, são esses membros que serão disponibilizados aos clientes em forma de operações, definindo os parâmetros de entrada e o tipo de retorno e o formato da mensagem (request-reply, one-way ou duplex).

Configuração do Cliente

Para que possamos fazer o uso do serviço em aplicações cliente, é necessário efetuar a referência deste serviço. A referência consiste em criar uma classe que encapsulará todo o acesso para o serviço, que também é conhecida como proxy. Este processo irá ler os metadados do serviço, criando o proxy com os mesmos membros expostos, dando a impressão ao consumidor que está chamando uma classe local mas, em tempo de execução, a requisição será encaminhada para o serviço remoto.

Podemos efetuar a criação do proxy de três formas diferentes. A primeira delas é através da referência do serviço por meio da IDE do Visual Studio .NET que, por sua vez, fornece uma opção chamada Add Service Reference que exige a referência para o serviço. A segunda opção é fazer uso do utilitário svcutil.exe que, dada uma URL até o

C# 2008 - Módulo II 75

Treinar – Cursos e Treinamentos 75

serviço, ele também é capaz de gerar a classe que representará o proxy. Ambas as opções também automatizam a criação do arquivo de configuração, que fornece de forma declarativa todas as configurações do serviço.

Ao efetuar a referência, será criada uma representação local do contrato (Interface), bem como os tipos complexos que fazem parte do serviço. Além disso, a classe que representa o proxy herda diretamente da classe abstrata e genérica ClientBase<TChannel> que fornece toda a implementação necessária para permitir aos clientes se comunicarem com o respectivo serviço. Essa classe irá configurar em tempo de execução as propriedades necessárias para efetuar a requisição, extraindo tais informações do arquivo de configuração. É a partir desta classe que começamos a criar o código cliente para efetuar a requisição:

using System;

using Client.Servico;

using (ContratoClient proxy = new ContratoClient())

{

Usuario usuario = proxy.RecuperarUsuario("Israel Aece");

Console.WriteLine(usuario.Nome);

}

A terceira e última forma que existe para efetuar a comunicação entre o cliente e o serviço é realizar toda a configuração de forma manual. Isso obriga a conhecer exatamente o binding e suas respectivas configurações, qual o endereço onde o serviço está publicado e, principalmente, a Interface do contrato e os tipos que ela expõe devem estar compartilhados entre o cliente e o serviço. Apesar deste compartilhamento ser simples de realizar, pois basta isolar os tipos em um assembly (DLL), o problema é quando o número de clientes aumenta consideravelmente, dificultando a distrubuição. De qualquer forma, abaixo consta um exemplo de como efetuar essa configuração:

using System;

using System.ServiceModel;

using (ChannelFactory<IContrato> srv =

new ChannelFactory<IContrato>(new NetTcpBinding(),

new EndpointAddress("net.tcp://localhost:9393/")))

{

Usuario u = srv.CreateChannel().RecuperarUsuario("Israel Aece");

Console.WriteLine(u.Nome);

}

Criando seu primeiro serviço com WCF

Vamos criar um serviço de calculadora, realizando algumas operações básicas, como: somar, subtrair, multiplicar e dividir. Devemos começar criando um projeto WCF escolhendo WCF Service Library entre as opções existentes.

C# 2008 - Módulo II 76

Treinar – Cursos e Treinamentos 76

O Wizard do Visual Studio cria o projeto com alguns arquivos para você.

Adicione um novo Serviço WCF.

C# 2008 - Módulo II 77

Treinar – Cursos e Treinamentos 77

É importante notar o atributo ServiceContract decorando a interface, ele é o responsável de

indicar para o runtime do WCF que toda classe que implementar esta interface estará expondo

um serviço.

O próximo passo é indicar os métodos que o serviço irá expor, feito através do atributo

OperationContract. Adicione o seguinte código na sua interface:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

namespace CalcWS

{

// NOTE: If you change the interface name "ICalc" here, you must also

update the reference to "ICalc" in Web.config.

[ServiceContract]

public interface ICalc

{

[OperationContract]

double Somar(double val1_, double val2_);

[OperationContract]

double Subtrair(double val1_, double val2_);

[OperationContract]

double Multiplicar(double val1_, double val2_);

[OperationContract]

double Dividir(double val1_, double val2_);

}

}

C# 2008 - Módulo II 78

Treinar – Cursos e Treinamentos 78

Devemos agora trabalhar no código que contém a inteligência do serviço.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Runtime.Serialization;

using System.ServiceModel;

using System.Text;

namespace CalcWS

{

// NOTE: If you change the class name "Calc" here, you must also update

the reference to "Calc" in Web.config.

public class Calc : ICalc

{

#region ICalc Members

public double Somar(double val1_, double val2_)

{

return val1_ + val2_;

}

public double Subtrair(double val1_, double val2_)

{

return val1_ - val2_;

}

public double Multiplicar(double val1_, double val2_)

{

return val1_ * val2_;

}

public double Dividir(double val1_, double val2_)

{

return val1_ / val2_;

}

#endregion

}

}

Criando o cliente

Para criarmos o cliente, precisamos adicionar um novo projeto à nossa solução.

C# 2008 - Módulo II 79

Treinar – Cursos e Treinamentos 79

Agora adicione a referência ao WCF

Aparecerá a janela onde podemos referenciar o serviço

Clique em Discover para listar os serviços da solução:

C# 2008 - Módulo II 80

Treinar – Cursos e Treinamentos 80

Agora crie um formulário para chamar o WCF Service.

Escreva o seguinte código no formulário

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

namespace WSClient

{

public partial class Form1 : Form

{

public Form1()

{

C# 2008 - Módulo II 81

Treinar – Cursos e Treinamentos 81

InitializeComponent();

}

private void button1_Click(object sender, EventArgs e)

{

CalcRef.CalcClient c = new WSClient.CalcRef.CalcClient();

textBox3.Text = c.Somar(

double.Parse(textBox1.Text),

double.Parse(textBox2.Text)).ToString();

}

private void button2_Click(object sender, EventArgs e)

{

CalcRef.CalcClient c = new WSClient.CalcRef.CalcClient();

textBox3.Text = c.Subtrair(

double.Parse(textBox1.Text),

double.Parse(textBox2.Text)).ToString();

}

private void button3_Click(object sender, EventArgs e)

{

CalcRef.CalcClient c = new WSClient.CalcRef.CalcClient();

textBox3.Text = c.Multiplicar(

double.Parse(textBox1.Text),

double.Parse(textBox2.Text)).ToString();

}

private void button4_Click(object sender, EventArgs e)

{

CalcRef.CalcClient c = new WSClient.CalcRef.CalcClient();

textBox3.Text = c.Dividir(

double.Parse(textBox1.Text),

double.Parse(textBox2.Text)).ToString();

}

}

}