Problemas em arquivos de dados

by Israel Aece 29. October 2009 21:08

Até agora não entendi o motivo, mas repentinamente os arquivos que representam as estruturas de classes do LINQ To SQL e do Entity Framework deixaram de funcionar, ou melhor, o Visual Studio .NET deixou de exibir graficamente a estrutura de classes. O Server Explorer deixou de ser exibido; quando tentava criar um novo arquivo EDMX, o wizard simplesmente desaparecia; e quando tentava criar e/ou carregar um arquivo do LINQ To SQL, a seguinte mensagem era exibida: The operation could not be completed. The custom tool 'MSLinqToSQLGenerator' failed.  Could not retrieve the current project.

Depois de algumas pesquisas, cheguei à um blogue que dizia para excluir as sub-chaves que existiam dentro do seguinte path: HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Packages. Basicamente o que tinha ali é uma chave chamada SkipLoading, que estava definida como "1". Antes de excluir, eu simplesmente mudei para "0", e tudo voltou a funcionar.

Tags: ,

Data

Granularidade de Serviços

by Israel Aece 29. October 2009 15:03

Independentemente de qual tecnologia estamos utilizando, uma das grandes perguntas que nos fazemos ao criar serviços, é o que realmente devemos disponibilizar em cada um deles, que ao meu ver, vai muito além os parâmetros e do resultado que cada operação recebe e/ou retorna. Existe uma série de aspectos que devemos nos atentar ao projetar ou construir um conjunto de serviços, que muitas vezes atenderão as aplicações que rodam dentro dessa mesma companhia.

O primeiro aspecto que temos que verificar é a questão da granularidade dos serviços, que é utilizada para mensurar a profundidade de abstração que foi aplicado. A granularidade pode ser dividida em duas partes, sendo: granularidade fina (fine-grained) e granularidade grossa (coarse-grained), onde granularidade fina determina que precisamos de muitos "grãos", enquanto na granularidade grossa, teremos poucos "grãos", bem maiores.

O que eu quero mostrar com o parágrafo acima, é que com a granularidade fina, teremos serviços com poucas operações, mas dividiremos essas operações por vários serviços. Já com a granularidade grossa, isso se inverte, ou seja, teremos poucos serviços, mas cada um deles conterá uma porção bem maior de operações. Cada uma das técnicas tem suas vantagens e desvantagens, e ao meu ver, quando temos uma granularidade fina, temos pequenos "blocos" de funcionalidades bem específicas e muitas vezes independentes, e que ficam bem mais fáceis de serem atualizadas, distribuídas e gerenciadas, mas isso pode se tornar complexo demais para aqueles que consomem os serviços, já que terão que compor e sincronizar suas operações, para que atinja um determinado objetivo. Por outro lado, a granularidade grossa pode tornar os serviços mais auto-suficientes, mas o problema disso é que cada serviço poderá, acidentalmente, fazer muito mais trabalho do que ele realmente deveria, e que muitas vezes precisará recorrer à outros serviços, para que, também, atinjam o seu objetivo, aumentando assim o acoplamento.

Acoplamento é um outro problema que pode ocorrer, que nada mais é do que a - forte - dependência que um serviço tem de outro. Isso infringe um dos princípios do SOA, que diz que os serviços precisam ser autônomos, ou seja, não depender de outros serviços. Mas em algumas situações isso pode ser benéfico, principalmente em um ambiente de composição. Imagine que você queira criar um serviço para resolver um problema maior, com uma complexidade muito grande. Ao invés de todos os consumidores acessarem esses serviços e ficar sob responsabilidade de cada um organizar isso, você poderá criar um serviço para compor essas tarefas que, como dissemos acima, precisarão recorrer à outros serviços.

Esses conceitos que vimos acima não são novidades. Eles já são (ou deveriam ser) aplicados na programação orientada à objetos, exatamente para termos os mesmos benefícios. Essas características não são exclusividades da computação, pois podemos adotar esses mesmos princípios no nosso dia-à-dia.

Outro grande ponto a ser considerado na construção de serviços, é a criação de serviços com interfaces CRUD (Create, Read, Update e Delete). A proposta destes tipos de serviços é permitir, na maioria das vezes, a manipulação de registros dentro de uma determinada base de dados. Nestes casos, o serviço será apenas uma espécie de wrapper para os dados, não fazendo nada além do que as operações básicas que todo banco de dados possui (INSERT, SELECT, UPDATE e DELETE).

