Pesquisar

18 de jul de 2008

MSDN Magazine - Julho 2008

Está disponível a edição de Julho da MSDN Magazine. Assuntos de capa:

  • Serviços de dados: Desenvolva aplicativos eficientes e escalonáveis com os Serviços de Dados do SQL Server
    Aqui o autor apresenta os Serviços de Dados do SQL Server, que expõem sua funcionalidade em relação a interfaces padrão de Web service. David Robinson

  • ADO.NET: Obtenha uma modelagem de dados flexível com o Entity Framework
    A autora responde a perguntas sobre o Entity Framework e permite entender como e por que ele foi desenvolvido. Elisa Flasko

  • Dados e WPF: Personalize a Exibição de Dados com Ligação de Dados e WPF
    Aqui, apresentamos técnicas para a ligação de dados programática e declarativa e a exibição com o Windows Presentation Foundation. Josh Smith

  • Transações: Construa sistemas escaláveis que gerenciam falhas sem perder dados
    Sistemas que gerenciam falhas sem perder dados são enganosos. Aprenda como conquistar sistemas escalonáveis e eficientes. Udi Dahan

  • WCF P2P: Como projetar compartilhamento de estado em redes de mesmo nível
    Saiba como habilitar recursos ponto a ponto em aplicativos de negócios, permitindo que compartilhem estado em uma rede de mesmo nível sem servidor. Kevin Hoffman

Download em Português

17 de jul de 2008

Google Code Jam - Saving the Universe

Infelizmente meus planos de participar do Google Code Jam já eram. Marquei pra competir no dia 26(Sábado), até ai tudo ok, mas pra ajudar fizeram as eliminatórias HOJE, numa quinta-feira. Claro, como a maioria das pessoas estava trabalhando, quando cheguei ja tinha acabado o tempo...
De qualquer forma fiz o primeiro desafio, em C#, vou postar o código fonte pra vocês.

O desafio é salvar o Universo =). Segundo o Google, se você buscar pelo nome de um buscador nele mesmo(Ex.: Buscar Google, no Google) o universo vai implodir. Você recebe uma lista de Buscadores e a lista de Palavras à buscar(que vai ser sempre o nome de um dos buscadores). Então tem que gerar um algoritmo que faça que a busca, sem buscar a Palavra no Buscador com mesmo nome(pra nao implodir o Universo). Até aí nada demais, seria só um if pra fazer o switch entre qual buscador vai usar cada vez.

O desafio é fazer esse switch da forma mais otimizada possível, dizendo qual o número mínimo de switchs em cada caso, exemplos:

5
Yeehaw
NSM
Dont Ask
B9
Googol
10
Yeehaw
Yeehaw
Googol
B9
Googol
NSM
B9
NSM
Dont Ask
Googol
5
Yeehaw
NSM
Dont Ask
B9
Googol
7
Googol
Dont Ask
NSM
NSM
Yeehaw
Yeehaw
Googol

O primeiro número indica a quantidade de Buscadores, seguido pelos nomes dos mesmo, depois o número de Searchs e as respectivas Buscas.

No caso acima, devemos retornar:

Case #1: 1
Case #2: 0

Segue o código:

class Saving_the_Universe
{
    const string CaminhoEntrada = @"C:\Documents and Settings\Felipe\Meus documentos\Visual Studio 2008\Projects\CodeJam\CodeJam\Saving_the_Universe_Entrada.txt";
    const string CaminhoSaida = @"C:\Documents and Settings\Felipe\Meus documentos\Visual Studio 2008\Projects\CodeJam\CodeJam\Saving_the_Universe_Saida.txt";
 
    static List<string> Buscadores = new List<string>();
    static List<string> Querys = new List<string>();
 
    static void Main(string[] args)
    {
        StreamReader Entrada = new StreamReader(CaminhoEntrada);
        if (File.Exists(CaminhoSaida))
            File.Delete(CaminhoSaida);
 
        StreamWriter Saida = new StreamWriter(CaminhoSaida);
 
        int CasoAtual = 1;
        int CasosTotais = int.Parse(Entrada.ReadLine());
 
        string Temp = "";
 
        while (CasoAtual <= CasosTotais)
        {
            Buscadores.Clear();
            Querys.Clear();
 
            Temp = Entrada.ReadLine();
            for (int i = 0; i < int.Parse(Temp); i++)
            {
                Buscadores.Add(Entrada.ReadLine());
            }
 
            Temp = Entrada.ReadLine();
            for (int i = 0; i < int.Parse(Temp); i++)
            {
                Querys.Add(Entrada.ReadLine());
            }
 
            Saida.WriteLine("Case #{0}: {1}", CasoAtual, Algoritmo());
 
            CasoAtual++;
        }
 
        Entrada.Close();
        Saida.Close();
        Console.ReadKey(true);
    }
 
