Covariância e Contravariância em C#, Parte Sete: Por que precisamos de uma sintaxe para tudo?

05/16/2008 09:12:00 By Felipe Pessoto

Suponha que estamos implementando interface genérica e variância delegada em um futura versão hipotética do C#. Como, hipoteticamente, deveria ser a sintaxe? Há muitas opções que poderíamos considerar.

Antes de pensarmos nas opções, nos perguntamos, "E se não tivermos uma sintaxe para tudo?" Por que não inferimos a variância para o desenvolvedor, de modo que tudo apenas magicamente funcione?

Infelizmente isso não existe, por várias razões.

Primeira, assim parece que a variância devia ser algo que você deliberadamente implementa em sua interface ou delegate. Fazendo isso você começa a perder o controle do objetivo que o usuário busca.

Fazer isso "automagicamente" também significa que, assim como o processo de desenvolvimento e métodos forem adicionados às interfaces, a variância da interface pode mudar inesperadamente. Isto pode introduzir inesperadas e profundas mudanças em outros pontos do programa.

Segundo, tentar fazer isso introduz um novo tipo de ciclo à análise da linguagem. Nós já temos que detectar coisas como ciclos nas classes base, ciclos na interfaces base e ciclos nas regras de tipos genéricos, então em teoria não tem nenhuma novidade. Mas na prática, há alguns problemas.

No artigo anterior não discutimos sobre restrições adicionais que nós precisamos para criar interfaces variantes. Uma restrição importante é que a interface variante que herda de outra interface variante deve fazer de uma maneira que não introduza problemas no sistema de tipos. Basicamente, todas as regras para quando um tipo de parâmetro pode ser covariante ou contravariante precisam "fluir" para a interface base.

Por exemplo, suponha que o compilador esteja tentando deduzir a variância neste programa:

interface IFrob<T> : IBlah<T> { }
interface IBlah<U>
{
    IFrob<U> Frob();
}

E nos perguntamos "é válido para T ser variante em IFrob<T>?" Para responder esta pergunta, nós precisamos determinar se é válido para U ser variante em IBlah. E para responder esta pergunta nós precisamos saber se válido para U ser variante no tipo de saída IFrob<U>, e...voltamos do ponto que começamos!

Não queremos que o compilador entre em um loop infinito quando compilar este programa. Mas esta claro que este programa é perfeitamente válido. Quando detectarmos um ciclo nas classes base, podemos parar e dizer "Seu programa é inválido". Mas não podemos fazer isso aqui. É uma questão complicada.

Terceiro, mesmo se pudéssemos descobrir uma maneira de resolver o problema do ciclo, nós ainda teríamos um problema com o caso acima. Ou seja, há três possíveis respostas logicamente consistentes: "ambas invariantes", "+T, +U" e "-T, -U" todas produzem programas que seriam typesafe. Como podemos escolher?

Podíamos começar em situações ainda piores:

interface IRezrov<V, W>
{
    IRezrov<V, W> Rezrov(IRezrov<W, V> x);
}

Nesta interface louca podemos deduzir que "ambas invariantes", <+V, -W> e <-V, +W> são todas as possibilidades. Novamente, como escolher?

E quarto, mesmo se pudéssemos resolver todos esses problemas, suspeito que o desempenho de tal algoritmo seria potencialmente muito ruim. Isto tem tudo para ter um "crescimento exponencial". Temos outros algoritmos exponenciais no compilador, mas prefiro não adicionar mais, se podemos evitar.

Assim, se adicionarmos interface e variância delegada em uma hipotética futura versão do C#, precisamos dar uma sintaxe para ela.

Covariância e Contravariância em C#, Parte Seis:Variância de Interface

05/14/2008 16:12:00 By Felipe Pessoto

Nos posts passados nós discutimos como seria possível tratar um delegate como contravariante nos seus argumentos e covariante no seu tipo de retorno. Um delegate é basicamente um objeto que representa uma chamada de função. Nós podemos fazer estes mesmos tipos de coisas para outros que representam chamadas de funções. Interfaces, por exemplo, são contratos que especificam qual o conjunto de chamadas de função que estão disponíveis em um objeto em particular.

Isto quer dizer que nós devemos extender a noção de variância para definições de interface também, usando as mesmas regras que nós temos para delegates. Por exemplo, considere:

public interface IEnumerator<T> : IDisposable, IEnumerator
{
    new T Current { get; }
}

Aqui nós temos uma interface genérica onde o único uso de parâmetro está em uma posição de saída. Poderíamos, assim, considerar o parâmetro covariante. Isso significaria que seria válido atribuir um objeto que implementa IEnumerator<Girafa> para uma variável do tipo IEnumerator<Animal>. Desde que o usuário daquela variável irá sempre esperar um Animal, e a nossa implementação irá sempre produzir uma Girafa, fica tudo ok.

Então temos IEnumerator<+T>, então nós podemos perceber que IEnumerable<T> é definido como:

