5. Classes .NET de uso comum
Apresentamos aqui algumas classes da plataforma .NET frequentemente utilizadas. Antes disso, mostramos como obter informações sobre as centenas de classes disponíveis. Essa ajuda é indispensável até mesmo para desenvolvedores C# experientes. O nível de qualidade de uma documentação (fácil acesso, organização compreensível, relevância das informações, etc.) pode determinar o sucesso ou o fracasso de um ambiente de desenvolvimento.
5.1. Como procurar ajuda sobre as classes .NET
Apresentamos aqui algumas orientações para encontrar ajuda com o Visual Studio.NET
5.1.1. Ajuda/Índice
![]() |
- no [1], selecione a opção Help/Contents no menu.
- no [2], selecione a opção Visual C# Express Edition
- em [3], a árvore de ajuda sobre C#
- em [4], outra opção útil é .NET Framework, que dá acesso a todas as classes do framework .NET.
Vamos dar uma olhada nos títulos dos capítulos da ajuda do C#:
![]() |
- [1]: uma visão geral do C#
- [2]: uma série de exemplos sobre alguns aspectos do C#
- [3]: um curso de C# — poderia substituir com vantagem o presente documento…
![]() |
- [4]: para aprofundar os detalhes do C#
- [5]: útil para desenvolvedores de C++ ou Java. Ajuda a evitar algumas armadilhas.
- [6]: quando estiver procurando exemplos, pode começar por aqui.
![]() |
- [7]: o que você precisa saber para criar interfaces gráficas
- [8]: para usar melhor o IDE Visual Studio Express
![]() |
- [9]: O SQL Server Express 2005 é um SGBD de alta qualidade distribuído gratuitamente. Vamos utilizá-lo neste curso.
A documentação do C# é apenas uma parte do que o desenvolvedor precisa. A outra parte é a documentação sobre as centenas de classes do framework .NET que facilitarão seu trabalho.
![]() |
- [1]: selecione a documentação sobre o framework .NET
- [2]: a ajuda está localizada no ramo .NET Framework SDK
- [3]: o ramo .NET Framework Class Library apresenta todas as classes .NET de acordo com o namespace ao qual pertencem
- [4]: o espaço de nomes System, que foi o mais utilizado nos exemplos dos capítulos anteriores
![]() |
- [5]: no espaço de nomes System, um exemplo, neste caso a estrutura DateTime
![]() |
- [6]: a ajuda sobre a estrutura DateTime
5.1.2. Ajuda/Índice/Pesquisar
A ajuda fornecida pelo MSDN é imensa e pode ser difícil saber onde procurar. Nesse caso, pode-se usar o índice da ajuda:
![]() |
- no [1], use a opção [Help/Index] se a janela de ajuda ainda não estiver aberta; caso contrário, use [2] em uma janela de ajuda já aberta.
- em [3], especifique o domínio no qual a pesquisa deve ser realizada
- em [4], especifique o que você está procurando, neste caso, uma classe
- em [5], a resposta
Outra maneira de procurar ajuda é usar a função de pesquisa da ajuda:
![]() |
- em [1]; use a opção [Help/Search] se a janela de ajuda ainda não estiver aberta; caso contrário, use [2] em uma janela de ajuda já aberta.
- em [3], especifique o que está sendo pesquisado
- em [4], filtrar as áreas de pesquisa
![]() |
- em [5], a resposta na forma de diferentes tópicos onde o texto procurado foi encontrado.
5.2. As sequências de caracteres
5.2.1. A classe System.String
![]() | ![]() | ![]() |
A classe System.String é idêntica ao tipo string simples. Ela possui diversas propriedades e métodos. Aqui estão alguns deles:
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
É importante observar o seguinte: quando um método retorna uma sequência de caracteres, essa sequência é diferente daquela à qual o método foi aplicado. Assim, S1.Trim() retorna a string S2, e S1 e S2 são duas strings diferentes.
Uma sequência C pode ser considerada como uma matriz de caracteres. Assim,
- C[i] é o caractere i de C
- C.Length é o número de caracteres de C
Consideremos o seguinte exemplo:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
string uneChaine = "l'oiseau vole au-dessus des nuages";
affiche("uneChaine=" + uneChaine);
affiche("uneChaine.Length=" + uneChaine.Length);
affiche("chaine[10]=" + uneChaine[10]);
affiche("uneChaine.IndexOf(\"vole\")=" + uneChaine.IndexOf("vole"));
affiche("uneChaine.IndexOf(\"x\")=" + uneChaine.IndexOf("x"));
affiche("uneChaine.LastIndexOf('a')=" + uneChaine.LastIndexOf('a'));
affiche("uneChaine.LastIndexOf('x')=" + uneChaine.LastIndexOf('x'));
affiche("uneChaine.Substring(4,7)=" + uneChaine.Substring(4, 7));
affiche("uneChaine.ToUpper()=" + uneChaine.ToUpper());
affiche("uneChaine.ToLower()=" + uneChaine.ToLower());
affiche("uneChaine.Replace('a','A')=" + uneChaine.Replace('a', 'A'));
string[] champs = uneChaine.Split(null);
for (int i = 0; i < champs.Length; i++) {
affiche("champs[" + i + "]=[" + champs[i] + "]");
}//for
affiche("Join(\":\",champs)=" + System.String.Join(":", champs));
affiche("(\" abc \").Trim()=[" + " abc ".Trim() + "]");
}//Principal
public static void affiche(string msg) {
// exibe mensagem
Console.WriteLine(msg);
}//exibe
}//classe
}//namespace
A execução produz os seguintes resultados:
Consideremos um novo exemplo:
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// a linha a ser analisada
string ligne = "un:deux::trois:";
// separadores de campos
char[] séparateurs = new char[] { ':' };
// divisão
string[] champs = ligne.Split(séparateurs);
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("Champs[" + i + "]=" + champs[i]);
}
// junção
Console.WriteLine("join=[" + System.String.Join(":", champs) + "]");
}
}
}
e os resultados da execução:
O método Split da classe String permite inserir os elementos de uma sequência de caracteres em um array. A definição do método Split utilizado aqui é a seguinte:
public string[] Split(char[] separator);
tabela de caracteres. Esses caracteres representam os caracteres usados para separar os campos da sequência de caracteres. Assim, se a sequência for “champ1, champ2, champ3"”, poderá-se usar separator=new char[] {','}. Se o separador for uma sequência de espaços, utilizar-se-á separator=null. | |
matriz de cadeias de caracteres em que cada elemento da matriz é um campo da cadeia. |
O método Join é um método estático da classe String:
public static string Join(string separator, string[] value);
matriz de cadeias de caracteres | |
uma sequência de caracteres que servirá como separador de campos | |
uma sequência de caracteres formada pela concatenação dos elementos da matriz value, separados pela sequência separator. |
5.2.2. A classe System.Text.StringBuilder
![]() | ![]() | ![]() |
Anteriormente, mencionamos que os métodos da classe String aplicados a uma sequência de caracteres S1 retornavam outra sequência de caracteres S2. A classe System.Text.StringBuilder permite manipular S1 sem precisar criar uma sequência de caracteres S2. Isso melhora o desempenho, evitando a multiplicação de sequências de caracteres com vida útil muito limitada.
A classe admite diversos construtores:
| |
|
Um objeto StringBuilder trabalha com blocos de capacité caracteres para armazenar a sequência subjacente. Por padrão, capacité é igual a 16. O terceiro construtor acima permite especificar a capacidade dos blocos. O número de blocos de caracteres capacité necessários para armazenar uma sequência S é ajustado automaticamente pela classe StringBuilder. Existem construtores para definir o número máximo de caracteres em um objeto StringBuilder. Por padrão, essa capacidade máxima é 2 147 483 647.
Aqui está um exemplo que ilustra esse conceito de capacité:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str
StringBuilder str = new StringBuilder("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
for (int i = 0; i < 10; i++) {
str.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str.Length, str.Capacity);
}
// str2
StringBuilder str2 = new StringBuilder("test",10);
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
for (int i = 0; i < 10; i++) {
str2.Append("test");
Console.WriteLine("taille={0}, capacité={1}", str2.Length, str2.Capacity);
}
}
}
}
- linha 7: criação de um objeto StringBuilder com um tamanho de bloco de 16 caracteres
- linha 8: str.Length é o número atual de caracteres da cadeia str. str.Capacity é o número de caracteres que a cadeia str atual pode armazenar antes da realocação de um novo bloco.
- linha 10: str.Append(String S) permite concatenar a string S, do tipo String, à string str, do tipo StringBuilder.
- linha 14: criação de um objeto StringBuilder com capacidade de bloco de 10 caracteres
Resultado da execução:
Esses resultados mostram que a classe segue um algoritmo próprio para alocar novos blocos quando sua capacidade é insuficiente:
- linhas 4-5: aumento da capacidade em 16 caracteres
- linhas 8-9: aumento da capacidade em 32 caracteres, embora 16 fossem suficientes.
Aqui estão alguns dos métodos da classe:
| |
| |
| |
| |
|
Aqui está um exemplo:
using System.Text;
using System;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// str3
StringBuilder str3 = new StringBuilder("test");
Console.WriteLine(str3.Append("abCD").Insert(2, "xyZT").Remove(0, 2).Replace("xy", "XY"));
}
}
}
e seus resultados:
5.3. As matrizes
As tabelas derivam da classe Array:
![]() | ![]() | ![]() |
A classe Array possui diversos métodos para ordenar um array, procurar um elemento em um array, redimensionar um array, etc. Apresentamos algumas propriedades e métodos dessa classe. Quase todos são sobrecarregados, c.a.d, de modo que existem em diferentes variantes. Todo array herda deles.
Propriedades
Métodos
O programa a seguir ilustra o uso de alguns métodos da classe Array:
using System;
namespace Chap3 {
class Program {
// tipo de pesquisa
enum TypeRecherche { linéaire, dichotomique };
// método principal
static void Main(string[] args) {
// leitura dos elementos de uma tabela digitados pelo teclado
double[] éléments;
Saisie(out éléments);
// exibição da matriz não ordenada
Affiche("Tableau non trié", éléments);
// Pesquisa linear na tabela não ordenada
Recherche(éléments, TypeRecherche.linéaire);
// classificação da tabela
Array.Sort(éléments);
// exibição da tabela ordenada
Affiche("Tableau trié", éléments);
// Pesquisa dicotômica na tabela ordenada
Recherche(éléments, TypeRecherche.dichotomique);
}
// Inserção dos valores da tabela de elementos
// elementos: referência à tabela criada pelo método
static void Saisie(out double[] éléments) {
bool terminé = false;
string réponse;
bool erreur;
double élément = 0;
int i = 0;
// inicialmente, a tabela não existe
éléments = null;
// Ciclo de inserção dos elementos da tabela
while (!terminé) {
// pergunta
Console.Write("Elément (réel) " + i + " du tableau (rien pour terminer) : ");
// leitura da resposta
réponse = Console.ReadLine().Trim();
// fim da inserção se a string estiver vazia
if (réponse.Equals(""))
break;
// verificação da entrada
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.Error.WriteLine("Saisie incorrecte, recommencez");
erreur = true;
}//try-catch
// se não houver erro
if (!erreur) {
// mais um elemento na matriz
i += 1;
// redimensionamento da matriz para acomodar o novo elemento
Array.Resize(ref éléments, i);
// inserção de novo elemento
éléments[i - 1] = élément;
}
}//enquanto
}
// método genérico para exibir os elementos de uma matriz
static void Affiche<T>(string texte, T[] éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// busca de um elemento na matriz
// elementos: matriz de números reais
// TypeRecherche: dicotômico ou linear
static void Recherche(double[] éléments, TypeRecherche type) {
// Pesquisa
bool terminé = false;
string réponse = null;
double élément = 0;
bool erreur = false;
int i = 0;
while (!terminé) {
// pergunta
Console.WriteLine("Elément cherché (rien pour arrêter) : ");
// leitura e verificação da resposta
réponse = Console.ReadLine().Trim();
// terminado?
if (réponse.Equals(""))
break;
// verificação
try {
élément = Double.Parse(réponse);
erreur = false;
} catch {
Console.WriteLine("Erreur, recommencez...");
erreur = true;
}//try-catch
// se não houver erro
if (!erreur) {
// procurando o elemento na matriz
if (type == TypeRecherche.dichotomique)
// busca dicotômica
i = Array.BinarySearch(éléments, élément);
else
// busca linear
i = Array.IndexOf(éléments, élément);
// Exibição da resposta
if (i >= 0)
Console.WriteLine("Trouvé en position " + i);
else
Console.WriteLine("Pas dans le tableau");
}//if
}//while
}
}
}
- linhas 27-62: o método Saisie insere os elementos de uma matriz éléments digitados pelo teclado. Como não é possível dimensionar a matriz antecipadamente (não se sabe seu tamanho final), é necessário redimensioná-la a cada novo elemento (linha 57). Um algoritmo mais eficiente teria sido alocar espaço para a tabela em grupos de N elementos. No entanto, uma tabela não foi projetada para ser redimensionada. Esse caso é melhor tratado com uma lista (ArrayList, List<T>).
- linhas 75-113: o método Recherche permite pesquisar no array éléments um elemento digitado no teclado. O modo de pesquisa varia dependendo se o array está ordenado ou não. Para um array não ordenado, realiza-se uma pesquisa linear com o método IndexOf da linha 106. Para um array ordenado, realiza-se uma pesquisa dicotômica com o método BinarySearch da linha 103.
- linha 18: a tabela éléments é ordenada. Aqui, utiliza-se uma variante de Sort que possui apenas um parâmetro: a tabela a ser ordenada. A relação de ordem utilizada para comparar os elementos da tabela é, então, a implícita desses elementos. Aqui, os elementos são numéricos. É utilizada a ordem natural dos números.
Os resultados exibidos na tela são os seguintes:
5.4. As coleções genéricas
Além da tabela, existem diversas classes para armazenar coleções de elementos. Há versões genéricas no espaço de nomes System.Collections.Generic e versões não genéricas em System.Collections. Apresentaremos duas coleções genéricas frequentemente utilizadas: a lista e o dicionário.
A lista das coleções genéricas é a seguinte:

