Como tenho experiência em C # e Java, estou acostumado a minhas listas serem homogêneas, e isso faz sentido para mim. Quando comecei a pegar o Lisp, notei que as listas podem ser heterogêneas. Quando comecei a brincar com a dynamic
palavra - chave em C #, notei que, a partir do C # 4.0, também havia listas heterogêneas:
List<dynamic> heterogeneousList
Minha pergunta é qual é o objetivo? Parece que uma lista heterogênea terá muito mais sobrecarga ao processar e, se você precisar armazenar tipos diferentes em um único local, poderá precisar de uma estrutura de dados diferente. Minha ingenuidade está criando uma cara feia ou há realmente momentos em que é útil ter uma lista heterogênea?
List<dynamic>
diferença (para sua pergunta) de simplesmente fazerList<object>
?Respostas:
O artigo Strongly Type Heterogenous Collections de Oleg Kiselyov, Ralf Lämmel e Keean Schupke contém não apenas uma implementação de listas heterogêneas em Haskell, mas também um exemplo motivador de quando, por que e como você usaria HLists. Em particular, eles o estão usando para acessar o banco de dados verificado em tempo de compilação com segurança de tipo. (Pense no LINQ, de fato, o artigo que eles estão fazendo referência é o artigo de Haskell de Erik Meijer et al. Que levou ao LINQ.)
Citando o parágrafo introdutório do documento HLists:
Observe que os exemplos que você deu na sua pergunta realmente não são listas heterogêneas no sentido em que a palavra é comumente usada. São listas fracamente tipadas ou não tipadas . De fato, são na verdade listas homogêneas , pois todos os elementos são do mesmo tipo:
object
oudynamic
. Você é forçado a executar conversões ouinstanceof
testes não verificados ou algo parecido, a fim de poder trabalhar de maneira significativa com os elementos, o que os torna fracamente digitados.fonte
Recipientes heterogêneos, curtos e longos, trocam o desempenho do tempo de execução por flexibilidade. Se você deseja ter uma "lista de itens" sem considerar o tipo específico de itens, a heterogeneidade é o caminho a seguir. Os lisps são tipicamente dinamicamente digitados, e quase tudo é uma lista de contras de valores em caixa de qualquer maneira, portanto, o pequeno resultado de desempenho é esperado. No mundo Lisp, a produtividade do programador é considerada mais importante que o desempenho do tempo de execução.
Em uma linguagem de tipo dinâmico, homogêneo contêineres teriam uma sobrecarga leve em comparação com os heterogêneos, porque todos os elementos adicionados precisariam ser verificados quanto ao tipo.
Sua intuição sobre a escolha de uma melhor estrutura de dados está no ponto. De um modo geral, quanto mais contratos você pode estabelecer no seu código, mais você sabe sobre como ele funciona e mais confiável, sustentável, etc. se torna. No entanto, às vezes você realmente deseja um contêiner heterogêneo e deve ter um se precisar.
fonte
IUserSetting
e implementá-la várias vezes ou criar um genéricoUserSetting<T>
, mas um dos problemas com a digitação estática é que você está definindo uma interface antes de saber exatamente como ela será usada. As coisas que você faz com as configurações de número inteiro provavelmente são muito diferentes das que você faz com as configurações de string, então quais operações fazem sentido colocar em uma interface comum? Até que você tenha certeza, é melhor usar criteriosamente a digitação dinâmica e torná-la concreta mais tarde.object
vez de umdynamic
, use o primeiro.Em linguagens funcionais (como lisp), você usa a correspondência de padrões para determinar o que acontece com um elemento específico em uma lista. O equivalente em C # seria uma cadeia de instruções if ... elseif que verificam o tipo de um elemento e executam uma operação com base nisso. Desnecessário dizer que a correspondência de padrões funcionais é mais eficiente que a verificação do tipo de tempo de execução.
Usar polimorfismo seria uma correspondência mais próxima da correspondência de padrões. Ou seja, fazer com que os objetos de uma lista correspondam a uma interface específica e chame uma função nessa interface para cada objeto. Outra alternativa seria fornecer uma série de métodos sobrecarregados que usem um tipo de objeto específico como parâmetro. O método padrão, tendo Object como seu parâmetro.
Essa abordagem fornece uma aproximação à correspondência de padrões Lisp. O padrão de visitante (conforme implementado aqui, é um ótimo exemplo de uso para listas heterogêneas). Outro exemplo seria o envio de mensagens onde, há ouvintes para determinadas mensagens em uma fila prioritária e, usando cadeia de responsabilidade, o expedidor passa a mensagem e o primeiro manipulador que corresponde à mensagem lida com ela.
O outro lado é notificar todos os que se registram para receber uma mensagem (por exemplo, o padrão Agregador de Eventos normalmente usado para acoplamento flexível de ViewModels no padrão MVVM). Eu uso a seguinte construção
A única maneira de adicionar ao dicionário é uma função
(e o objeto é realmente um WeakReference para o manipulador passado). Então, aqui eu tenho que usar a lista <object> porque, em tempo de compilação, não sei qual será o tipo fechado. No tempo de execução, no entanto, posso garantir que esse tipo seja a chave do dicionário. Quando eu quero disparar o evento que eu chamo
e novamente eu resolvo a lista. Não há vantagem em usar a Lista <dinâmica> porque eu preciso convertê-lo de qualquer maneira. Então, como você vê, há méritos nas duas abordagens. Se você vai despachar dinamicamente um objeto usando sobrecarga de método, dinâmico é a maneira de fazê-lo. Se você é forçado a transmitir independentemente, pode usar Object.
fonte
DoSomething(Object)
(pelo menos ao usarobject
noforeach
loop;dynamic
é uma coisa completamente diferente).Você está certo de que a heterogeneidade carrega sobrecarga no tempo de execução, mas o mais importante é que enfraquece as garantias em tempo de compilação fornecidas pelo verificador de máquina. Mesmo assim, existem alguns problemas em que as alternativas são ainda mais caras.
Na minha experiência, ao lidar com bytes não processados por meio de arquivos, soquetes de rede etc., você costuma encontrar esses problemas.
Para dar um exemplo real, considere um sistema para computação distribuída usando futuros . Um trabalhador em um nó individual pode gerar trabalhos de qualquer tipo serializável, gerando um futuro desse tipo. Nos bastidores, o sistema envia o trabalho para um colega e salva um registro associando essa unidade de trabalho ao futuro específico que deve ser preenchido assim que a resposta a esse trabalho retornar.
Onde esses registros podem ser mantidos? Intuitivamente, o que você deseja é algo como a
Dictionary<WorkId, Future<TValue>>
, mas isso limita você a gerenciar apenas um tipo de futuro em todo o sistema. O tipo mais adequado éDictionary<WorkId, Future<dynamic>>
que o trabalhador pode converter para o tipo apropriado quando força o futuro.Nota : Este exemplo vem do mundo Haskell, onde não temos subtipo. Eu não ficaria surpreso se houvesse uma solução mais idiomática para este exemplo específico em C #, mas espero que ainda seja ilustrativo.
fonte
ISTR que Lisp não tem quaisquer outros do que uma lista de estruturas de dados, por isso, se você precisar de qualquer tipo de objeto de dados agregados, que vai ter que ser uma lista heterogênea. Como outros já apontaram, eles também são úteis para serializar dados para transmissão ou armazenamento. Um recurso interessante é que eles também são abertos, para que você possa usá-los em um sistema baseado em uma analogia de tubos e filtros e ter etapas de processamento sucessivas para aumentar ou corrigir os dados sem exigir um objeto de dados fixo ou uma topologia de fluxo de trabalho .
fonte