Quando você trabalha com uma aplicação data-centric, onde a toda a regra se concentra em manipular a base de dados, talvez esses tipos de serviços sejam úteis. Considere aqui o uso do ADO.NET Data Services, que evitará conhecer e criar toda a estrutura necessária para expor via WCF.

Mas o ideal é não ter isso em mente ao construir serviços. Interfaces (contratos) CRUD induzem à granularidade fina, onde cada serviço será responsável por manipular uma determinada entidade/tabela. Como vimos acima, a granularidade fina em si não é o problema. A questão aqui é que muitas vezes, um cadastro de um cliente não consiste apenas em um INSERT na base de dados, ao contrário, vai muito além disso.

Imagine um novo cliente que deseja abrir uma conta bancária em um determinado banco, com um cartão de crédito vinculado. O processo de cadastro do cliente consistirá em:

  1. Validar os dados, como idade, renda, endereço, etc.;
  2. Consultar outras instituições financeiras para se certificar de que ele é um bom pagador;
  3. Definir o limite que ele terá no cheque especial;
  4. Inserir o cliente na base de dados;
  5. Criar a conta corrente para este cliente;
  6. Efetuar o lançamento da taxa de cadastro/abertura na conta corrente recém criada;
  7. Comunicar com o serviço de cartões de crédito, para que ele gere um novo cartão para este cliente;
  8. Notificar outros departamentos do banco de que uma nova conta foi aberta, para oferecimento de novos produtos.

Como podemos perceber, o cadastro de um novo cliente não consiste apenas em adicioná-lo na base de dados. Há muito mais do que isso. Se modelarmos nossos serviços orientado à dados, eu teria um serviço que manipula os clientes, outro serviço que manipula a conta corrente, outro de notificação e por aí vai. Quanto mais entidades/tabelas você tiver envolvidas em uma mesma tarefa, mais complicado ficará para gerenciar tudo isso, principalmente do ponto de vista daquele que consumirá esses serviços.

O consumo de serviço é um processo caro para se fazer à todo momento, e neste cenário, para efetuar o cadastro de um cliente, eu precisarei chamar, no mínimo, quatro serviços e tudo o que eu precisarei passar para eles, é exatamente os dados do cliente que eu desejo avaliar/cadastrar. Outro ponto importante é com relação ao fluxo de informações. Neste caso, fica sempre sob responsabilidade do cliente que consome esses serviços, configurar a ordem de chamadas, e em um ambiente onde múltiplas aplicações podem incluir clientes, eventualmente uma delas poderá alterar essa ordem, fazendo com que o processo fique em um estado inválido, comprometendo assim a veracidade e consistência das informações. Nada impedirá que uma pessoa maliciosa invoque apenas o serviço de cadastro de cliente diretamente, sem passar pelas políticas de validação necessárias.

Quando temos várias "sub-tarefas" que se juntam para algo maior, em muitos casos queremos garantir a atomicidade, que garantirá que todos os passos sejam efetuados com sucesso, ou tudo falhará. O que garante a atomicidade são as transações, e transações distribuídas são caras, e todas as chamadas para esses serviços devem estar envolvidas dentro dessa transação, que será, também, coordenada pelo cliente, que será o responsável por avaliar se tudo deu certo. Se sim, ele efetivará (Commit), do contrário, irá desfazer (Rollback).

Um segundo cenário que também ilustra isso: você possui clientes e cada um deles possui um flag que determina a situação dele dentro da sua empresa: Ativo, Bloqueado, EmProcessoJuridico, etc. Da mesma forma que vimos antes, alterar situação dele vai muito além de um simples comando de UPDATE na base de dados. Quando eu mover um determinado cliente para a situação de EmProcessoJuridico, eu terei que inserir um item no histórico deste cliente, desativar algumas opções que o mesmo tem site, e alterar a sua situação na tabela do banco de dados. Se movê-lo para Bloqueado, terei que inserir um item no seu histórico, efetuar um lockdown em todas as contas de acesso desse cliente no site, notificar o gerente responsável e, finalmente, efetuar o UPDATE da coluna onde armazeno a situação atual na tabela de clientes.

