Existe uma alternativa melhor do que isso para “ativar o tipo”?

Visto que o C # não pode ativar um Type (que eu entendo que não foi adicionado como um caso especial porque é um relacionamento significa que mais de um caso distinto pode ser aplicado), existe uma maneira melhor de simular um tipo de switching do que este? ?

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()); } } 

    Ligar definitivamente os tipos está faltando em C # ( ATUALIZAÇÃO: em C # 7 / VS 2017, os tipos de ativação são suportados – veja 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 no blog por um tempo, detalhando como construir uma estrutura de TypeSwitch.

    http://blogs.msdn.com/jaredpar/archive/2008/05/16/switching-on-types.aspx

    Versão resumida: O TypeSwitch foi projetado para evitar a transmissão redundante e fornecer uma syntax semelhante a uma instrução normal de switch / caso. Por exemplo, aqui está o TypeSwitch em ação em um evento de formulário padrão do Windows

     TypeSwitch.Do( sender, TypeSwitch.Case 

    O código para TypeSwitch é realmente muito pequeno e pode ser facilmente colocado em seu projeto.

     static class TypeSwitch { public class CaseInfo { public bool IsDefault { get; set; } public Type Target { get; set; } public Action Action { get; set; } } public static void Do(object source, params CaseInfo[] cases) { var type = source.GetType(); foreach (var entry in cases) { if (entry.IsDefault || entry.Target.IsAssignableFrom(type)) { entry.Action(source); break; } } } public static CaseInfo Case(Action action) { return new CaseInfo() { Action = x => action(), Target = typeof(T) }; } public static CaseInfo Case(Action action) { return new CaseInfo() { Action = (x) => action((T)x), Target = typeof(T) }; } public static CaseInfo Default(Action action) { return new CaseInfo() { Action = x => action(), IsDefault = true }; } } 

    Com o C # 7 , fornecido com o Visual Studio 2017 (Release 15. *), você pode usar os tipos em instruções case (correspondência de padrões):

     switch(shape) { case Circle c: WriteLine($"circle with radius {c.Radius}"); break; case Rectangle s when (s.Length == s.Height): WriteLine($"{s.Length} x {s.Height} square"); break; case Rectangle r: WriteLine($"{r.Length} x {r.Height} rectangle"); break; default: WriteLine(""); break; case null: throw new ArgumentNullException(nameof(shape)); } 

    Com o C # 6, você pode usar uma instrução switch com o operador nameof () (obrigado @Joey Adams):

     switch(o.GetType().Name) { case nameof(AType): break; case nameof(BType): break; } 

    Com o C # 5 e versões anteriores, você poderia usar uma instrução switch, mas você teria que usar uma string mágica contendo o nome do tipo … que não é particularmente amigável ao refatorador (obrigado @nukefusion)

     switch(o.GetType().Name) { case "AType": break; } 

    Uma opção é ter um dictionary de Type para Action (ou algum outro representante). Procure a ação com base no tipo e, em seguida, execute-a. Eu usei isso para fábricas antes de agora.

    Com a resposta do JaredPar na parte de trás da minha cabeça, eu escrevi uma variante de sua class TypeSwitch que usa a inferência de tipos para uma syntax mais agradável:

     class A { string Name { get; } } class B : A { string LongName { get; } } class C : A { string FullName { get; } } class X { public string ToString(IFormatProvider provider); } class Y { public string GetIdentifier(); } public string GetName(object value) { string name = null; TypeSwitch.On(value) .Case((C x) => name = x.FullName) .Case((B x) => name = x.LongName) .Case((A x) => name = x.Name) .Case((X x) => name = x.ToString(CultureInfo.CurrentCulture)) .Case((Y x) => name = x.GetIdentifier()) .Default((x) => name = x.ToString()); return name; } 

    Observe que a ordem dos methods Case() é importante.


    Obter o código completo e comentado para minha class TypeSwitch . Esta é uma versão abreviada de trabalho:

     public static class TypeSwitch { public static Switch On(TSource value) { return new Switch(value); } public sealed class Switch { private readonly TSource value; private bool handled = false; internal Switch(TSource value) { this.value = value; } public Switch Case(Action action) where TTarget : TSource { if (!this.handled && this.value is TTarget) { action((TTarget) this.value); this.handled = true; } return this; } public void Default(Action action) { if (!this.handled) action(this.value); } } } 

    Crie uma superclass (S) e faça A e B herdarem dela. Em seguida, declare um método abstrato em S que cada subclass precisa implementar.

    Ao fazer isso, o método “foo” também pode alterar sua assinatura para Foo (S o), tornando-o seguro, e você não precisa fazer essa exceção feia.

    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 seja mais lento, mas tem certa elegância.

     class Thing { void Foo(A a) { a.Hop(); } void Foo(B b) { b.Skip(); } } 

    E o uso:

     object aOrB = Get_AOrB(); Thing t = GetThing(); ((dynamic)t).Foo(aorB); 

    A razão pela qual isso funciona é que uma chamada de método dynamic C # 4 tem suas sobrecargas resolvidas em tempo de execução em vez de tempo de compilation. Eu escrevi um pouco mais sobre essa ideia recentemente . Mais uma vez, gostaria apenas de reiterar que isso provavelmente tem um desempenho pior do que todas as outras sugestões, estou oferecendo isso simplesmente como uma curiosidade.

    Você deveria estar realmente sobrecarregando seu método, não tentando fazer a desambiguação sozinho. A maioria das respostas até agora não leva em conta subclasss futuras, o que pode levar a problemas de manutenção realmente terríveis mais tarde.

    Eu gostei do uso da digitação implícita pelo Virtlink para tornar o switch muito mais legível, mas eu não gostei que uma saída antecipada não seja possível, e que estamos fazendo alocações. Vamos aumentar o desempenho um pouco.

     public static class TypeSwitch { public static void On(TV value, Action action1) where T1 : TV { if (value is T1) action1((T1)value); } public static void On(TV value, Action action1, Action action2) where T1 : TV where T2 : TV { if (value is T1) action1((T1)value); else if (value is T2) action2((T2)value); } public static void On(TV value, Action action1, Action action2, Action action3) where T1 : TV where T2 : TV where T3 : TV { if (value is T1) action1((T1)value); else if (value is T2) action2((T2)value); else if (value is T3) action3((T3)value); } // ... etc. } 

    Bem, isso faz meus dedos doerem. Vamos fazer no T4:

     < #@ template debug="false" hostSpecific="true" language="C#" #> < #@ output extension=".cs" #> < #@ Assembly Name="System.Core.dll" #> < #@ import namespace="System.Linq" #> < #@ import namespace="System.IO" #> < # string GenWarning = "// THIS FILE IS GENERATED FROM " + Path.GetFileName(Host.TemplateFile) + " - ANY HAND EDITS WILL BE LOST!"; const int MaxCases = 15; #> < #=GenWarning#> using System; public static class TypeSwitch { < # for(int icase = 1; icase <= MaxCases; ++icase) { var types = string.Join(", ", Enumerable.Range(1, icase).Select(i => "T" + i)); var actions = string.Join(", ", Enumerable.Range(1, icase).Select(i => string.Format("Action action{0}", i))); var wheres = string.Join(" ", Enumerable.Range(1, icase).Select(i => string.Format("where T{0} : TV", i))); #> < #=GenWarning#> public static void On>(TV value, < #=actions#>) < #=wheres#> { if (value is T1) action1((T1)value); < # for(int i = 2; i <= icase; ++i) { #> else if (value is T< #=i#>) action< #=i#>((T< #=i#>)value); < #}#> } < #}#> < #=GenWarning#> } 

    Ajustando o exemplo do Virtlink um pouco:

     TypeSwitch.On(operand, (C x) => name = x.FullName, (B x) => name = x.LongName, (A x) => name = x.Name, (X x) => name = x.ToString(CultureInfo.CurrentCulture), (Y x) => name = x.GetIdentifier(), (object x) => name = x.ToString()); 

    Legível e rápido. Agora, como todos continuam apontando em suas respostas, e dada a natureza dessa questão, a ordem é importante na correspondência de tipos. Assim sendo:

    • Coloque os tipos de folha primeiro e depois os tipos de base.
    • Para os tipos de pares, coloque as correspondências mais prováveis ​​primeiro para maximizar o desempenho.
    • Isso implica que não há necessidade de um caso padrão especial. Em vez disso, basta usar o tipo mais básico no lambda e colocá-lo por último.

    Para tipos internos, você pode usar a enumeração TypeCode. Por favor, note que GetType () é um pouco lento, mas provavelmente não é relevante na maioria das situações.

     switch (Type.GetTypeCode(someObject.GetType())) { case TypeCode.Boolean: break; case TypeCode.Byte: break; case TypeCode.Char: break; } 

    Para tipos personalizados, você pode criar sua própria enumeração e uma interface ou uma class base com propriedade ou método abstrato …

    Implementação de class abstrata de propriedade

     public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public abstract class Foo { public abstract FooTypes FooType { get; } } public class FooFighter : Foo { public override FooTypes FooType { get { return FooTypes.FooFighter; } } } 

    Implementação de class abstrata de método

     public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public abstract class Foo { public abstract FooTypes GetFooType(); } public class FooFighter : Foo { public override FooTypes GetFooType() { return FooTypes.FooFighter; } } 

    Implementação de interface de propriedade

     public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public interface IFooType { FooTypes FooType { get; } } public class FooFighter : IFooType { public FooTypes FooType { get { return FooTypes.FooFighter; } } } 

    Implementação de interface do método

     public enum FooTypes { FooFighter, AbbreviatedFool, Fubar, Fugu }; public interface IFooType { FooTypes GetFooType(); } public class FooFighter : IFooType { public FooTypes GetFooType() { return FooTypes.FooFighter; } } 

    Um de meus colegas de trabalho acabou de me contar sobre isso também: Isso tem a vantagem de você poder usá-lo para literalmente qualquer tipo de object, não apenas aqueles que você define. Tem a desvantagem de ser um pouco maior e mais lento.

    Primeiro defina uma class estática como esta:

     public static class TypeEnumerator { public class TypeEnumeratorException : Exception { public Type unknownType { get; private set; } public TypeEnumeratorException(Type unknownType) : base() { this.unknownType = unknownType; } } public enum TypeEnumeratorTypes { _int, _string, _Foo, _TcpClient, }; private static Dictionary typeDict; static TypeEnumerator() { typeDict = new Dictionary(); typeDict[typeof(int)] = TypeEnumeratorTypes._int; typeDict[typeof(string)] = TypeEnumeratorTypes._string; typeDict[typeof(Foo)] = TypeEnumeratorTypes._Foo; typeDict[typeof(System.Net.Sockets.TcpClient)] = TypeEnumeratorTypes._TcpClient; } ///  /// Throws NullReferenceException and TypeEnumeratorException /// NullReferenceException /// TypeEnumeratorException public static TypeEnumeratorTypes EnumerateType(object theObject) { try { return typeDict[theObject.GetType()]; } catch (KeyNotFoundException) { throw new TypeEnumeratorException(theObject.GetType()); } } } 

    E então você pode usá-lo assim:

     switch (TypeEnumerator.EnumerateType(someObject)) { case TypeEnumerator.TypeEnumeratorTypes._int: break; case TypeEnumerator.TypeEnumeratorTypes._string: break; } 

    Dada a inheritance facilita um object a ser reconhecido como mais de um tipo, acho que uma mudança pode levar a uma má ambiguidade. Por exemplo:

    Caso 1

     { string s = "a"; if (s is string) Print("Foo"); else if (s is object) Print("Bar"); } 

    Caso 2

     { string s = "a"; if (s is object) Print("Foo"); else if (s is string) Print("Bar"); } 

    Porque s é uma string e um object. Eu acho que quando você escreve um switch(foo) você espera que foo corresponda a uma e apenas uma das declarações de case . Com uma opção em tipos, a ordem em que você escreve suas declarações de caso pode alterar o resultado de toda a instrução switch. Eu acho que isso seria errado.

    Você poderia pensar em uma verificação de compilador nos tipos de uma instrução “typeswitch”, verificando se os tipos enumerados não herdam um do outro. Isso não existe embora.

    foo is T não é o mesmo que foo.GetType() == typeof(T) !!

    Eu também

    • usar sobrecarga de método (assim como x0n ), ou
    • usar subclasss (assim como Pablo ), ou
    • aplique o padrão de visitante .

    Eu olhei algumas opções aqui, espelhando o que o F # pode fazer. F # tem muito melhor suporte para comutação baseada em tipo (embora eu ainda estou aderindo a c # ;-p). Você pode querer ver aqui e aqui .

    Outra maneira seria definir uma interface IThing e depois implementá-la em ambas as classs. Aqui está o snipet:

     public interface IThing { void Move(); } public class ThingA : IThing { public void Move() { Hop(); } public void Hop(){ //Implementation of Hop } } public class ThingA : IThing { public void Move() { Skip(); } public void Skip(){ //Implementation of Skip } } public class Foo { static void Main(String[] args) { } private void Foo(IThing a) { a.Move(); } } 

    Sim agradeça ao C # 7 isso pode ser alcançado, aqui está como é feito (usando o padrão de expressão ):

      switch(o) { case A a: a.Hop(); break; case B b: b.Skip(); break; case C _: return new ArgumentException("Type C will be supported in the next version"); default: return new ArgumentException("Unexpected type: " + o.GetType()); } 

    Crie uma interface IFooable, então faça suas classs A e B para implementar um método comum, que por sua vez chama o método correspondente que você deseja:

     interface IFooable { public void Foo(); } class A : IFooable { //other methods ... public void Foo() { this.Hop(); } } class B : IFooable { //other methods ... public void Foo() { this.Skip(); } } class ProcessingClass { public void Foo(object o) { if (o == null) throw new NullRefferenceException("Null reference", "o"); IFooable f = o as IFooable; if (f != null) { f.Foo(); } else { throw new ArgumentException("Unexpected type: " + o.GetType()); } } } 

    Note que é melhor usar “as” em vez disso, primeiro checando com “is” e então lançando, assim você faz 2 lançamentos (caro).

    Você pode criar methods sobrecarregados:

     void Foo(A a) { a.Hop(); } void Foo(B b) { b.Skip(); } void Foo(object o) { throw new ArgumentException("Unexpected type: " + o.GetType()); } 

    E use o tipo de parâmetro dynamic para ignorar a verificação de tipo estático:

     Foo((dynamic)something); 

    Você está procurando Discriminated Unions que são um recurso de linguagem do F #, mas você pode conseguir um efeito semelhante usando uma biblioteca que eu fiz, chamada OneOf

    https://github.com/mcintyre321/OneOf

    A principal vantagem sobre o switch (e if e exceptions as control flow ) é que ele é seguro em tempo de compilation – não há um manipulador padrão ou uma falha

     void Foo(OneOf o) { o.Switch( a => a.Hop(), b => b.Skip() ); } 

    Se você adicionar um terceiro item ao o, receberá um erro do compilador, pois precisará adicionar um Func do manipulador dentro da chamada do comutador.

    Você também pode fazer um .Match que retorna um valor, em vez de executar uma instrução:

     double Area(OneOf o) { return o.Match( square => square.Length * square.Length, circle => Math.PI * circle.Radius * circle.Radius ); } 

    Eu concordo com Jon sobre ter um hash de ações para o nome da class. Se você mantiver seu padrão, convém usar o construtor “as”:

     A a = o as A; if (a != null) { a.Hop(); return; } B b = o as B; if (b != null) { b.Skip(); return; } throw new ArgumentException("..."); 

    A diferença é que quando você usa o padrão se (foo é Bar) {((Bar) foo) .Action (); } você está fazendo o tipo de casting duas vezes. Agora talvez o compilador otimize e só faça esse trabalho uma vez – mas eu não contaria com isso.

    Como sugere Pablo, a abordagem de interface é quase sempre a coisa certa a fazer para lidar com isso. Para realmente utilizar switch, outra alternativa é ter um enum personalizado indicando seu tipo em suas classs.

     enum ObjectType { A, B, Default } interface IIdentifiable { ObjectType Type { get; }; } class A : IIdentifiable { public ObjectType Type { get { return ObjectType.A; } } } class B : IIdentifiable { public ObjectType Type { get { return ObjectType.B; } } } void Foo(IIdentifiable o) { switch (o.Type) { case ObjectType.A: case ObjectType.B: //...... } } 

    Isso também é implementado na BCL. Um exemplo é MemberInfo.MemberTypes , outro é GetTypeCode para tipos primitivos, como:

     void Foo(object o) { switch (Type.GetTypeCode(o.GetType())) // for IConvertible, just o.GetTypeCode() { case TypeCode.Int16: case TypeCode.Int32: //etc ...... } } 

    Esta é uma resposta alternativa que mistura contribuições das respostas JaredPar e VirtLink, com as seguintes restrições:

    • A construção do switch se comporta como uma function e recebe funções como parâmetros para os casos.
    • Garante que ele seja construído adequadamente e sempre exista uma function padrão .
    • Ele retorna após a primeira partida (verdadeiro para a resposta do JaredPar, não verdadeiro para o VirtLink).

    Uso:

      var result = TSwitch .On(val) .Case((string x) => "is a string") .Case((long x) => "is a long") .Default(_ => "what is it?"); 

    Código:

     public class TSwitch { class CaseInfo { public Type Target { get; set; } public Func Func { get; set; } } private object _source; private List> _cases; public static TSwitch On(object source) { return new TSwitch { _source = source, _cases = new List>() }; } public TResult Default(Func defaultFunc) { var srcType = _source.GetType(); foreach (var entry in _cases) if (entry.Target.IsAssignableFrom(srcType)) return entry.Func(_source); return defaultFunc(_source); } public TSwitch Case(Func func) { _cases.Add(new CaseInfo { Func = x => func((TSource)x), Target = typeof(TSource) }); return this; } }