    static int Algoritmo()
    {
        Dictionary<string, int> QuerysContadas = new Dictionary<string, int>();
        //Pra cada buscador, conta quantas Querys existem chamando eles
        foreach (var item in Buscadores)
        {
            QuerysContadas.Add(item, Querys.Count<string>(p => p == item));
        }
 
        if (QuerysContadas.Values.Min() == 0)
            return 0;
 
 
        int UltimoIndice = 0;
        List<string> PalavraBuscadasNaOrdem = new List<string>();
 
        for (int i = 0; i < Querys.Count; i++)
        {
            if (PalavraBuscadasNaOrdem.Count == 0 || Querys[i] == PalavraBuscadasNaOrdem[PalavraBuscadasNaOrdem.Count - 1])
            {
                foreach (var NomeBuscador in Buscadores)
                {
                    if (Querys.IndexOf(NomeBuscador, i) == -1)
                        return PalavraBuscadasNaOrdem.Count;
 
                    if (UltimoIndice < Querys.IndexOf(NomeBuscador, i) && (PalavraBuscadasNaOrdem.Count == 0 || NomeBuscador != Querys[i]))
                        UltimoIndice = Querys.IndexOf(NomeBuscador, i);
                }
                PalavraBuscadasNaOrdem.Add(Querys[UltimoIndice]);
            }
        }
 
        return 1;
    }
}

16 de jul de 2008

Desafio I - Dynamic Iterators

Vou propor um desafio: Qual o resultado do seguinte código? E por quê?

    public class MainClass
    {
        static bool DeveMudar = false;
 
        public static void Main()
        {
            Console.WriteLine(DeveMudar);
            MetodoQualquer();
            Console.WriteLine(DeveMudar);
            Console.ReadKey();
        }
 
        public static IEnumerable<int> MetodoQualquer()
        {
            DeveMudar = true;
            yield return 1;
        }
    }

14 de jul de 2008

Design Patterns (Padrões de Projeto de Software) II: Observer

Continuando a série de artigos sobre Design Patterns, vamos discutir o Observer.

Vou novamente recorrer a definição da Wikipedia:

Define uma dependência um-para-muitos entre objetos de modo que quando um objeto muda o estado, todos seus dependentes sejam notificados e atualizados automaticamente. Isto é o padrão Observer e permite que objetos interessados sejam avisados da mudança de estado ou outros eventos ocorrendo num outro objeto.

O padrão Observer é também chamado de Publisher-Subscriber, Event Generator, Dependents.


O Observer é composto de duas partes, o Subject que é o objeto que muda de estado com uma frequência imprevisível e os Observers que devem pedir que sejam notificados das mudanças de estado do Subject.

Os Observers podem se "cadastrar" e "descadastrar" para receber essas notificações.

Veja um diagrama do pattern:

E vamos partir para os códigos:

ISubject.cs:


    public interface ISubject
    {
        void Registrar(IObserver observador);
 
        void Desregistrar(IObserver observador);
 
        void Notificar();
    }

IObserver.cs

    public interface IObserver
    {
        void Atualizar();
    }

SubjectConcreto.cs:

        private string _SubjectState = "ON";
 
        public string SubjectState
        {
            get
            {
                return _SubjectState;
            }
            set
            {
                _SubjectState = value;
            }
        }
 
        private List<IObserver> Observers = new List<IObserver>();
 
        #region ISubject Members
 
        public void Registrar(IObserver observador)
        {
            Observers.Add(observador);
        }
 
        public void Desregistrar(IObserver observador)
        {
            Observers.Remove(observador);
        }
 
        public void Notificar()
        {
            foreach (IObserver Observador in Observers)
            {
                Observador.Atualizar();
            }
        }
 
        #endregion
    }

ObserverConcreto.cs:

    public class ObserverConcreto : IObserver
    {
        private string _Nome;
        private string _ObserverState;
        private SubjectConcreto _Subject;
 
        public SubjectConcreto Subject
        {
            get { return _Subject; }
            set { _Subject = value; }
        }
 
        public ObserverConcreto(SubjectConcreto pSubject, string pNome)
        {
            this._Subject = pSubject;
            this._Nome = pNome;
        }
 
        #region IObserver Members
 
        public void Atualizar()
        {
            _ObserverState = _Subject.SubjectState;
            Console.WriteLine("Novo estado do Observer {0} é {1}", _Nome, _ObserverState);
        }
 
        #endregion
    }

