Al ver que C # no puede switch
en un Tipo (que, según creo, no se agregó como un caso especial porque las is
relaciones significan que case
podría aplicarse más de un distintivo ), ¿hay una mejor manera de simular el cambio de tipo que no sea 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());
}
}
switch
declaración. Un ejemplo: el conjunto A contiene un conjunto de objetos de datos (que no va a cambiar, definidos en un documento de especificación o similar). Los ensamblajes B , C y D hacen referencia a A y proporcionan una conversión para los diversos objetos de datos de A (por ejemplo, una serialización / deserialización a algún formato particular). Tienes que reflejar toda la jerarquía de clases en B , C y D , y usar fábricas, o tienes ...Respuestas:
La activación de tipos definitivamente falta en C # ( ACTUALIZACIÓN: en C # 7 / VS 2017 se admite la activación de tipos; consulte la respuesta de Zachary Yates a continuación ). Para hacer esto sin una gran declaración if / else if / else, deberá trabajar con una estructura diferente. Escribí una publicación de blog hace un tiempo detallando cómo construir una estructura TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Versión corta: TypeSwitch está diseñado para evitar la conversión redundante y proporcionar una sintaxis que es similar a una declaración de cambio / caso normal. Por ejemplo, aquí está TypeSwitch en acción en un evento de formulario estándar de Windows
El código para TypeSwitch es bastante pequeño y se puede incluir fácilmente en su proyecto.
fuente
foreach
(lo que solo sucedería si no se encuentra una coincidencia)CaseInfo
simplemente verificando el valor de tipo (si es nulo, es el valor predeterminado).Con C # 7 , que se envió con Visual Studio 2017 (Versión 15. *), puede usar Tipos en
case
declaraciones (coincidencia de patrones):Con C # 6, puede usar una instrucción switch con el operador nameof () (gracias @Joey Adams):
Con C # 5 y versiones anteriores, podría usar una instrucción de cambio, pero tendrá que usar una cadena mágica que contenga el nombre del tipo ... que no es particularmente fácil de refactorizar (gracias @nukefusion)
fuente
nameof()
operador brillante .case UnauthorizedException _:
Una opción es tener un diccionario desde
Type
hastaAction
(o algún otro delegado). Busque la acción según el tipo y luego ejecútela. He usado esto para fábricas antes ahora.fuente
Con la respuesta de JaredPar en mi cabeza, escribí una variante de su
TypeSwitch
clase que usa inferencia de tipos para una sintaxis más agradable:Tenga en cuenta que el orden de los
Case()
métodos es importante.Obtenga el código completo y comentado para mi
TypeSwitch
clase . Esta es una versión abreviada que funciona:fuente
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Esto te permite decirvalue.Case((C x) ...
Crea una superclase (S) y haz que A y B hereden de ella. Luego declare un método abstracto en S que cada subclase necesita implementar.
Al hacer esto, el método "foo" también puede cambiar su firma a Foo (S o), lo que lo hace seguro, y no necesita lanzar esa fea excepción.
fuente
Puede usar la coincidencia de patrones en C # 7 o superior:
fuente
Realmente debería estar sobrecargando su método, no tratando de hacer la desambiguación usted mismo. La mayoría de las respuestas hasta ahora no tienen en cuenta las subclases futuras, lo que puede conducir a problemas de mantenimiento realmente terribles más adelante.
fuente
Si estaba utilizando C # 4, podría hacer uso de la nueva funcionalidad dinámica para lograr una alternativa interesante. No digo que esto sea mejor, de hecho, parece muy probable que sea más lento, pero tiene cierta elegancia.
Y el uso:
La razón por la que esto funciona es que una invocación de método dinámico C # 4 tiene sus sobrecargas resueltas en tiempo de ejecución en lugar de tiempo de compilación. Recientemente escribí un poco más sobre esta idea . Una vez más, me gustaría reiterar que esto probablemente funciona peor que todas las otras sugerencias, lo estoy ofreciendo simplemente como curiosidad.
fuente
Sí, gracias a C # 7 que se puede lograr. Así es como se hace (usando el patrón de expresión ):
fuente
Para los tipos integrados, puede usar la enumeración TypeCode. Tenga en cuenta que GetType () es un poco lento, pero probablemente no es relevante en la mayoría de las situaciones.
Para los tipos personalizados, puede crear su propia enumeración, y una interfaz o una clase base con propiedad o método abstracto ...
Implementación de clase abstracta de propiedad
Implementación de clase abstracta del método
Implementación de interfaz de propiedad
Implementación de interfaz del método
Uno de mis compañeros de trabajo también me contó sobre esto: tiene la ventaja de que puede usarlo literalmente para cualquier tipo de objeto, no solo para los que defina. Tiene la desventaja de ser un poco más grande y lento.
Primero defina una clase estática como esta:
Y luego puedes usarlo así:
fuente
Me gustó el uso de la escritura implícita de Virtlink para hacer que el cambio sea mucho más legible, pero no me gustó que no sea posible una salida anticipada, y que estamos haciendo asignaciones. Subamos un poco el rendimiento.
Bueno, eso me duele los dedos. Hagámoslo en T4:
Ajustando un poco el ejemplo de Virtlink:
Legible y rápido. Ahora, como todo el mundo sigue señalando en sus respuestas, y dada la naturaleza de esta pregunta, el orden es importante en la coincidencia de tipos. Por lo tanto:
fuente
Dado que la herencia facilita que un objeto sea reconocido como más de un tipo, creo que un cambio podría conducir a una mala ambigüedad. Por ejemplo:
Caso 1
Caso 2
Porque s es una cadena y un objeto. Creo que cuando escribes un
switch(foo)
, esperas que coincida con una y solo una de lascase
declaraciones. Con un cambio de tipos, el orden en el que escribe sus declaraciones de caso podría cambiar el resultado de toda la declaración de cambio. Creo que eso estaría mal.Podría pensar en una verificación del compilador sobre los tipos de una declaración de "cambio de tipos", verificando que los tipos enumerados no se hereden entre sí. Sin embargo, eso no existe.
foo is T
no es lo mismo quefoo.GetType() == typeof(T)
!!fuente
Yo tampoco
fuente
Otra forma sería definir una interfaz IThing y luego implementarla en ambas clases, aquí está el fragmento:
fuente
Según la especificación de C # 7.0, puede declarar una variable local con un alcance
case
de aswitch
:Esta es la mejor manera de hacer tal cosa, ya que involucra solo operaciones de conversión y push-on-the-stack, que son las operaciones más rápidas que un intérprete puede ejecutar justo después de las operaciones y
boolean
condiciones bit a bit .Comparando esto con un
Dictionary<K, V>
, aquí hay mucho menos uso de memoria: mantener un diccionario requiere más espacio en la RAM y algunos cálculos más por parte de la CPU para crear dos matrices (una para las claves y la otra para los valores) y recopilar códigos hash para que las claves pongan valores a sus respectivas claves.Por lo tanto, para la medida de lo sé, no creo que una manera más rápida podría existir a menos que desee utilizar sólo una
if
-then
-else
bloque con elis
operador de la siguiente manera:fuente
Puedes crear métodos sobrecargados:
Y envíe el argumento a
dynamic
escribir para evitar la comprobación de tipo estático:fuente
Las mejoras de C # 8 en la coincidencia de patrones permitieron hacerlo así. En algunos casos hace el trabajo y es más conciso.
fuente
Estás buscando
Discriminated Unions
cuáles son las características del lenguaje de F #, pero puedes lograr un efecto similar usando una biblioteca que hice, llamada OneOfhttps://github.com/mcintyre321/OneOf
La principal ventaja sobre
switch
(yif
, yexceptions as control flow
) es que se trata de tiempo de compilación seguro - no hay ningún controlador predeterminado o caer a travésSi agrega un tercer elemento a o, obtendrá un error de compilación ya que debe agregar un controlador Func dentro de la llamada del conmutador.
También puede hacer un
.Match
que devuelve un valor, en lugar de ejecutar una declaración:fuente
Cree una interfaz
IFooable
, luego haga que suA
yB
clases implementen un método común, que a su vez llama al método correspondiente que desea:Tenga en cuenta que es mejor usar en
as
lugar de verificar primerois
y luego lanzar, ya que de esa manera hace 2 lanzamientos, por lo que es más costoso.fuente
En tales casos, generalmente termino con una lista de predicados y acciones. Algo en este sentido:
fuente
Después de haber comparado las opciones que algunas de las respuestas aquí proporcionadas a las características de F #, descubrí que F # tiene un mejor soporte para la conmutación basada en tipos (aunque todavía me quedo con C #).
Es posible que desee ver aquí y aquí .
fuente
Crearía una interfaz con cualquier nombre y nombre de método que tuviera sentido para su conmutador, llamémoslos respectivamente:
IDoable
eso indica implementarvoid Do()
.y cambie el método de la siguiente manera:
Al menos con eso, está a salvo en el momento de la compilación y sospecho que en cuanto al rendimiento es mejor que verificar el tipo en tiempo de ejecución.
fuente
Con C # 8 en adelante, puede hacerlo aún más conciso con el nuevo interruptor. Y con el uso de la opción de descarte _ puede evitar crear variables innecesarias cuando no las necesite, de esta manera:
Factura y ShippingList son clases y el documento es un objeto que puede ser cualquiera de ellos.
fuente
Estoy de acuerdo con Jon sobre tener un hash de acciones para el nombre de la clase. Si mantiene su patrón, es posible que desee considerar el uso de la construcción "como" en su lugar:
La diferencia es que cuando usa el patrón if (foo es Bar) {((Bar) foo) .Action (); } estás haciendo el tipo de conversión dos veces. Ahora tal vez el compilador se optimizará y solo hará ese trabajo una vez, pero no contaría con eso.
fuente
Como Pablo sugiere, el enfoque de interfaz es casi siempre lo correcto para manejar esto. Para utilizar realmente el interruptor, otra alternativa es tener una enumeración personalizada que indique su tipo en sus clases.
Esto también se implementa en BCL. Un ejemplo es MemberInfo.MemberTypes , otro es
GetTypeCode
para tipos primitivos, como:fuente
Esta es una respuesta alternativa que combina las contribuciones de las respuestas de JaredPar y VirtLink, con las siguientes restricciones:
Uso:
Código:
fuente
Sí, solo use la "coincidencia de patrones" un poco extrañamente nombrada de C # 7 hacia arriba para que coincida con la clase o estructura:
fuente
yo suelo
fuente
Debería trabajar con
me gusta:
fuente
Si conoce la clase que espera pero aún no tiene un objeto, incluso puede hacer esto:
fuente