Como podemos perceber, interfaces CRUD definem os serviços como sendo data-centric, que modelando dessa forma, nós perderemos o contexto de negócio que será executado pelo cliente, tornando bem mais complicado de se entender o processo como um todo. Ao modelar os serviços, o ideal seria pensar em task-centric, ou seja, o serviço fornecerá operações que englobam grande parte do processo, não sendo o consumidor o responsável por isso. Nos dois cenários que vimos acima, teríamos um serviço de cliente, e que me forneceria uma operação para criar um novo cliente dentro do banco (IncluirNovoCliente), outra operação para bloquear o cliente (Bloquear), outro para criar um processo contra este cliente (AbrirProcessoJuridico), que o moverá para a situação EmProcessoJuridico, e assim por diante.

Note que as operações que serão expostas pelo serviço expressarão claramente o negócio, fazendo internamente tudo o que for necessário para atingir o respectivo objetivo. O interessante é que neste caso, não temos o overhead de chamar N serviços, problemas de fluxo e, principalmente, evitando transações distrubuídas.

Claro que poderá haver situações em que, mesmo que você utilize o modelo task-centric, os teus serviços estarão, coincidentemente, alinhados à interfaces CRUD, mas o importante é que isso apenas seja uma coincidência e não uma regra. Isso muitas vezes acontece quando as regras não estão tão aparentes. Por exemplo, se você mantém um cadastro de cidades e permite a alteração delas, provavelmente você poderá ter: 1 - Valinhos e 2 - Campinas. Os clientes cadastrados na sua base de dados e que são de Valinhos guardam o número 1, e os de Campinas, o número 2. Se agora, você diz que Valinhos será o 2 e Campinas o 1, você corromperá todos os clientes que fazem uso dessas cidades. Poderia haver aqui uma regra que, ao efetuar a alteração da cidade (swap), você atualizasse todos os clientes relacionados.

É importante dizer que serviços task-centric, em algum momento, precisarão efetuar operações de CRUD dentro da base de dados. Com isso, podemos criar duas categorias de serviços: Task Service e Entity Service. Task Services são os tipos de serviços que vimos acima, que serão responsáveis por orquestrar toda a regra de validação, fluxo, manipulação e persistência das informações. É neste ponto que entra em cena os Entity Services, que são responsáveis por gerenciar a vida/estado de uma entidade específica, incluindo seus respectivos relacionamentos, tendo esses serviços, uma interface semelhante à interface CRUD.

Os Entity Services levam o nome de uma entidade, como por exemplo: Cliente, ContaCorrente, PoliticasDeValidacao, etc., não fazendo nada além do que o nome diz, ou seja, disponibilizará apenas as informações referentes à respectiva entidade, não conhecendo nada sobre negócios. Já os nomes dos Task Services são voltados para o negócio em si: AdministracaoDeClientes, GestorDeCredito, CobrancaDeTitulos, etc. E ainda, os Task Services poderão utilizar um ou vários Entity Services para executar uma determinada tarefa, atentanto-se sempre aos conceitos que vimos acima, como é o caso do baixo/alto acoplamento e a granularidade.

Tudo o que foi falado aqui poderá, em alguns cenários, não ser a melhor opção, mas utilizar esse tipo de visão para a construção de serviços, ajudará a ter e criar uma representação muito mais consolidada de sua estrutura, e que será relativamente fácil de gerenciar, mas que refletirá, virtualmente, o seu negócio.

Tags:

CSD

Nova versão do método Monitor.Enter

by Israel Aece 26. October 2009 14:48

Desde a primeira versão do .NET Framework, temos a keyword lock. Com ela, podemos criar um bloco de código que será acessado por uma única thread de cada vez. Na verdade, depois de compilado, esse bloco é transformado em chamadas para os métodos Enter e Exit, respectivamente, expostos pela classe Monitor (System.Threading), e envolvido por um bloco try/finally. Imagine o seguinte código: 

lock (_meuLock)
{
    //código "protegido"
}

Ao ser compilado, esse código será transformado em:

Monitor.Enter(_meuLock);
try
{
    //código "protegido"
}
finally
{
    Monitor.Exit(_meuLock);
}

O problema disso é que, segundo o Joe Duffy, exceções podem acontecer entre o método Enter (que é responsável por adquirir o lock) e o bloco try; isso dará origem ao que ele chama de "bloqueios orfãos", já que nunca serão liberados, pois o bloco finally não será disparado.