public interface IEnumerable<T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

Novamente, o parâmetro aparece somente em uma posição de saída, então nós poderiamos ter IEnumerable<+T> covariante também.

Isto então abre uma janela de assassinatos de bons cenários. Hoje, este código deve falhar na compilação:

void AlimentarAnimais(IEnumerable<Animal> animals)
{
    foreach (Animal animal in animals)
        if (animal.Faminto)
            Alimentar(animal);
}
...
IEnumerable<Girafa> GirafasAdultas = from g in girafas where g.Age > 5 select g;
AlimentarAnimais(GirafasAdultas);

Porque GirafasAdultas implementa IEnumerable<Girafa>, não IEnumerable<Animal>. No C# 3.0 você tem que fazer um idiota e dispendiosa operação de conversão para fazer isto compilar, algo como:

AlimentarAnimais(GirafasAdultas.Cast<Animal>());

ou

AlimentarAnimais(from g in GirafasAdultas select (Animal)g);

Ou seja o que for. Esta declaração explícita não deveria ser necessária. Diferente de arrays (que são leitura-escrita) é perfeitamente seguro tratar uma lista de Girafas read-only como uma lista de Animais.

Do mesmo modo, poderíamos tornar:

public interface IComparer<-T>
{
    int Compare(T x, T y);
}

uma interface contravariante, desde que o tipo fosse usado somente em posições de entrada. Você poderia, então, implementar um objeto que compara dois Animais e usá-lo em um contexto onde você precisa de um objeto que compare duas Girafas sem se preocupar com problemas no sistema de tipos.

MCTS em .Net 2.0 Windows-Based Client Development

05/08/2008 14:15:00 By Felipe Pessoto

Hoje(dia 7) fiz o exame 70-526 pra concluir o MCTS que é composto de um exame sobre o .Net Framework, o 70-536(Application Development Foundation) que fiz ano passado e um eletivo que pode ser pra aplicações Windows, Web ou Distributed.

O 70-526 é o Windows-Based Client Development, e agora sim ganhei o título de MCTS. Fiz 820 pontos de 1000, sendo que pra passar você precisa de pelo menos 700.

O próximo passo é o MCPD em Designing and Developing Windows®-Based Applications Using the Microsoft® .NET Framework, exame 70-548.

Em agosto deve sair os novos exames pra VS2008, os MCPD vão poder fazer uma prova de UPGRADE, acho que vou seguir esse caminho, tentando tirar o MCPD em .Net2.0 e depois fazer o exame de atualização.

MSDN Magazine - Março e Abril 2008

05/08/2008 14:04:00 By Felipe Pessoto

Está disponível as edições de Março e Abril da MSDN Magazine:

ASP.NET MVC: Building Web Apps without Web Forms
Chris Tavares explains how the ASP.NET MVC Framework's Model View Controller pattern helps you build flexible, easily tested Web applications. Chris Tavares

Loosen Up: Tame Your Software Dependencies for More Flexible Apps
James Kovacs explains the dark side of tightly coupled architectures, why they're hard to test and how they limit adaptation. He then proposes a number of solutions. James Kovacs

CI Server: Redefine Your Build Process with Continuous Integration
Jay Flowers demonstrates how to set up and use a Continuous Integration server using both discrete tools and the more comprehensive CI Factory solution. Jay Flowers

Performance: Find Application Bottlenecks with Visual Studio Profiler
We will introduce you to the Visual Studio Profiler by walking through a sample performance investigation, pinpointing code inefficiencies in some sample applications. Hari Pulapaka and Boris Vidolov

Office Development: OBA Solution Patterns In The Real World
OBA solution patterns help architects and developers build Office Business Applications (OBAs). This article introduces the seven core OBA solution patterns and applies one to a real-world problem. Steve Fox

Download Março

Talk Back: Voice Response Workflows with Speech Server 2007
Speech Server 2007 lets you create sophisticated voice-response applications with Microsoft .NET Framework and Visual Studio tool integration. Here’s how. Michael Dunn

Performance: Scaling Strategies for ASP.NET Applications
Performance problems can creep into your Web app as it scales up, and when they do, you need to find the causes and the best strategies to address them. Richard Campbell and Kent Alstad

Silverlight: Building Advanced 3D Animations with Silverlight 2.0
Animating with Silverlight is easier than you think. Here we create a 3D app that folds a polyhedron using XAML, C#, and by emulating the DirectX math libraries. Declan Brennan

Interview++: Bjarne Stroustrup on the Evolution of Languages
Howard Dierking talks to the inventor of C++, Bjarne Stroustrup, about language zealots, the evolution of programming, and what’s in the future of programming. Howard Dierking

Office Development: Manage Metadata with Document Information Panels
Here the author uses Document Information Panels in the Microsoft 2007 Office system to manipulate metadata from Office docs for better discovery and management. Ashish Ghoda

Download Abril