Agora que temos nossas classes, vamos testa-las:

        static void Main(string[] args)
        {
            SubjectConcreto s = new SubjectConcreto();
 
            s.Registrar(new ObserverConcreto(s, "X"));
            s.Registrar(new ObserverConcreto(s, "Y"));
            s.Registrar(new ObserverConcreto(s, "Z"));
 
            s.SubjectState = "ABC";
            s.Notificar();
 
            Console.ReadKey();
        }

Iniciamos nossos Observers e já passamos como argumento no método Registrar. Quando mudamos o valor de s que é nosso Subject e Notificamos, o método Atualizar() dos Observers é executado e você verá o seguinte resultado:

No .Net também temos uma forma otimizada de fazer isso, usando delegates e events, que é muito mais simples e elegante, mas nada impede você de implementar essas classes da forma que vimos.

Há quem diga que Designs Patterns são na verdade "defeitos" das linguagens, se persarmos bem estão certos. O Observer é seria uma forma de usar delegates e events nas linguagens que não tem esses recursos, assim como vimos com o Singleton que pode ser implementado de forma muito mais simples no .Net. Até a herança que temos hoje, antigamente era um Design Pattern pra linguagens como C. Em assembly tinhamos um Pattern pra criar métodos! Que até então eram codigos que se repetiam por todo o programa, dificultando a manutenção. Mas continuando ao assunto principal...

Delegates são equivalentes aos ponteiros de função, mas orientado à objetos e "type-safe". Uma instância de delegate mantém uma referência à um método instanciado ou de classe.

Events por sua vez, são construções declaradas numa classe pra ajudar a expor mudanças de estado aos objetos interessados, que são definidos em tempo de execução.  Um event representa os métodos Registrar, Desregistrar e Notificar que usamos no Observer, mas suportado diretamente pelo compilador e pela CLR.

Os delegates são registrados pra cada event desejado em tempo de execução e quando o event é chamado, todos os delegates registrados são invocados.

Fazendo uma comparação direta com o Design Pattern Observer, a classe que declara o event, seria o Subject, mas sem precisarmos criar Interfaces. E o Observer deve instanciar um delegate passando o nome do método a ser notificado e registrá-lo ao event. Também é possível desregistrar os delegates. A notificação do Subject acontece quando o event é invocado.

Pra quem não está acostumado com delegates e events pode parecer um pouco complicado, mas na verdade é bem mais simples, pois você não tem que se preocupar com Interfaces ou classes Base. Veja um exemplo:

    public class Estoque
    {
        //Declaração do delegate
        public delegate void DelegatePerguntaPreco(decimal pPreco);
        //Declaração do event
        public event DelegatePerguntaPreco PrecoMudou;
 
        //Variável observada
        decimal _Preco;
 
        public decimal Preco
        {
            set
            {
                _Preco = value;
 
                //Dispara o event
                PrecoMudou(_Preco);
            }
        }
    }
 
    public class TelaEstoque
    {
        public void MetodoPrecoMudou(decimal pPreco)
        {
            Console.Write("O novo preço é:" + pPreco.ToString("c") + "\r\n");
        }
    }
 
    public class MainClass
    {
 
        public static void Main()
        {
            TelaEstoque objTela = new TelaEstoque();//Observer
            Estoque objEstoque = new Estoque();//Subject
 
            //Cria novo delegate e associa ao método
            //objTela.MetodoPrecoMudou do Observer
            Estoque.DelegatePerguntaPreco aDelegate = new
               Estoque.DelegatePerguntaPreco(objTela.MetodoPrecoMudou);
 
            //Adiciona o delegate ao event
            objEstoque.PrecoMudou += aDelegate;
 
            //Muda o preco 10 vezes
            for (int i = 0; i < 10; i++)
            {
                objEstoque.Preco = i;
            }
 
            //Remove o delegate do event
            objEstoque.PrecoMudou -= aDelegate;
 
            Console.ReadKey();
        }
 
    }

A vantagem dos delegates além da simplicidade é que permite que qualquer classe atue como um Observer, independente das Interfaces implementadas ou da classe Base, só precisamos criar um método com a mesma assinatura do delegate.

10 de jul de 2008

MSDN Magazine - Junho 2008