5.4.1. A classe genérica List<T>
A classe System.Collections.Generic.List<T> permite implementar coleções de objetos do tipo T cujo tamanho varia durante a execução do programa. Um objeto do tipo List<T> é manipulado quase como um array. Assim, o elemento i de uma lista l é denotado por l[i].
Existe também um tipo de lista não genérica: ArrayList, capaz de armazenar referências a quaisquer objetos. ArrayList é funcionalmente equivalente a List<Object>.. Um objeto ArrayList tem a seguinte aparência:
![]() |
Acima, os elementos 0, 1 e i da lista apontam para objetos de tipos diferentes. É necessário que um objeto seja criado primeiro antes de adicionar sua referência à lista ArrayList. Embora um ArrayList armazene referências a objetos, é possível armazenar números nele. Isso é feito por meio de um mecanismo chamado Boxing: o número é encapsulado em um objeto O do tipo Object, e é a referência O que é armazenada na lista. Esse é um mecanismo transparente para o desenvolvedor. Assim, pode-se escrever:
Isso produzirá o seguinte resultado:
![]() |
No exemplo acima, o número 4 foi encapsulado em um objeto O e a referência O é armazenada na lista. Para recuperá-lo, pode-se escrever:
int i = (int)liste[0];
A operação Object -> int é chamada de Unboxing. Se uma lista for inteiramente composta por tipos int, declará-la como List<int> melhora o desempenho. De fato, os números do tipo int passam a ser armazenados na própria lista e não em tipos Object externos à lista. As operações de boxing/unboxing não ocorrem mais.
Para um objeto List<T> ou T que seja uma classe, a lista armazena, mais uma vez, as referências dos objetos do tipo T:
![]() |
Aqui estão algumas das propriedades e métodos das listas genéricas:
Propriedades
Métodos
Vamos retomar o exemplo abordado anteriormente com um objeto do tipo Array e tratá-lo agora com um objeto do tipo List<T>.. Como a lista é um objeto semelhante a uma matriz, o código sofre poucas alterações. Apresentamos apenas as modificações mais significativas:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
// tipo de busca
enum TypeRecherche { linéaire, dichotomique };
// método principal
static void Main(string[] args) {
// leitura dos elementos de uma lista digitados no teclado
List<double> éléments;
Saisie(out éléments);
// número de elementos
Console.WriteLine("La liste a {0} éléments et une capacité de {1} éléments", éléments.Count, éléments.Capacity);
// exibição da lista não ordenada
Affiche("Liste non triée", éléments);
// Pesquisa linear na lista não ordenada
Recherche(éléments, TypeRecherche.linéaire);
// classificação da lista
éléments.Sort();
// exibição da lista ordenada
Affiche("Liste triée", éléments);
// Pesquisa dicotômica na lista ordenada
Recherche(éléments, TypeRecherche.dichotomique);
}
// inserção dos valores da lista de elementos
// elementos: referência à lista criada pelo método
static void Saisie(out List<double> éléments) {
...
// inicialmente, a lista está vazia
éléments = new List<double>();
// Ciclo de inserção dos elementos da lista
while (!terminé) {
...
// se não houver erro
if (!erreur) {
// mais um elemento na lista
éléments.Add(élément);
}
}//while
}
// método genérico para exibir os elementos de um objeto enumerável
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
// busca de um elemento na lista
// elementos: lista de números reais
// TypeRecherche: dicotômico ou linear
static void Recherche(List<double> éléments, TypeRecherche type) {
...
while (!terminé) {
...
// se não houver erro
if (!erreur) {
// procura-se o elemento na lista
if (type == TypeRecherche.dichotomique)
// busca dicotômica
i = éléments.BinarySearch(élément);
else
// busca linear
i = éléments.IndexOf(élément);
// Exibição da resposta
...
}//if
}//while
}
}
}
- linhas 46-51: o método genérico Affiche<T> aceita dois parâmetros:
- o primeiro parâmetro é um texto a ser gravado
- o segundo parâmetro é um objeto que implementa a interface genérica IEnumerable<T>:
A estrutura foreach( T elemento in elementos), na linha 48, é válida para qualquer objeto *éléments que implemente a interface *IEnumerable. As tabelas (Array) e as listas (List<T>) implementam a interface IEnumerable<T>. Portanto, o método Affiche é adequado tanto para exibir tabelas quanto listas.
Os resultados da execução do programa são os mesmos do exemplo que utiliza a classe Array.
5.4.2. A classe Dictionary<TKey,TValue>
A classe System.Collections.Generic.Dictionary<TKey,TValue> permite implementar um dicionário. Pode-se considerar um dicionário como uma tabela de duas colunas:
chave | valor |
chave1 | valor1 |
chave2 | valor2 |
.. | ... |
Na classe Dictionary<TKey,TValue>, as chaves são do tipo Tkey e os valores, do tipo TValue. As chaves são únicas, c.a.d, de modo que não pode haver duas chaves idênticas. Um dicionário desse tipo poderia se parecer com isto se os tipos TKey e TValue designassem classes:
![]() |
O valor associado à chave C de um dicionário D é obtido pela notação D[C]. Esse valor é de leitura e gravação. Assim, pode-se escrever:
Se a chave c não existir no dicionário D, a notação D[c] lança uma exceção.
Os principais métodos e propriedades da classe Dictionary<TKey,TValue> são os seguintes:
Construtores
Propriedades
Métodos
Consideremos o seguinte programa de exemplo:
using System;
using System.Collections.Generic;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// criação de um dicionário <string, int>
string[] liste = { "jean:20", "paul:18", "mélanie:10", "violette:15" };
string[] champs = null;
char[] séparateurs = new char[] { ':' };
Dictionary<string,int> dico = new Dictionary<string,int>();
for (int i = 0; i <liste.Length; i++) {
champs = liste[i].Split(séparateurs);
dico[champs[0]]= int.Parse(champs[1]);
}//for
// número de elementos no dicionário
Console.WriteLine("Le dictionnaire a " + dico.Count + " éléments");
// lista de chaves
Affiche("[Liste des clés]",dico.Keys);
// lista de valores
Affiche("[Liste des valeurs]", dico.Values);
// lista de chaves e valores
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// a chave “paul” é excluída
Console.WriteLine("[Suppression d'une clé]");
dico.Remove("paul");
// lista de chaves e valores
Console.WriteLine("[Liste des clés & valeurs]");
foreach (string clé in dico.Keys) {
Console.WriteLine("clé=" + clé + " valeur=" + dico[clé]);
}
// pesquisa no dicionário
String nomCherché = null;
Console.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
int value;
while (!nomCherché.Equals("")) {
dico.TryGetValue(nomCherché, out value);
if (value!=0) {
Console.WriteLine(nomCherché + "," + value);
} else {
Console.WriteLine("Nom " + nomCherché + " inconnu");
}
// próxima pesquisa
Console.Out.Write("Nom recherché (rien pour arrêter) : ");
nomCherché = Console.ReadLine().Trim();
}//while
}
// método genérico para exibir os elementos de um tipo enumerável
static void Affiche<T>(string texte, IEnumerable<T> éléments) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (T élément in éléments) {
Console.WriteLine(élément);
}
}
}
}
- linha 8: um array de string que servirá para inicializar o dicionário <string,int>
- linha 11: o dicionário <string,int>
- linhas 12-15: sua inicialização a partir da matriz de string da linha 8
- linha 17: número de entradas do dicionário
- linha 19: as chaves do dicionário
- linha 21: os valores do dicionário
- linha 29: exclusão de uma entrada do dicionário
- linha 41: busca por uma chave no dicionário. Se ela não existir, o método TryGetValue atribuirá o valor 0 a value, pois value é do tipo numérico. Essa técnica só pode ser utilizada aqui porque sabemos que o valor 0 não está no dicionário.
Os resultados da execução são os seguintes:
5.5. Os arquivos de texto
5.5.1. A classe StreamReader
A classe System.IO.StreamReader permite ler o conteúdo de um arquivo de texto. Na verdade, ela é capaz de processar fluxos que não são arquivos. Aqui estão algumas de suas propriedades e métodos:
Construtores
Propriedades
Métodos
Aqui está um exemplo:
using System;
using System.IO;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// diretório de execução
Console.WriteLine("Répertoire d'exécution : "+Environment.CurrentDirectory);
string ligne = null;
StreamReader fluxInfos = null;
// leitura do conteúdo do arquivo infos.txt
try {
// leitura 1
Console.WriteLine("Lecture 1----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
ligne = fluxInfos.ReadLine();
while (ligne != null) {
Console.WriteLine(ligne);
ligne = fluxInfos.ReadLine();
}
}
// leitura 2
Console.WriteLine("Lecture 2----------------");
using (fluxInfos = new StreamReader("infos.txt")) {
Console.WriteLine(fluxInfos.ReadToEnd());
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- linha 8: exibe o nome do diretório de execução
- linhas 12, 27: um try/catch para lidar com uma possível exceção.
- linha 15: a estrutura using flux=new StreamReader(...) é um recurso para evitar ter que fechar explicitamente o fluxo após seu uso. Esse fechamento é feito automaticamente assim que se sai do escopo do using.
- linha 15: o arquivo lido se chama infos.txt. Como se trata de um nome relativo, ele será procurado no diretório de execução exibido pela linha 8. Se não estiver lá, uma exceção será lançada e tratada pelo try/catch.
- linhas 16-20: o arquivo é lido linha por linha
- linha 25: o arquivo é lido de uma só vez
O arquivo infos.txt é o seguinte:
e está localizado na seguinte pasta do projeto C#:
![]() |
Veremos que bin/Release é a pasta de execução quando o projeto é executado com Ctrl-F5.
A execução produz os seguintes resultados:
Se, na linha 15, inserirmos o nome do arquivo xx.txt, obtemos os seguintes resultados:
5.5.2. A classe StreamWriter
A classe System.IO.StreamReader permite gravar em um arquivo de texto. Assim como a classe StreamReader, ela é capaz de processar fluxos que não são arquivos. Aqui estão algumas de suas propriedades e métodos:
Construtores
Propriedades
Métodos
Consideremos o seguinte exemplo:
using System;
using System.IO;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// diretório de execução
Console.WriteLine("Répertoire d'exécution : " + Environment.CurrentDirectory);
string ligne = null; // uma linha de texto
StreamWriter fluxInfos = null; // o arquivo de texto
try {
// criação do arquivo de texto
using (fluxInfos = new StreamWriter("infos2.txt")) {
Console.WriteLine("Mode AutoFlush : {0}", fluxInfos.AutoFlush);
// leitura da linha digitada no teclado
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
// loop enquanto a linha digitada não estiver vazia
while (ligne != "") {
// gravação da linha no arquivo de texto
fluxInfos.WriteLine(ligne);
// leitura de uma nova linha digitada no teclado
Console.Write("ligne (rien pour arrêter) : ");
ligne = Console.ReadLine().Trim();
}//while
}
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : " + e.Message);
}
}
}
}
- linha 13: mais uma vez, utilizamos a sintaxe using(flux) para não precisarmos fechar explicitamente o fluxo por meio de uma operação Close. Esse fechamento é feito automaticamente ao sair da operação using.
- Por que um try/catch nas linhas 11 e 27? Na linha 13, poderíamos especificar um nome de arquivo no formato /rep1/rep2/ .../fichier com um caminho /rep1/rep2/... que não existe, tornando assim impossível a criação de fichier. Nesse caso, seria lançada uma exceção. Existem outros casos possíveis de exceção (disco cheio, permissões insuficientes, ...)
Os resultados da execução são os seguintes:
O arquivo infos2.txt foi criado na pasta bin/Release do projeto:
![]() | ![]() |
5.6. Os arquivos binários
As classes System.IO.BinaryReader e System.IO.BinaryWriter servem para ler e gravar arquivos binários.
Consideremos o seguinte aplicativo:
// sintaxe pg texto bin logs
// lê-se um arquivo de texto (texto) e seu conteúdo é armazenado em um arquivo binário (bin
// o arquivo de texto contém linhas no formato nome : idade, que serão armazenadas em uma estrutura string, int
// (logs) é um arquivo de texto de logs
O arquivo de texto tem o seguinte conteúdo:
O programa é o seguinte:
using System;
using System.IO;
// sintaxe pg texto bin logs
// lê-se um arquivo de texto (texto) e seu conteúdo é armazenado em um arquivo binário (bin)
// o arquivo de texto contém linhas no formato nome : idade, que serão armazenadas em uma estrutura string, int
// (logs) é um arquivo de texto de logs
namespace Chap3 {
class Program {
static void Main(string[] arguments) {
// são necessários 3 argumentos
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg texte binaire log");
Environment.Exit(1);
}//if
// variáveis
string ligne=null;
string nom=null;
int age=0;
int numLigne = 0;
char[] séparateurs = new char[] { ':' };
string[] champs=null;
StreamReader input = null;
BinaryWriter output = null;
StreamWriter logs = null;
bool erreur = false;
// leitura de arquivo de texto – gravação de arquivo binário
try {
// abertura do arquivo de texto para leitura
input = new StreamReader(arguments[0]);
// abertura do arquivo binário para gravação
output = new BinaryWriter(new FileStream(arguments[1], FileMode.Create, FileAccess.Write));
// abertura do arquivo de logs para gravação
logs = new StreamWriter(arguments[2]);
// análise do arquivo de texto
while ((ligne = input.ReadLine()) != null) {
// mais uma linha
numLigne++;
// linha vazia?
if (ligne.Trim() == "") {
// ignorado
continue;
}
// uma linha com nome: idade
champs = ligne.Split(séparateurs);
// precisamos de 2 campos
if (champs.Length != 2) {
// registramos o erro
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nombre de champs incorrect", numLigne, arguments[0]);
// linha seguinte
continue;
}//if
// o primeiro campo não pode estar vazio
erreur = false;
nom = champs[0].Trim();
if (nom == "") {
// registramos o erro
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un nom vide", numLigne, arguments[0]);
erreur = true;
}
// o segundo campo deve ser um número inteiro >=0
if (!int.TryParse(champs[1],out age) || age<0) {
// registra-se o erro
logs.WriteLine("La ligne n° [{0}] du fichier [{1}] a un âge [{2}] incorrect", numLigne, arguments[0], champs[1].Trim());
erreur = true;
}//se
// se não houver erro, os dados são gravados no arquivo binário
if (!erreur) {
output.Write(nom);
output.Write(age);
}
// próxima linha
}//enquanto
}catch(Exception e){
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// fechamento dos arquivos
if(input!=null) input.Close();
if(output!=null) output.Close();
if(logs!=null) logs.Close();
}
}
}
}
Vamos nos concentrar nas operações relacionadas à classe BinaryWriter:
- linha 34: o objeto BinaryWriter é aberto pela operação
output=new BinaryWriter(new FileStream(arguments[1],FileMode.Create,FileAccess.Write));
O argumento do construtor deve ser um fluxo (Stream). Aqui, trata-se de um fluxo criado a partir de um arquivo (FileStream), cujos dados são:
- (continuação)
- o nome
- a operação a ser realizada, neste caso FileMode.Create para criar o arquivo
- o tipo de acesso, neste caso FileAccess.Write para acesso em gravação ao arquivo
- linhas 70-73: as operações de gravação
A classe BinaryWriter possui diferentes métodos Write sobrecarregados para gravar os diversos tipos de dados simples
- linha 81: a operação de fechamento do fluxo
Os três argumentos do método Main são fornecidos ao projeto (por meio de suas propriedades) [1], e o arquivo de texto a ser processado é colocado na pasta bin/Release [2]:
![]() |
Com o seguinte arquivo [personnes1.txt]:
os resultados da execução são os seguintes:
![]() |
- no [1], o arquivo binário [personnes1.bin] criado, bem como o arquivo de logs [logs.txt]. Este último tem o seguinte conteúdo:
O conteúdo do arquivo binário [personnes1.bin] será fornecido pelo programa a seguir. Este também aceita três argumentos:
// sintaxe pg bin texto logs
// lê-se um arquivo binário e seu conteúdo é armazenado em um arquivo de texto (texto)
// o arquivo binário possui uma estrutura string, int
// o arquivo de texto contém linhas no formato nome : idade
// logs é um arquivo de texto de logs
Portanto, realizamos a operação inversa. Lemos um arquivo binário para criar um arquivo de texto. Se o arquivo de texto gerado for idêntico ao arquivo original, saberemos que a conversão texto → binário → texto ocorreu corretamente. O código é o seguinte:
using System;
using System.IO;
// sintaxe pg bin texto logs
// lê-se um arquivo binário bin e seu conteúdo é gravado em um arquivo de texto (texto)
// o arquivo binário tem uma estrutura string, int
// o arquivo de texto contém linhas no formato nome : idade
// logs é um arquivo de texto de logs
namespace Chap3 {
class Program2 {
static void Main(string[] arguments) {
// são necessários 3 argumentos
if (arguments.Length != 3) {
Console.WriteLine("syntaxe : pg binaire texte log");
Environment.Exit(1);
}//if
// variáveis
string nom = null;
int age = 0;
int numPersonne = 1;
BinaryReader input = null;
StreamWriter output = null;
StreamWriter logs = null;
bool fini;
// leitura de arquivo binário – gravação de arquivo de texto
try {
// abertura do arquivo binário para leitura
input = new BinaryReader(new FileStream(arguments[0], FileMode.Open, FileAccess.Read));
// abertura do arquivo de texto para gravação
output = new StreamWriter(arguments[1]);
// abertura do arquivo de logs para gravação
logs = new StreamWriter(arguments[2]);
// análise do arquivo binário
fini = false;
while (!fini) {
try {
// leitura do nome
nom = input.ReadString().Trim();
// leitura da idade
age = input.ReadInt32();
// gravação no arquivo de texto
output.WriteLine(nom + ":" + age);
// próxima pessoa
numPersonne++;
} catch (EndOfStreamException) {
fini = true;
} catch (Exception e) {
logs.WriteLine("L'erreur suivante s'est produite à la lecture de la personne n° {0} : {1}", numPersonne, e.Message);
}
}//while
} catch (Exception e) {
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
} finally {
// fechamento dos arquivos
if (input != null)
input.Close();
if (output != null)
output.Close();
if (logs != null)
logs.Close();
}
}
}
}
Vamos nos deter nas operações relacionadas à classe BinaryReader:
- linha 30: o objeto BinaryReader é aberto pela operação
input=new BinaryReader(new FileStream(arguments[0],FileMode.Open,FileAccess.Read));
O argumento do construtor deve ser um fluxo (Stream). Aqui, trata-se de um fluxo construído a partir de um arquivo (FileStream), cujos dados são:
- (continuação)
- o nome
- a operação a ser realizada, neste caso FileMode.Open para abrir um arquivo existente
- o tipo de acesso, neste caso FileAccess.Read para acesso em leitura ao arquivo
- linhas 40, 42: as operações de leitura
A classe BinaryReader dispõe de diferentes métodos ReadXX para ler os diferentes tipos de dados simples
- linha 60: a operação de fechamento do fluxo
Se executarmos os dois programas em sequência, transformando personnes1.txt em personnes1.bin e, em seguida, personnes1.bin em personnes2.txt2, obtemos os seguintes resultados:
![]() |
- em [1], o projeto está configurado para executar o segundo aplicativo
- em [2], os argumentos passados para Main
- em [3], os arquivos gerados pela execução do aplicativo.
O conteúdo de [personnes2.txt] é o seguinte:
5.7. Expressões regulares
A classe System.Text.RegularExpressions.Regex permite o uso de expressões regulares. Elas permitem verificar o formato de uma sequência de caracteres. Assim, é possível verificar se uma sequência que representa uma data está, de fato, no formato dd/mm/aa. Para isso, utiliza-se um modelo e compara-se a sequência com esse modelo. Portanto, neste exemplo, j, m e a devem ser algarismos. O modelo de um formato de data válido é, portanto, “\d\d/\d\d/\d\d”, em que o símbolo \d representa um algarismo. Os símbolos que podem ser utilizados em um modelo são os seguintes:
Descrição | |
Marca o caractere seguinte como caractere especial ou literal. Por exemplo, “n” corresponde ao caractere “n”. “\n” corresponde a um caractere de nova linha. A sequência “\\” corresponde a “\”, enquanto “\(" corresponde a “(”. | |
Corresponde ao início da entrada. | |
Corresponde ao fim da entrada. | |
Corresponde ao caractere anterior zero ou mais vezes. Assim, “zo*” corresponde a “z” ou a “zoo”. | |
Corresponde ao caractere anterior uma ou mais vezes. Assim, “zo+” corresponde a “zoo”, mas não a “z”. | |
Corresponde ao caractere anterior zero ou uma vez. Por exemplo, “a?ve?” corresponde a “ve” em “lever”. | |
Corresponde a qualquer caractere único, exceto o caractere de nova linha. | |
Procura o modèle e armazena a correspondência. A subcadeia correspondente pode ser extraída da coleção Matches obtida, usando o Item [0]...[n]. Para encontrar correspondências com caracteres entre parênteses ( ), use “\(" ou “\)”. | |
Corresponde a x ou a y. Por exemplo, “z|foot” corresponde a “z” ou a “foot”. “(z|f)oo” corresponde a “zoo” ou a “foo”. | |
n é um número inteiro não negativo. Corresponde exatamente a n vezes o caractere. Por exemplo, “o{2}” não corresponde a “o” em “Bob”, mas aos dois primeiros “o” em “fooooot”. | |
n é um número inteiro não negativo. Corresponde a pelo menos n vezes o caractere. Por exemplo, “o{2,}” não corresponde ao “o” em “Bob”, mas a todos os “o” em “fooooot”. “o{1,}” equivale a “o+” e “o{0,}” equivale a “o*”. | |
m e n são números inteiros não negativos. Corresponde a pelo menos n e, no máximo, m vezes o caractere. Por exemplo, “o{1,3}” corresponde aos três primeiros “o” em “foooooot” e “o{0,1}” equivale a “o?”. | |
Conjunto de caracteres. Corresponde a um dos caracteres indicados. Por exemplo, “[abc]” corresponde a “a” em “plat”. | |
Conjunto de caracteres negativo. Corresponde a qualquer caractere não indicado. Por exemplo, “[^abc]” corresponde a “p” em “plat”. | |
Intervalo de caracteres. Corresponde a qualquer caractere na série especificada. Por exemplo, “[a-z]” corresponde a qualquer letra minúscula entre “a” e “z”. | |
Intervalo de caracteres negativo. Corresponde a qualquer caractere que não esteja na série especificada. Por exemplo, “[^m-z]” corresponde a qualquer caractere que não esteja entre “m” e “z”. | |
Corresponde a um limite que representa uma palavra; em outras palavras, à posição entre uma palavra e um espaço. Por exemplo, “er\b” corresponde a “er” em “lever”, mas não a “er” em “verbe”. | |
Corresponde a um limite que não representa uma palavra. “en*t\B” corresponde a “ent” em “bien entendu”. | |
Corresponde a um caractere que representa um algarismo. É equivalente a [0-9]. | |
Corresponde a um caractere que não representa um algarismo. É equivalente a [^0-9]. | |
Corresponde a um caractere de quebra de página. | |
Corresponde a um caractere de nova linha. | |
Corresponde a um caractere de retorno de carro. | |
Corresponde a qualquer espaço em branco, incluindo espaço, tabulação, quebra de página etc. É equivalente a “[ \f\n\r\t\v]”. | |
Corresponde a qualquer caractere de espaço não em branco. É equivalente a “[^ \f\n\r\t\v]”. | |
Corresponde a um caractere de tabulação. | |
Corresponde a um caractere de tabulação vertical. | |
Corresponde a qualquer caractere que represente uma palavra e inclua um sublinhado. É equivalente a “[A-Za-z0-9_]”. | |
Corresponde a qualquer caractere que não represente uma palavra. É equivalente a “[^A-Za-z0-9_]”. | |
Corresponde a num, onde num é um número inteiro positivo. Refere-se às correspondências armazenadas. Por exemplo, “(.)\1” corresponde a dois caracteres idênticos consecutivos. | |
Corresponde a n, em que n é um valor de escape octal. Os valores de escape octais devem ter 1, 2 ou 3 dígitos. Por exemplo, “\11” e “\011” correspondem, ambos, a um caractere de tabulação. "\0011" equivale a "\001" e "1". Os valores de escape octais não devem exceder 256. Se isso ocorrer, apenas os dois primeiros dígitos serão considerados na expressão. Permite usar os códigos ASCII em expressões regulares. | |
Corresponde a n, onde n é um valor de escape hexadecimal. Os valores de escape hexadecimais devem conter obrigatoriamente dois dígitos. Por exemplo, "\x41" corresponde a "A". "\x041" equivale a "\x04" e "1". Permite usar os códigos ASCII em expressões regulares. |
Um elemento em um modelo pode aparecer uma ou mais vezes. Vejamos alguns exemplos relacionados ao símbolo \d, que representa um dígito:
modelo | significado |
\d | um dígito |
\d? | 0 ou 1 dígito |
\d* | 0 ou mais dígitos |
\d+ | 1 ou mais dígitos |
\d{2} | 2 dígitos |
\d{3,} | pelo menos 3 dígitos |
\d{5,7} | entre 5 e 7 dígitos |
Imaginemos agora o modelo capaz de descrever o formato esperado para uma sequência de caracteres:
cadeia procurada | modelo |
uma data no formato dd/mm/aa | \d{2}/\d{2}/\d{2} |
uma hora no formato hh:mm:ss | \d{2}:\d{2}:\d{2} |
um número inteiro sem sinal | \d+ |
uma sequência de espaços, que pode estar vazia | \s* |
um número inteiro sem sinal que pode ser precedido ou seguido por espaços | \s*\d+\s* |
um número inteiro que pode ser com sinal e precedido ou seguido por espaços | \s*[+|-]?\s*\d+\s* |
um número real sem sinal que pode ser precedido ou seguido por espaços | \s*\d+(.\d*)?\s* |
um número real que pode ser com sinal e precedido ou seguido por espaços | \s*[+|]?\s*\d+(.\d*)?\s* |
uma sequência de caracteres contendo a palavra juste | \bjuste\b |
É possível especificar onde procurar o padrão na sequência:
padrão | significado |
^padrão | o padrão inicia a sequência |
padrão$ | o modelo encerra a sequência |
^padrão$ | o modelo inicia e encerra a sequência |
padrão | o padrão é procurado em toda a sequência, começando pelo início dela. |
sequência pesquisada | padrão |
uma sequência que termina com um ponto de exclamação | !$ |
uma sequência que termina com um ponto | \.$ |
uma sequência que começa com // | ^// |
uma sequência que contém apenas uma palavra, eventualmente seguida ou precedida por espaços | ^\s*\w+\s*$ |
uma sequência contendo duas palavras, eventualmente seguidas ou precedidas por espaços | ^\s*\w+\s*\w+\s*$ |
uma sequência que contenha a palavra secret | \bsecret\b |
Os subconjuntos de um padrão podem ser “recuperados”. Assim, não só é possível verificar se uma sequência corresponde a um padrão específico, como também é possível recuperar nessa sequência os elementos correspondentes aos subconjuntos do padrão que foram colocados entre parênteses. Assim, se analisarmos uma sequência que contenha uma data dd/mm/aa e quisermos, além disso, extrair os elementos dd, mm e aa dessa data, utilizaremos o modelo (\d\d)/(\d\d)/(\d\d).
5.7.1. Verificar se uma sequência corresponde a um determinado modelo
Um objeto do tipo Regex é construído da seguinte maneira:
Depois que a expressão regular modelo for criada, é possível compará-la com sequências de caracteres usando o método IsMatch:
Aqui está um exemplo:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program {
static void Main(string[] args) {
// uma expressão regular modelo
string modèle1 = @"^\s*\d+\s*$";
Regex regex1 = new Regex(modèle1);
// comparar um exemplar com o modelo
string exemplaire1 = " 123 ";
if (regex1.IsMatch(exemplaire1)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire1, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire1, modèle1);
}//if
string exemplaire2 = " 123a ";
if (regex1.IsMatch(exemplaire2)) {
Console.WriteLine("[{0}] correspond au modèle [{1}]", exemplaire2, modèle1);
} else {
Console.WriteLine("[{0}] ne correspond pas au modèle [{1}]", exemplaire2, modèle1);
}//if
}
}
}
e os resultados da execução:
5.7.2. Encontrar todas as ocorrências de um padrão em uma string
O método Matches permite recuperar os elementos de uma string que correspondam a um padrão:
A classe MatchCollection possui uma propriedade Count, que representa o número de elementos da coleção. Se résultats for um objeto MatchCollection, resultados[i] é o elemento i dessa coleção e é do tipo Match. A classe Match possui diversas propriedades, entre as quais as seguintes:
- Value: o valor do objeto Match,, ou seja, um elemento que corresponde ao modelo
- Index: a posição em que o elemento foi encontrado na sequência explorada
Vejamos o exemplo a seguir:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program2 {
static void Main(string[] args) {
// várias ocorrências do modelo no exemplo
string modèle2 = @"\d+";
Regex regex2 = new Regex(modèle2);
string exemplaire3 = " 123 456 789 ";
MatchCollection résultats = regex2.Matches(exemplaire3);
Console.WriteLine("Modèle=[{0}],exemplaire=[{1}]", modèle2, exemplaire3);
Console.WriteLine("Il y a {0} occurrences du modèle dans l'exemplaire ", résultats.Count);
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("[{0}] trouvé en position {1}", résultats[i].Value, résultats[i].Index);
}//for
}
}
}
- linha 8: o padrão procurado é uma sequência de números
- linha 10: a sequência na qual se procura esse padrão
- linha 11: recuperam-se todos os elementos de exemplaire3 que atendem ao padrão modèle2
- linhas 14-16: exibimos esses elementos
Os resultados da execução do programa são os seguintes:
5.7.3. Recuperar partes de um modelo
Subconjuntos de um modelo podem ser “recuperados”. Assim, não só é possível verificar se uma sequência corresponde a um modelo específico, como também recuperar nessa sequência os elementos correspondentes aos subconjuntos do modelo que foram colocados entre parênteses. Assim, se analisarmos uma sequência de caracteres contendo uma data dd/mm/aa e quisermos, além disso, extrair os elementos dd, mm e aa dessa data, utilizaremos o modelo (\d\d)/(\d\d)/(\d\d).
Vejamos o seguinte exemplo:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program3 {
static void Main(string[] args) {
// captura de elementos no modelo
string modèle3 = @"(\d\d):(\d\d):(\d\d)";
Regex regex3 = new Regex(modèle3);
string exemplaire4 = "Il est 18:05:49";
// verificação do modelo
Match résultat = regex3.Match(exemplaire4);
if (résultat.Success) {
// a instância corresponde ao modelo
Console.WriteLine("L'exemplaire [{0}] correspond au modèle [{1}]",exemplaire4,modèle3);
// são exibidos os grupos de parênteses
for (int i = 0; i < résultat.Groups.Count; i++) {
Console.WriteLine("groupes[{0}]=[{1}] trouvé en position {2}",i, résultat.Groups[i].Value,résultat.Groups[i].Index);
}//for
} else {
// a instância não corresponde ao modelo
Console.WriteLine("L'exemplaire[{0}] ne correspond pas au modèle [{1}]", exemplaire4, modèle3);
}
}
}
}
A execução deste programa produz os seguintes resultados:
A novidade está nas linhas 12 a 19:
- linha 12: a cadeia exemplaire4 é comparada ao modelo regex3 por meio do método Match. Esse método retorna um objeto Match já apresentado. Utilizamos aqui duas novas propriedades dessa classe:
- Success (linha 13): indica se houve correspondência
- Groups (linhas 17, 18): coleção em que
- Groups[0] corresponde à parte da string que corresponde ao modelo
- Groups[i] (i>=1) corresponde ao grupo de parênteses nº i
Se résultat for do tipo Match, résultats.Groups é do tipo GroupCollection e résultats.Groups[i] é do tipo Group. A classe Group possui duas propriedades que utilizamos aqui:
- Value (linha 18): o valor do objeto Group, que é o elemento correspondente ao conteúdo de um parêntese
- Index (linha 18): a posição em que o elemento foi encontrado na sequência analisada
5.7.4. Um programa de treino
Encontrar a expressão regular que permite verificar se uma sequência corresponde de fato a um determinado padrão pode, às vezes, ser um verdadeiro desafio. O programa a seguir permite que você pratique. Ele solicita um padrão e uma sequência e indica se a sequência corresponde ou não ao padrão.
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program4 {
static void Main(string[] args) {
// dados
string modèle, chaine;
Regex regex = null;
MatchCollection résultats;
// solicita-se ao usuário os modelos e os exemplares a serem comparados com este
while (true) {
// solicita-se o modelo
Console.Write("Tapez le modèle à tester ou rien pour arrêter :");
modèle = Console.In.ReadLine();
// concluído?
if (modèle.Trim() == "")
break;
// criamos a expressão regular
try {
regex = new Regex(modèle);
} catch (Exception ex) {
Console.WriteLine("Erreur : " + ex.Message);
continue;
}
// solicita-se ao usuário os exemplares a serem comparados com o modelo
while (true) {
Console.Write("Tapez la chaîne à comparer au modèle [{0}] ou rien pour arrêter :", modèle);
chaine = Console.ReadLine();
// concluído?
if (chaine.Trim() == "")
break;
// faz-se a comparação
résultats = regex.Matches(chaine);
// sucesso?
if (résultats.Count == 0) {
Console.WriteLine("Je n'ai pas trouvé de correspondances");
continue;
}//se
// são exibidos os elementos que correspondem ao modelo
for (int i = 0; i < résultats.Count; i++) {
Console.WriteLine("J'ai trouvé la correspondance [{0}] en position [{1}]", résultats[i].Value, résultats[i].Index);
// dos subelementos
if (résultats[i].Groups.Count != 1) {
for (int j = 1; j < résultats[i].Groups.Count; j++) {
Console.WriteLine("\tsous-élément [{0}] en position [{1}]", résultats[i].Groups[j].Value, résultats[i].Groups[j].Index);
}
}
}
}
}
}
}
}
Veja um exemplo de execução:
5.7.5. O método Split
Já vimos esse método na classe String:
|
O método Split da classe Regex nos permite definir o separador com base em um modelo:
|
Suponhamos, por exemplo, que tenhamos em um arquivo de texto linhas do tipo campo1, campo2, ..., campo n. Os campos são separados por uma vírgula, mas esta pode ser precedida ou seguida por espaços. O método Split da classe string não é adequado nesse caso. O método RegEx oferece a solução. Se ligne for a linha lida, os campos poderão ser obtidos por
conforme mostra o exemplo a seguir:
using System;
using System.Text.RegularExpressions;
namespace Chap3 {
class Program5 {
static void Main(string[] args) {
// uma linha
string ligne = "abc , def , ghi";
// um modelo
Regex modèle = new Regex(@"\s*,\s*");
// decomposição da linha em campos
string[] champs = modèle.Split(ligne);
// exibição
for (int i = 0; i < champs.Length; i++) {
Console.WriteLine("champs[{0}]=[{1}]", i, champs[i]);
}
}
}
}
Resultados da execução:
5.8. Aplicativo de exemplo — V3
Retomamos a aplicação estudada nos parágrafos 3.6 (versão 1) e 4.10 (versão 2).
Na última versão analisada, o cálculo do imposto era feito na classe abstrata AbstractImpot:
namespace Chap2 {
abstract class AbstractImpot : IImpot {
// as faixas de imposto necessárias para o cálculo do imposto
// provêm de uma fonte externa
protected TrancheImpot[] tranchesImpot;
// cálculo do imposto
public int calculer(bool marié, int nbEnfants, int salaire) {
// cálculo do número de cotas
decimal nbParts;
if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// cálculo do rendimento tributável e do quociente familiar
decimal revenu = 0.72M * salaire;
decimal QF = revenu / nbParts;
// cálculo do imposto
tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
int i = 0;
while (QF > tranchesImpot[i].Limite) i++;
// retorno do resultado
return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
}//calcular
}//classe
}
O método calculer da linha 38 utiliza a tabela tranchesImpot da linha 35, tabela que não é inicializada pela classe AbstractImpot. É por isso que ela é abstrata e precisa ser derivada para ser útil. Essa inicialização era feita pela classe derivada HardwiredImpot:
using System;
namespace Chap2 {
class HardwiredImpot : AbstractImpot {
// tabelas de dados necessárias para o cálculo do imposto
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
public HardwiredImpot() {
// criação da tabela de faixas de imposto
tranchesImpot = new TrancheImpot[limites.Length];
// preenchimento
for (int i = 0; i < tranchesImpot.Length; i++) {
tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
}
}
}// classe
}// espaço de nomes
No exemplo acima, os dados necessários para o cálculo do imposto estavam embutidos no código da classe. A nova versão do exemplo os coloca em um arquivo de texto:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
Como a execução deste arquivo pode gerar exceções, criamos uma classe especial para gerenciá-las:
using System;
namespace Chap3 {
class FileImpotException : Exception {
// códigos de erro
[Flags]
public enum CodeErreurs { Acces = 1, Ligne = 2, Champ1 = 4, Champ2 = 8, Champ3 = 16 };
// código de erro
public CodeErreurs Code { get; set; }
// construtores
public FileImpotException() {
}
public FileImpotException(string message)
: base(message) {
}
public FileImpotException(string message, Exception e)
: base(message,e) {
}
}
}
- linha 4: a classe FileImpotException deriva da classe Exception. Ela servirá para registrar qualquer erro que ocorra durante a processamento do arquivo de texto com os dados.
- linha 7: uma enumeração que representa códigos de erro:
- Acces: erro de acesso ao arquivo de texto de dados
- Ligne: linha sem os três campos esperados
- Champ1: o campo nº 1 está incorreto
- Champ2: o campo nº 2 está incorreto
- Champ3: o campo nº 3 está incorreto
Alguns desses erros podem ocorrer em combinação (Champ1, Champ2, Champ3). Por isso, a enumeração CodeErreurs foi anotada com o atributo [Flags], o que implica que os diferentes valores da enumeração devem ser potências de 2. Um erro nos campos 1 e 2 resultará, então, no código de erro Champ1 | Champ2.
- linha 10: a propriedade automática Code armazenará o código do erro.
- linha 15: um construtor que permite criar um objeto FileImpotException, passando-lhe como parâmetro uma mensagem de erro.
- linha 18: um construtor que permite criar um objeto FileImpotException, passando-lhe como parâmetros uma mensagem de erro e a exceção que originou o erro.
A classe que inicializa a matriz tranchesImpot da classe AbstractImpot agora é a seguinte:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace Chap3 {
class FileImpot : AbstractImpot {
public FileImpot(string fileName) {
// dados
List<TrancheImpot> listTranchesImpot = new List<TrancheImpot>();
int numLigne = 1;
// exceção
FileImpotException fe = null;
// leitura do conteúdo do arquivo fileName, linha por linha
Regex pattern = new Regex(@"s*:\s*");
// inicialmente sem erros
FileImpotException.CodeErreurs code = 0;
try {
using (StreamReader input = new StreamReader(fileName)) {
while (!input.EndOfStream && code == 0) {
// linha atual
string ligne = input.ReadLine().Trim();
// ignora-se as linhas vazias
if (ligne == "") continue;
// linha dividida em três campos separados por:
string[] champsLigne = pattern.Split(ligne);
// existem 3 campos?
if (champsLigne.Length != 3) {
code = FileImpotException.CodeErreurs.Ligne;
}
// conversões dos 3 campos
decimal limite = 0, coeffR = 0, coeffN = 0;
if (code == 0) {
if (!Decimal.TryParse(champsLigne[0], out limite)) code = FileImpotException.CodeErreurs.Champ1;
if (!Decimal.TryParse(champsLigne[1], out coeffR)) code |= FileImpotException.CodeErreurs.Champ2;
if (!Decimal.TryParse(champsLigne[2], out coeffN)) code |= FileImpotException.CodeErreurs.Champ3; ;
}
// erro?
if (code != 0) {
// registramos o erro
fe = new FileImpotException(String.Format("Ligne n° {0} incorrecte", numLigne)) { Code = code };
} else {
// armazenamos a nova faixa de imposto
listTranchesImpot.Add(new TrancheImpot() { Limite = limite, CoeffR = coeffR, CoeffN = coeffN });
// linha seguinte
numLigne++;
}
}
}
// a lista listImpot é transferida para a tabela tranchesImpot
if (code == 0) {
// a lista listImpot é transferida para a tabela tranchesImpot
tranchesImpot = listTranchesImpot.ToArray();
}
} catch (Exception e) {
// observa-se o erro
fe= new FileImpotException(String.Format("Erreur lors de la lecture du fichier {0}", fileName), e) { Code = FileImpotException.CodeErreurs.Acces };
}
// erro a ser relatado?
if (fe != null) throw fe;
}
}
}
- linha 7: a classe FileImpot deriva da classe AbstractImpot, assim como a classe HardwiredImpot fazia na versão 2.
- linha 9: o construtor da classe FileImpot tem a função de inicializar o campo trancheImpot de sua classe base AbstractImpot. Ele aceita como parâmetro o nome do arquivo de texto que contém os dados.
- linha 11: o campo tranchesImpot da classe base AbstractImpot é uma matriz que deve ser preenchida com os dados do arquivo filename passado como parâmetro. A leitura de um arquivo de texto é sequencial. Só se sabe o número de linhas depois de ler o arquivo inteiro. Portanto, não é possível dimensionar a matriz tranchesImpot. Os dados serão armazenados temporariamente na lista genérica listTranchesImpot.
Lembramos que o tipo TrancheImpot é uma estrutura:
namespace Chap3 {
// uma faixa de imposto
struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
- linha 14: fe, do tipo FileImpotException, serve para encapsular um possível erro de processamento do arquivo de texto.
- linha 16: a expressão regular do separador de campos em uma linha champ1:champ2:champ3 do arquivo de texto. Os campos são separados pelo caractere :, precedido e seguido por um número qualquer de espaços.
- linha 18: o código do erro em caso de erro
- linha 20: processamento do arquivo de texto com um StreamReader
- linha 21: o ciclo é repetido enquanto houver uma linha para ler e não tiver ocorrido nenhum erro
- linha 27: a linha lida é dividida em campos por meio da expressão regular da linha 16
- linhas 29-31: verifica-se se a linha possui, de fato, três campos — registra-se um eventual erro
- linhas 33-38: conversão das três cadeias de caracteres em três números decimais — registra-se eventuais erros
- linhas 40-43: se houver erro, é gerada uma exceção do tipo FileImpotException.
- linhas 44-47: se não houver erro, passa-se para a leitura da próxima linha do arquivo de texto após salvar os dados da linha atual.
- linhas 52-55: na saída da boca while, os dados da lista genérica listTranchesImpot são copiados para a tabela tranchesImpot da classe base AbstractImpot. Vale lembrar que esse era o objetivo do criador.
- linhas 56-59: tratamento de uma possível exceção. Esta é encapsulada em um objeto do tipo FileImpotException.
- linha 61: se a exceção fe da linha 18 tiver sido inicializada, ela é lançada.
O projeto C# completo é o seguinte:
![]() |
- em [1]: o projeto completo
- em [2,3]: as propriedades do arquivo [DataImpot.txt] [2]. A propriedade [Copy to Output Directory] [3] é definida como “always”. Isso faz com que o arquivo [DataImpot.txt] seja copiado para a pasta bin/Release (modo Release) ou bin/Debug (modo Debug) a cada execução. É lá que ele é procurado pelo executável.
- No [4]: faz-se o mesmo com o arquivo [DataImpotInvalide.txt].
O conteúdo de [DataImpot.txt] é o seguinte:
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54
O conteúdo de [DataImpotInvalide.txt] é o seguinte:
O programa de teste [Program.cs] não sofreu alterações: trata-se do programa da versão 2, parágrafo 4.10, com a seguinte diferença:
using System;
namespace Chap3 {
class Program {
static void Main() {
...
// criação de um objeto IImpot
IImpot impot = null;
try {
// criação de um objeto IImpot
impot = new FileImpot("DataImpot.txt");
} catch (FileImpotException e) {
// exibição de erro
string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
// interrupção do programa
Environment.Exit(1);
}
// loop infinito
while (true) {
...
}//while
}
}
}
- linha 8: objeto impot do tipo da interface IImpot
- linha 11: instanciação do objeto impot com um objeto do tipo FileImpot. Isso pode gerar uma exceção que é tratada pelo bloco try/catch nas linhas 9, 12 e 18.
Veja alguns exemplos de execução:
Com o arquivo [DataImpot.txt]
Com um arquivo [xx] inexistente
Com o arquivo [DataImpotInvalide.txt]






