Para resolver isso, a Microsoft criou uma nova versão (overload) do método Enter, que além do objeto que representa o lock, recebe um parâmetro boleano indicando se o lock foi ou não adquirido com sucesso. O valor retornado por esse parâmetro boleano, será avaliado mais tarde, que determinará se o método Exit correspondente deverá ou não ser invocado. Além dessa mudança, o método Enter passa a ser chamado dentro do bloco try, garantindo assim que o bloco finally seja disparado e, consequentemente, liberando o lock. O mesmo código acima, a partir da versão 4.0 do .NET Framework, passa a ser compilado da seguinte forma:

bool taken = false;
try
{
    Monitor.Enter(_meuLock, ref taken);
    //código protegido
}
finally
{
    if (taken)
        Monitor.Exit(_meuLock);
}

Tags:

.NET Framework

Novas classes para inicialização de objetos

by Israel Aece 21. October 2009 06:57

Quando desenvolvemos algum tipo de aplicação ou componente, é muito comum encontrarmos dentro do nosso código, classes que são extremamente custosas, ou melhor, que possuem um grande overhead na inicialização, como por exemplo, efetuam acesso à IO, cálculos complexos, etc. Dependendo da situação, instanciamos essas classes (pagando o alto preço da inicialização) e não utilizamos, já que, eventualmente, o teu sistema não precisará dela naquele momento.

Para melhorar isso, a Microsoft está disponibilizando no .NET Framework 4.0, uma classe genérica chamada Lazy<T> (namespace System). Basicamente, a finalidade desta classe é postergar, ao máximo, a criação do teu objeto, ou seja, isso somente acontecerá quando você realmente precisar dele. Por ser uma classe genérica e o parâmetro T não ter qualquer restrição, você pode definir T como qualquer tipo. Essa classe será um wrapper para o teu objeto custoso, efetuando a criação do mesmo somente quando for requisitado.

Ao instanciar a classe Lazy<T>, você tem algumas opções que variam de acordo com o overload do construtor que utiliza. Em um dos construtores, há um parâmetro boleano, que determina se a inicialização será ou não thread-safe. Se a instância da classe Lazy<T> pode ser acessada por um ambiente multi-threading, então definir este valor como True (que é o padrão), evitará problemas conhecidos, tal como as races conditions. Com isso, a primeira thread que entrar, incializará o objeto, e as threads subsequentes compartilharão o mesmo objeto, que já está criado. Mas se ambiente multi-threading não é o cenário, então definir esse parâmetro como False evitará processamentos extras, que são desnecessários neste caso.

Há também um overload do construtor, que recebe como parâmetro a instância de um delegate do tipo Func<T>. Esse delegate é referido como uma "factory", ou seja, apontará para um método responsável por criar o objeto quando for solicitado, nos permitindo inicializá-lo de acordo com uma regra específica. Quando esse parâmetro não é informado, a classe Lazy<T> irá instanciar o tipo através do método CreateInstance da classe Activator, obrigando o tipo definido em T, a ter um construtor público sem parâmetros, caso contrário, uma exceção será disparada.

Além dos construtores, essa classe ainda expõe, publicamente, duas propriedades de somente leitura: IsValueCreated e Value. A primeira delas, retorna um valor boleano indicando se o objeto já foi ou não criado. Já a segunda, é a propriedade que utilizamos para extrair o objeto que foir criado e está sendo gerenciado pelo wrapper. É dentro desta propriedade que há toda a regra utilizada para determinar se o objeto já foi criado. E como vimos acima, caso ele ainda não tenha sido, invocará o método privado LazyInitValue, e me retornará a instância. Chamadas subsequentes, da mesma thread ou não, não entrarão mais neste método, reutilizando a instância criada. O código abaixo exibe um exemplo da utilização desta classe:

public class NotaFiscal
{
    public int Codigo { get; set; }
    public DateTime Data { get; set; }

    private Lazy<List<Item>> _itens;

    public NotaFiscal(int codigo)
    {
        this.Codigo = codigo;
        this._itens =
            new Lazy<List<Item>>(() => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));
    }

    public IEnumerable<Item> Itens
    {
        get
        {
            return this._itens.Value;
        }
    }
}