Está disponível a edição de Junho da MSDN Magazine. Assuntos de capa:

  • SAAS (software como serviço): Conecte aplicativos empresariais com serviços BizTalk hospedados
    Neste artigo, apresentamos os Serviços BizTalk, uma nova tecnologia que oferece os recursos de Barramento de Serviços Corporativos do BizTalk Server como um serviço hospedado. Jon Flanders e Aaron Skonnard

  • Simultaneidade: Ferramentas e técnicas para identificar problemas simultâneos
    Aplicativos paralelos eficientes não aparecem simplesmente executando um antigo aplicativo em um computador com processadores paralelos. É necessário fazer ajustes para aproveitar todas as vantagens. Rahul V. Patil e Boby George

  • Robótica: Simulando o mundo com o Microsoft Robotics Studio
    O Microsoft Robotics Studio não serve apenas para brincar com robôs. Ele também permite criar aplicativos baseados em serviço para uma ampla gama de dispositivos de hardware. Sara Morgan

  • Preenchimento de formulário: Crie fluxos de trabalho para capturar dados e criar documentos
    Saiba como criar um fluxo de trabalho que usa formulários InfoPath e outros documentos do Office a fim de passar dados para atividades direcionadas e para uso em documentos do Office. Rick Spiewak

  • Biblioteca GUI: A simplicidade do Windows Forms para aplicativos nativos
    Neste artigo, o autor John Torjo apresenta um guia para sua biblioteca de GUI C++ chamado eGUI++ e explica como ela torna a programação da interface do usuário mais fácil. John Torjo

Download em Português

3 de jul de 2008

Design Patterns (Padrões de Projeto de Software) I: Singleton

Design Patterns com certeza é um dos assuntos em que tenho mais interesse. Depois que você começa a estudá-los passa a pensar de forma diferente na hora de programar.

Há muito tempo vejo que cada vez que começava a escrever algum programa e o código ia crescendo e ficando mais complexo ficava muito difícil fazer alguma alteração, e testar tudo de novo. Isso quando era possível fazer alguma mudança ou extensão de forma viável.

Definição da Wikipedia:

In software engineering, a design pattern is a general reusable solution to a commonly occurring problem in software design. A design pattern is not a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that can be used in many different situations. Object-oriented design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved. Algorithms are not thought of as design patterns, since they solve computational problems rather than design problems.

Not all software patterns are design patterns. Design patterns deal specifically with problems at the level of software design. Other kinds of patterns, such as architectural patterns, describe problems and solutions that have alternative scopes.


Em português:

Na engenharia de software, um design pattern é uma solução geral reutilizável para um problema que ocorre comumente na concepção do software. Um design pattern não é um projeto acabado que possa ser diretamente transformado em código. É uma descrição ou modelo de como resolver um problema que pode ser usado em muitas situações diferentes. Design patterns em orientação à objetos tipicamente mostra os relacionamentos e interações entre classes ou objetos, sem especificar a aplicação final das classes ou objetos envolvidos. Algoritmos não são consideradas design patterns, pois eles resolvem problemas computacionais em vez de problemas de design.

Perceba que nem todos software patterns são design patterns. Design patterns lidam especificamente com problemas no nível de design de software. Outros tipos de padrões, como architectural patterns, descrevem problemas e soluções que tem escopos alternativos.


Vamos começar pelo mais simples deles, o Singleton. Que apesar de ser simples tem alguns problemas no qual precisamos ficar atentos, no caso com Multi-Thread.

O padrão Singleton deve garantir que uma classe tenha uma única instância e proporcione um ponto de acesso global.
Um exemplo de uso é no caso de um objeto de log, deixando uma única instância responsável por gerenciar o log.

Vemos aqui um diagrama de classe do Singleton:

Diagrama de Classes


Perceba que temos um construtor private, pois não podemos deixar a classe Cliente criar novas instâncias do Singleton.

Agora apresentando o código, a princípio poderíamos criar um código bem simples como esse:

public static Singleton Instancia

{

    get

    {

        if (_Instancia == null)

            _Instancia = new Singleton();

 

        return _Instancia;

    }

}



Mas teríamos um problema, se um thread A estiver acessando o Singleton pela primeira vez e logo após o if (_Instancia == null) fosse interrompido e então um thread B poderia passar também pela condição, já que o Singleton ainda é null e então instanciar o objeto. Quando o thread A voltasse, ele estaria dentro do if, e criaria outra instância do objeto, o que não é desejado nesse deisgn pattern.

