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.
public class ListVisitor
{
public void DoSomething(IEnumerable<dynamic> list)
{
foreach(dynamic obj in list)
{
DoSomething(obj);
}
}
public void DoSomething(SomeClass obj)
{
//do something with SomeClass
}
public void DoSomething(AnotherClass obj)
{
//do something with AnotherClass
}
public void DoSomething(Object obj)
{
//do something with everything els
}
}
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
IDictionary<Type, List<Object>>
A única maneira de adicionar ao dicionário é uma função
Register<T>(Action<T> handler)
(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
Send<T>(T message)
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.