Visto que o C # não pode switch
em um tipo (que, segundo entendo, não foi adicionado como um caso especial, porque os is
relacionamentos significam que mais de uma distinta case
pode ser aplicada), existe uma maneira melhor de simular a ativação de outro tipo que não seja esse?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
switch
declaração. Um exemplo: o conjunto A contém um conjunto de objetos de dados (que não serão alterados, definidos em um documento de especificação ou algo semelhante). Os conjuntos B , C e D referem-se a A e fornecem uma conversão para os vários objetos de dados de A (por exemplo, uma serialização / desserialização para um formato específico). Você precisa espelhar toda a hierarquia de classes em B , C e D e usar fábricas, ou possui ...Respostas:
Definitivamente, a ativação de tipos está ausente no C # ( UPDATE: no C # 7 / VS 2017, a ativação de tipos é suportada - consulte a resposta de Zachary Yates abaixo ). Para fazer isso sem uma declaração if / else if / else grande, você precisará trabalhar com uma estrutura diferente. Eu escrevi um post de blog há algum tempo, detalhando como construir uma estrutura TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Versão curta: TypeSwitch foi projetado para impedir a transmissão redundante e fornecer uma sintaxe semelhante a uma instrução switch / case normal. Por exemplo, aqui está TypeSwitch em ação em um evento de formulário padrão do Windows
O código para TypeSwitch é realmente muito pequeno e pode ser facilmente inserido no seu projeto.
fonte
foreach
(que só aconteceria se uma correspondência não fosse encontrada)CaseInfo
apenas checando o valor do tipo (se for nulo, é o padrão).Com o C # 7 , fornecido com o Visual Studio 2017 (versão 15. *), você pode usar tipos em
case
instruções (correspondência de padrão):Com o C # 6, você pode usar uma instrução switch com o operador nameof () (obrigado @Joey Adams):
Com o C # 5 e versões anteriores, você poderia usar uma instrução switch, mas precisará usar uma string mágica que contenha o nome do tipo ... o que não é particularmente favorável ao refator (obrigado @nukefusion)
fonte
nameof()
operador brilhante .case UnauthorizedException _:
Uma opção é ter um dicionário de
Type
paraAction
(ou algum outro delegado). Procure a ação com base no tipo e, em seguida, execute-a. Eu usei isso para fábricas antes de agora.fonte
Com a resposta de JaredPar na parte de trás da minha cabeça, escrevi uma variante de sua
TypeSwitch
classe que usa inferência de tipo para obter uma sintaxe mais agradável:Observe que a ordem dos
Case()
métodos é importante.Obtenha o código completo e comentado da minha
TypeSwitch
turma . Esta é uma versão abreviada de trabalho:fonte
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Isso permite que você digavalue.Case((C x) ...
Crie uma superclasse (S) e faça com que A e B a herdem. Em seguida, declare um método abstrato em S que todas as subclasses precisam implementar.
Fazendo isso, o método "foo" também pode alterar sua assinatura para Foo (S o), tornando-o seguro, e você não precisa lançar essa exceção feia.
fonte
Você pode usar a correspondência de padrões no C # 7 ou acima:
fonte
Você realmente deveria estar sobrecarregando seu método, não tentando fazer a desambiguação por conta própria. A maioria das respostas até agora não leva em consideração as subclasses futuras, o que pode levar a problemas de manutenção realmente terríveis posteriormente.
fonte
Se você estivesse usando o C # 4, poderia usar a nova funcionalidade dinâmica para obter uma alternativa interessante. Não estou dizendo que isso é melhor, na verdade parece muito provável que seria mais lento, mas tem certa elegância.
E o uso:
A razão pela qual isso funciona é que uma chamada de método dinâmico C # 4 tem suas sobrecargas resolvidas em tempo de execução, em vez de tempo de compilação. Escrevi um pouco mais sobre essa idéia recentemente . Mais uma vez, gostaria de reiterar que isso provavelmente tem um desempenho pior do que todas as outras sugestões. Estou oferecendo isso simplesmente como curiosidade.
fonte
Sim, graças ao C # 7 que pode ser alcançado. Aqui está como é feito (usando o padrão de expressão ):
fonte
Para tipos internos, você pode usar a enumeração TypeCode. Observe que GetType () é meio lento, mas provavelmente não é relevante na maioria das situações.
Para tipos personalizados, você pode criar sua própria enumeração e uma interface ou uma classe base com propriedade ou método abstrato ...
Implementação de classe abstrata da propriedade
Implementação de classe abstrata do método
Implementação da interface da propriedade
Implementação da interface do método
Um dos meus colegas de trabalho também me falou sobre isso: Isso tem a vantagem de você poder usá-lo para literalmente qualquer tipo de objeto, não apenas aqueles que você define. Tem a desvantagem de ser um pouco maior e mais lento.
Primeiro defina uma classe estática como esta:
E então você pode usá-lo assim:
fonte
Gostei do uso de digitação implícita do Virtlink para tornar a mudança muito mais legível, mas não gostei do fato de que o início não é possível e que estamos fazendo alocações. Vamos aumentar um pouco o desempenho.
Bem, isso faz meus dedos doerem. Vamos fazer isso no T4:
Ajustando um pouco o exemplo do Virtlink:
Legível e rápido. Agora, como todo mundo continua apontando em suas respostas, e dada a natureza dessa pergunta, a ordem é importante na correspondência de tipos. Portanto:
fonte
Dada a herança facilita que um objeto seja reconhecido como mais de um tipo, acho que uma mudança pode levar a uma ambiguidade ruim. Por exemplo:
Caso 1
Caso 2
Porque s é uma string e um objeto. Eu acho que quando você escreve um,
switch(foo)
você espera que foo corresponda a um e apenas um doscase
declarações. Com uma seleção de tipos, a ordem na qual você escreve suas instruções de caso pode alterar o resultado de toda a instrução de troca. Eu acho que isso estaria errado.Você pode pensar em uma verificação do compilador nos tipos de uma instrução "typeswitch", verificando se os tipos enumerados não herdam uns dos outros. Isso ainda não existe.
foo is T
não é o mesmo quefoo.GetType() == typeof(T)
!!fonte
Eu também
fonte
Outra maneira seria definir uma interface IThing e implementá-la nas duas classes, aqui está o trecho:
fonte
Conforme a especificação do C # 7.0, você pode declarar uma variável local com escopo definido em a
case
de aswitch
:Essa é a melhor maneira de fazer uma coisa dessas, pois envolve apenas operações de conversão e empurre na pilha, que são as operações mais rápidas que um intérprete pode executar logo após operações e
boolean
condições bit a bit .Comparando isso com a
Dictionary<K, V>
, aqui está muito menos uso de memória: manter um dicionário requer mais espaço na RAM e um pouco mais de computação pela CPU para criar duas matrizes (uma para chaves e outra para valores) e reunir códigos de hash para as chaves colocarem valores para suas respectivas chaves.Portanto, até onde eu sei, não acredito que exista uma maneira mais rápida, a menos que você queira usar apenas um bloco
if
-then
-else
com ois
operador da seguinte maneira:fonte
Você pode criar métodos sobrecarregados:
E lance o argumento para
dynamic
digitar, a fim de ignorar a verificação de tipo estático:fonte
Os aprimoramentos em C # 8 da correspondência de padrões tornaram possível fazê-lo dessa maneira. Em alguns casos, ele faz o trabalho e é mais conciso.
fonte
Você está procurando
Discriminated Unions
quais são os recursos de linguagem do F #, mas pode obter um efeito semelhante usando uma biblioteca que criei, chamada OneOfhttps://github.com/mcintyre321/OneOf
A principal vantagem sobre
switch
(eif
eexceptions as control flow
) é que é seguro em tempo de compilação - não há manipulador padrão ou falhaSe você adicionar um terceiro item ao o, receberá um erro do compilador, pois precisará adicionar um manipulador Func dentro da chamada do switch.
Você também pode fazer um
.Match
que retorne um valor, em vez de executar uma instrução:fonte
Criar uma interface
IFooable
, em seguida, fazer seusA
eB
classes para implementar um método comum, que por sua vez chama o método correspondente que você deseja:Observe que é melhor usar
as
primeiro a checagemis
e depois a transmissão, pois assim você faz 2 lançamentos, por isso é mais caro.fonte
Em tais casos, geralmente termino com uma lista de predicados e ações. Algo nesse sentido:
fonte
Depois de comparar as opções fornecidas por algumas respostas aqui aos recursos do F #, descobri que o F # tinha um suporte muito melhor para a comutação baseada em tipo (embora eu ainda esteja usando o C #).
Você pode querer ver aqui e aqui .
fonte
Eu criaria uma interface com qualquer nome e método que fizesse sentido para o seu switch, vamos chamá-los respectivamente:
IDoable
que diz para implementarvoid Do()
.e altere o método da seguinte maneira:
Pelo menos com isso, você está seguro no momento da compilação e suspeito que, em termos de desempenho, é melhor do que verificar o tipo em tempo de execução.
fonte
Com o C # 8 em diante, você pode torná-lo ainda mais conciso com o novo switch. E com o uso da opção de descarte _, você pode evitar a criação de variáveis desnecessárias quando não precisar delas, assim:
Invoice e ShippingList são classes e document é um objeto que pode ser um deles.
fonte
Eu concordo com Jon sobre ter um hash de ações para o nome da classe. Se você mantiver seu padrão, considere usar a construção "as":
A diferença é que, quando você usa o padrão, if (foo é Bar) {((Bar) foo) .Action (); } você está fazendo o tipo de conversão duas vezes. Agora, talvez o compilador otimize e faça isso funcionar apenas uma vez - mas eu não contaria com isso.
fonte
Como Pablo sugere, a abordagem da interface é quase sempre a coisa certa a ser feita para lidar com isso. Para realmente utilizar a opção, outra alternativa é ter uma enumeração personalizada indicando seu tipo em suas classes.
Isso também é implementado no BCL. Um exemplo é MemberInfo.MemberTypes , outro é
GetTypeCode
para tipos primitivos, como:fonte
Esta é uma resposta alternativa que combina contribuições das respostas JaredPar e VirtLink, com as seguintes restrições:
Uso:
Código:
fonte
Sim - basta usar a "correspondência de padrões" ligeiramente estranhamente denominada do C # 7 para cima para corresponder à classe ou estrutura:
fonte
eu uso
fonte
Deve trabalhar com
gostar:
fonte
Se você conhece a classe que está esperando, mas ainda não possui um objeto, pode fazer isso:
fonte