Para resolver este problema, podemos fazer um lock sobre a classe, garantindo que apenas um thread passe pelo lock. Adicionalmente criamos outro if antes do lock, para que depois que a classe estiver devidamente instanciada, não criarmos lock´s toda vez que alguém acessar a Instancia, o que degradaria a performance.
Este modelo se chama "Double Check Lock". Neste modelo, mesmo que dois ou mais threads passem pelo primeiro if, apenas um thread pode passar pelo lock por vez, então se o thread A passar pelo lock primeiro, ele vai verificar no segundo if se o objeto já foi instanciado, se não foi ele irá instanciá-lo. Quando o thread A terminar, irá liberar o lock, e o thread B então continua seu trabalho entrando no lock, mas desta vez ele não conseguirá passar pelo segundo if, pois o thread A já instanciou o objeto.

public static Singleton Instancia

{

    get

    {

        if (_Instancia == null)

        {

            lock (typeof(Singleton))

            {

                if (_Instancia == null)

                    _Instancia = new Singleton();

            }

        }

        return _Instancia;

    }

}



Geralmente quem olha esse código imagina que já temos o necessário, e realmente é o que parece. Mas temos um outro problema que não é visível no código.
Quando o compilamos nosso código, várias otimizações são feitas no código assembly(no caso IL, mas o modelo é aplicável a qualquer linguagem como C++) e essa otimização pode comprometer a integridade do nosso modelo. Pra entender como isso acontece vamos revisar como um objeto é instanciado:

1º - A memória(no Heap) é alocada para guardar o objeto Singleton.
2º - O Singleton é construído dentro do espaço de memória alocado.
3º - O endereço da memória alocada é atribuído à Instância(no Stack).

*Não vou explicar como funciona o Heap e Stack nesse artigo para não perdemos o foco.

Mas o compilador pode reordenar essas instruções quando faz a otimização do código, e o problema aparece quando o passo 2 e 3 são trocados:

1º - A memória(no Heap) é alocada para guardar o objeto Singleton.
2º - O endereço da memória alocada é atribuído à Instância(no Stack).
3º - O Singleton é construído dentro do espaço de memória alocado.

Usando o mesmo exemplo, se o thread A for interrompido depois de passar pelo 2º passo, o thread B ao chegar no if (_Instancia == null) irá receber false, pois _Instancia não tem mais o valor null, já foi atribuído um endereço de memória à ele, então o objeto _Instancia será retornado do jeito que está e quando sua classe Cliente for usá-lo...teremos um erro! O objeto ainda não existe, apenas o espaço alocado na memória.

Para resolvermos este problema no C# podemos usar a keyword volatile, que diz ao processador que não deve reordenar as instruções e deixar a otimização de lado:

public class Singleton

{

    private static volatile Singleton _Instancia = null;

 

    private Singleton()

    {

    }

 

    public static Singleton Instancia

    {

        get

        {

            if (_Instancia == null)

            {

                lock (typeof(Singleton))

                {

                    if (_Instancia == null)

                        _Instancia = new Singleton();

                }

            }

            return _Instancia;

        }

    }

 

    public static void MetodoUm()

    {

    }

 

    public static void MetodoDois()

    {       

    }

}



Em .Net temos uma outra opção, muito mais simples e elegante, mas essa forma é particular do .Net não tendo nada parecido em outras linguagens:

sealed class Singleton

{

    private Singleton() { }

    public static readonly Singleton Instancia = new Singleton();

}


Pra começar mudamos o comportamento da classe, que tinha um "lazy initialization" e o removemos. Não estamos mais usando uma propriedade e sim dando acesso direto ao objeto Singleton, além de marcá-lo como readonly e instanciarmos na declaração.
Então esse método é pior? Perdemos a vantagem do "lazy initialization" e estamos sobre o perigo do acesso em Multi-Thread? Na verdade não, estamos apenas passando a responsabilidade pro .Net.

Sobre o "lazy initialization", antes fizemos uma propriedade que na primeira vez que era chamada criava a instância do objeto. Esse comportamento é o "lazy initialization", só instanciar o objeto quando ele for necessário, se for. O .Net faz isso pra gente, o JIT é esperto o suficiente pra instanciar a propriedade static quando e somente quando alguém usá-la.

E sobre a inicialização Thread-Safe? O readonly cuida disso, já que uma das regras sobre o readonly é que ele só possa ser inicializado de forma estática, o que só vai acontecer uma vez.