Como podemos perceber no código acima, a instância da classe representa uma Nota Fiscal. Muitas vezes carregamos a Nota Fiscal completa, incluindo seus respectivos itens, mas nem sempre eles são utilizados. Ao invés de carregar esses itens na criação da Nota Fiscal, iremos postergar essa tarefa, recorrendo a classe Lazy<T>. Como podemos ter vários itens, então o argumento T será definido como List<Item>. No construtor da classe Nota Fiscal, instanciamos a classe Lazy<List<Item>>, definindo em seu construtor, o método responsável por carregar os itens da Nota Fiscal. A propriedade que expõe os itens da Nota Fiscal, quando solicitada, recorre a propriedade Value do objeto _itens, que como vimos acima, é neste momento que o método (factoryRecuperarItensDaNotaFiscal será disparado.

Além da classe Lazy<T>, ainda temos a classe ThreadLocal<T> (namespace System.Threading). Assim como a anterior, não há nenhuma restrição quanto ao argumento T, ou seja, podemos definir qualquer tipo. A finalidade desta classe, é sanar alguns comportamentos de campos estáticos quando utilizados em conjuto com o atributo ThreadStaticAttribute. Ao aplicar esse atributo, cada thread terá a sua própria cópia do valor, mesmo que ele seja declarado como estático (static). O problema que ocorre ao utilizar esse atributo, é quando temos um campo que já é automaticamente inicializado, como por exemplo:

public class Teste
{
    [ThreadStatic]
    public static int Numero = 4;
}

A inicialização de todo membro estático ocorre apenas uma única vez, mesmo quando temos este atributo aplicado. Para ilustrar isso, vamos criar três threads diferentes, onde cada uma delas escreverá o valor do membro Numero:

new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();
new Thread(() => Console.WriteLine(Teste.Numero)).Start();

E o resultado é: 4, 0 e 0. A classe ThreadLocal<T> vai conseguir lidar com isso, ou seja, permitirá especificar um delegate de inicialização, que será invocado sempre que o valor for requisitado por uma nova thread. Ao invés da inicialização acontecer uma única vez, ele sempre rodará quando solicitado, e sempre trazendo a cópia da informação para dentro da thread corrente, não compartilhando o mesmo, assim como já acontecia anteriormente. Com essa nova classe, podemos reescrever o exemplo da seguinte forma:

public class Teste
{
    public static ThreadLocal<int> Numero = new ThreadLocal<int>(() => 4);
}

new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();
new Thread(() => Console.WriteLine(Teste.Numero.Value)).Start();

E agora, como já era de se esperar, temos como resultado: 4, 4 e 4. É importante dizer que se nada for informado no construtor desta classe, ela sempre construirá o tipo com o seu valor padrão.

Para finalizar, temos a classe estática LazyInitializer, que possui apenas um único método público: EnsureInitialized. Neste caso, ao invés de definir todos os membros como Lazy<T>, podemos recorrer a este método para inicializá-los individualmente, quando você achar necessário, sem a necessidade do wrapper. O código abaixo ilustra a utilização desta técnica, mas repare que informamos o objeto que será abastecido e o método (factory) que o construirá.

public IEnumerable<Item> Itens
{
    get
    {
        if (this._itens == null)
            LazyInitializer.EnsureInitialized(ref _itens, () => DataHelper.RecuperarItensDaNotaFiscal(this.Codigo));

        return this._itens;
    }
}

Conclusão: Criar aplicações multi-threading é bem simples, mas o grande problema sempre é a sincronização delas. Classes como essas que vimos aqui, auxilia bastante neste caso, tirando em algum pontos, a responsabilidade do usuário em gerenciar isso, podendo ele se preocupar cada vez mais com as regras de negócio. Essas classes que vimos aqui, estarão disponíveis a partir da versão 4.0 do .NET Framework, que já trará também uma grande API para suportar o desenvolvimento de código paralelo.

Tags:

.NET Framework

Persistência da linha selecionada

by Israel Aece 20. October 2009 09:22

Quando temos a paginação e a seleção no GridView habilitadas, há um comportamento estranho. Ao selecionar uma linha em um GridView paginado, o ASP.NET armazena o índice da linha selecionada, e ao mudar de página, a mesma linha (porém outro registro) fica também marcada, e isso muitas vezes gera uma confusão.

Na versão 4.0 do ASP.NET, a Microsoft está adicionando uma propriedade boleana no GridView chamada EnablePersistedSelection. Ao definir como True, ele armazenará as datakeys ao invés do índice da linha no GridView, e com isso, ao mudar de página, nenhuma linha é marcada como selecionada, o que faz mais sentido. Ao voltar para a página que possui aquele registro, conseguimos visualizá-lo como marcado. Por padrão, essa propriedade é definida como False para efeitos de compatibilidade com as versões anteriores.

Tags:

ASP.NET

Ativação do host baseando-se na memória

by Israel Aece 20. October 2009 09:03

O WCF fornece uma opção que nos permite configurar a porcentagem mínima de memória disponível na máquina antes de ativar o serviço. Para customizar isso, recorremos a propriedade (atributo) MinFreeMemoryPercentageToActivateService da classe (elemento) ServiceHostingEnvironmentSection. Essa propriedade recebe um número inteiro, que corresponde a quantidade de memória disponível que a máquina onde o serviço está rodando deverá ter disponível.

Como essa configuração recorre a métodos não gerenciados para fazer essa verificação, a aplicação (serviço) deverá estar rodando em full-trust. Caso contrário, uma exceção do tipo SecurityException será disparada e, consequentemente, o serviço não estará disponível para receber requisições.

Tags: , ,

WCF

Novos métodos para Enumeradores

by Israel Aece 20. October 2009 07:27

A partir da versão 4.0 do .NET Framework, teremos dois novos métodos estáticos para a manipulação de enumeradores fornecidos pela classe Enum: TryParse<TEnum> e HasFlag.

Como podemos perceber, o primeiro deles, TryParse<TEnum>, trata-se de um método genérico, que recebe dois parâmetros. O primeiro deles, é uma string contendo o nome ou valor do item a ser pesquisado dentro do enumerador, já informado pelo argumento genérico TEnum; já o segundo parâmetro (de saída), é do tipo TEnum, pois caso a conversão seja possível, esse resultado já traz a informação tipada. E ainda, este método retorna um valor boleano, indicando se a conversão foi ou não possível, ao contrário do método Parse, que dispara uma exceção. Esse método trabalha de forma semalhante ao método TryParse das estruturas DateTime, Int32, entre outras que já fazem parte do .NET Framework. Abaixo temos o exemplo da sua utilização:

public enum Itens { Read, Write, FullControl, None }

Itens permissoes = Itens.None;

if (Enum.TryParse<Itens>("Read", out temp).ToString())
    Console.WriteLine("Nível de Permissão: {0}", temp);
else
    Console.WriteLine("Não foi possível determinar o tipo de Permissão.");

Além desse método, a Microsoft também está adicionando o método de instância chamado HasFlag, que retorna uma valor boleano indicando se o valor existe ou não dentro daquele enumerador. Com ele, a partir da variável que armazena o(s) enumerador(es) selecionado(s), podemos invocá-lo e passarmos o item a ser verificado, assim como podemos reparar no exemplo abaixo:

Itens permissoes = Itens.Read | Itens.Write;

if (permissoes.HasFlag(Itens.FullControl))
    Console.WriteLine("Você tem permissão total.");

Tags:

.NET Framework

Copiando Streams

by Israel Aece 20. October 2009 07:02

Uma operação muito comum é a cópia de streams. Há diversas situações onde você pode querer usar isso, como por exemplo, armazenar um conteúdo de um arquivo em memória, para não precisar acessar o arquivo físico toda vez que precisar dele. Antes do .NET Framework 4.0, com o stream de origem em mãos, precisávamos armazenar os blocos em um buffer temporário, e em seguida, copiar para o stream de destino.

Apesar de não ser uma tarefa muito difícil de fazer, ela é propícia a erros, já que você precisa ficar manipulando índices para invocar os métodos Read e Write. Com o .NET Framework 4.0, a Microsoft acaba de adicionar um método (de instância) chamado CopyTo na classe Stream. Com ele, todas as classes que derivam direta ou indiretamente dela, poderão copiar o conteúdo atual para um outro Stream. Tudo o que precisamos fazer é:

MemoryStream ms = new MemoryStream();

using (FileStream fs = File.Open("Arquivo.xml", FileMode.Open)
    fs.CopyTo(ms);

Tags:

.NET Framework

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host