Por fim declaramos a classe como sealed, pois não é uma boa prática herdar de um Singleton, além de que se você precisar de outra implementação de Singleton é muito fácil fazer do zero, e evita muitos problemas.

Espero ter sido claro o suficiente, qualquer dúvida é só deixar um comentário. Até o próximo artigo.

2 de jul de 2008

Covariância e Contravariância em C#, Parte Onze: Ao infinito, mas não além

Como discutido ao longo desse espaço, estavamos considerando adicionar Covariância e Contravariância em tipos de parâmetros de delegates e interfaces para uma futura versão do C#.

Variância é extremamente útil em muitos casos, mas expõe um problema irritante em certos casos raros e bizarros. Este é apenas um exemplo:

Considere uma interface "normal" contravariante em seu único parâmetro de tipo genérico, e uma interface "louca" invariante que herda a interface normal em uma forma estranha:

public interface IN<in U> {}
public interface IC<X> : IN<IN<IC<IC<X>>>> {}

Isto é um pouco estranho, mas com certeza válido.

Antes de continuarmos, vamos entender porque isto é válido. A maioria das pessoas quando ve algo como isso imediatamente diz "mas uma interface não pode herdar de si mesma, isso é inválido, é uma cadeia circular na herança!"

Primeiro, não, isso não é correto. Em nenhum lugar a especificação do C# faz este tipo de herança inválido, e de fato, uma forma mais simples disso deve ser válida. Você deve ser capaz de dizer:

interface INumber<X> : IComparable<INumber<X>> { ... }

ou seja, você deve ser capaz de expressar que uma das garantias do contrato de INumber<X> é que você possa sempre comparar um número com outro. Portanto, deve ser válido usar um nome de tipo no argumento de tipo do tipo generic do pai.

No entanto, nem tudo é perfeito. Este tipo particular bruto de herança que dei como exemplo é de fato inválido no CLR, mesmo que não seja inválido no C#. Isso significa que é possível ter um tipo de interface compilada em C# que não pode ser carregada pelo CLR. Isto infelizmente irá gerar problemas, e espero que em uma versão futura do C# faça as definição das regras do C# mais rígidas ou até mais rígidas que as do CLR. Até lá, evite fazer isso.

Segundo, infelizmente, o compilador de C# atualmente tem vários bugs no seu detector de ciclo, como algumas vezes em que coisas se parecem com ciclos mas não são realmente, e são marcados com erros de ciclos. Isto só torna ainda mais difícil a compreensão do que é um ciclo válido e o que não é. Por exemplo, hoje o compilador irá incorretamente dizer que isto é um ciclo de classe base inválido, mesmo que claramente não é:


    public class November<T> { }
    public class Romeo : November<Romeo.Sierra.Tango>
    {
        public class Sierra
        {
           public class Tango { }
        }
    }

De qualquer forma, voltando ao assunto: variância louca. Nós temos as interfaces definidas acima, e então damos ao compilador um pequeno enigma pra resolver:

IC<double> bar = qualquer;
IN<IC<string>> foo = bar;  // Esta atribuição é válida?

Estamos prestes a entrar em um nome generic praticamente impossível de ler, então pra ficar mais fácil, a partir de agora vamos abreviar IN<IC<string>> como NCS. IC<double> será abreviado como CD.

Do mesmo modo, vou abreviar "é convertível de forma implícita" por uma seta. Então a questão é: CD→NCS é true ou false?

Vamos ver. Claramente CD não se converte em NCS diretamente. Mas (por motivos do compilador) talvez o tipo base de CD’s consiga.

O tipo base de CD’s é NNCCD. Será que NNCCD→NCS? Bem, N é contravariante em seus parâmetros assim isto se resume a questão, CS→NCCD?

Claramente não diretamente. Mas talvez CS tenha um tipo base que converta em NCCD. O tipo base de CS é NNCCS. Agora nós temos a questão NNCCS→NCCD ?

N é contravariante em seus parâmetros, agora caímos em CCD→NCCS ?

O compilador vai "reduzindo" o problema se CD→NCS é verdadeiro à CCD→NCCS! Se mantermos uma "redução" como esta então teremos CCCD→NCCCS, CCCCD→NCCCCS, e assim por diante.

Isto é apenas uma fração de algumas das formas em que o sistema de tipos pode ficar estranho. Para se aprofundar mais nesse assunto, você pode ler o Microsoft Research paper.

Parte Anterior: Covariância e Contravariância em C#, Parte Dez: Lidando Com Ambiguidades