Downcasting significa transmitir desde una clase base (o interfaz) a una subclase o clase hoja.
Un ejemplo de abatido podría ser si realiza un envío desde System.Object
otro tipo.
Downcasting es impopular, tal vez un olor a código: la doctrina orientada a objetos es preferible, por ejemplo, definir y llamar a métodos virtuales o abstractos en lugar de downcasting.
- ¿Cuáles son, si los hay, casos de uso adecuados y adecuados para la conversión? Es decir, ¿en qué circunstancia (s) es apropiado escribir código que no sea válido?
- Si su respuesta es "ninguna", entonces ¿por qué el idioma admite el downcasting?
c#
object-oriented
ChrisW
fuente
fuente
ToString
); su otro ejemplo es "contenedores Java" porque Java no admite genéricos (que C # sí). stackoverflow.com/questions/1524197/downcast-and-upcast dice qué es downcasting , pero sin ejemplos de cuándo es apropiado .before Java had support for generics
, nobecause Java doesn't support generics
. Y amon prácticamente te dio la respuesta: cuando el sistema de tipos con el que estás trabajando es demasiado restrictivo y no estás en condiciones de cambiarlo.Respuestas:
Estos son algunos de los usos adecuados del downcasting.
Y con todo respeto vehementemente en desacuerdo con otros aquí que dicen que el uso de downcasts es sin duda un olor código, porque creo que no hay otra forma razonable de resolver los problemas correspondientes.
Equals
:Clone
:Stream.EndRead/Write
:Si va a decir que estos son olores de código, debe proporcionar mejores soluciones para ellos (incluso si se evitan por inconvenientes). No creo que existan mejores soluciones.
fuente
object.Equals
ajusta bastante bien a esa descripción (intente proporcionar correctamente un contrato igual en una superclase que permita que las subclases lo implementen correctamente; es absolutamente no trivial). Que como programadores de aplicaciones no podemos resolverlo (en algunos casos podemos evitarlo) es un tema diferente.Object.Equals is horrid. Equality computation in C# is completely messed up
(comentario de Eric sobre su respuesta). Es curioso cómo no quieres reconsiderar tu opinión, incluso cuando uno de los diseñadores principales del lenguaje en sí te dice que estás equivocado.Estoy en desacuerdo. Downcasting es extremadamente popular ; Una gran cantidad de programas del mundo real contienen uno o más programas descartados. Y tal vez no sea un olor a código. Definitivamente es un olor a código. Es por eso que se requiere que la operación de downcasting se manifieste en el texto del programa . Es para que pueda notar más fácilmente el olor y dedicar atención a revisar el código.
En cualquier circunstancia donde:
Si puede refactorizar un programa a bajo costo para que el compilador deduzca el tipo de tiempo de ejecución, o para refactorizar el programa de modo que no necesite la capacidad del tipo más derivado, hágalo. Se agregaron downcasts al lenguaje para aquellas circunstancias en las que es difícil y costoso refactorizar el programa.
C # fue inventado por programadores pragmáticos que tienen trabajos que hacer, para programadores pragmáticos que tienen trabajos que hacer. Los diseñadores de C # no son puristas de OO. Y el sistema de tipo C # no es perfecto; por diseño, subestima las restricciones que se pueden colocar en el tipo de tiempo de ejecución de una variable.
Además, el downcasting es muy seguro en C #. Tenemos una fuerte garantía de que el downcast se verificará en tiempo de ejecución y, si no se puede verificar, el programa hace lo correcto y falla. Esto es maravilloso; significa que si su comprensión 100% correcta de la semántica de tipos resulta ser 99.99% correcta, entonces su programa funciona el 99.99% del tiempo y se bloquea el resto del tiempo, en lugar de comportarse de manera impredecible y corromper los datos del usuario 0.01% del tiempo .
EJERCICIO: Hay al menos una forma de producir un downcast en C # sin usar un operador de conversión explícito. ¿Puedes pensar en tales escenarios? Dado que estos también son olores potenciales de código, ¿qué factores de diseño cree que entraron en el diseño de una característica que podría producir un bloqueo abatido sin tener un reparto manifiesto en el código?
fuente
Object.Equals
es horrible . El cálculo de igualdad en C # está completamente desordenado , demasiado desordenado para explicarlo en un comentario. Hay muchas formas de representar la igualdad; es muy fácil hacerlos inconsistentes; es muy fácil, de hecho se requiere violar las propiedades requeridas de un operador de igualdad (simetría, reflexividad y transitividad). Si estuviera diseñando un sistema de tipos desde cero hoy, no habría ningúnEquals
(oGetHashcode
oToString
) encendidoObject
.Los controladores de eventos generalmente tienen la firma
MethodName(object sender, EventArgs e)
. En algunos casos, es posible manejar el evento sin tener en cuenta qué tiposender
es, o incluso sin usarlosender
, pero en otros sesender
debe convertir a un tipo más específico para manejar el evento.Por ejemplo, puede tener dos
TextBox
sy desea utilizar un solo delegado para controlar un evento de cada uno de ellos. Entonces necesitaría convertirsender
a aTextBox
para poder acceder a las propiedades necesarias para manejar el evento:Sí, en este ejemplo, podría crear su propia subclase de la
TextBox
cual hizo esto internamente. Sin embargo, no siempre es práctico o posible.fuente
TextChanged
evento es miembro deControl
. Me pregunto por quésender
no es de tipo enControl
lugar de tipoobject
. También me pregunto si (en teoría) el marco podría haber redefinido el evento para cada subclase (de modo que, por ejemplo,TextBox
podría tener una nueva versión del evento, utilizandoTextBox
como tipo de remitente) ... que podría (o no) requerir un downcast ( toTextBox
) dentro de laTextBox
implementación (para implementar el nuevo tipo de evento), pero evitaría requerir un downcast en el controlador de eventos del código de la aplicación.TextBox sender
lugar de unoobject sender
sin complicar demasiado el código, estoy seguro de que se haría.Necesitas un downcasting cuando algo te da un supertipo y necesitas manejarlo de manera diferente dependiendo del subtipo.
No, esto no huele bien. En un mundo perfecto
Donation
tendría un método abstractogetValue
y la implementación de cada subtipo tendría una implementación adecuada.Pero, ¿qué pasa si estas clases son de una biblioteca que no puede o no quiere cambiar? Entonces no hay otra opción.
fuente
dynamic
palabra clave que logra el mismo resultado.void accept(IDonationVisitor visitor)
, que sus subclases implementan llamando a métodos específicos (por ejemplo, sobrecargados)IDonationVisitor
.dynamic
palabra clave.dynamic
sigue bajando , solo que más lento.Para agregar a la respuesta de Eric Lippert ya que no puedo comentar ...
A menudo puede evitar el downcasting refactorizando una API para usar genéricos. Pero los genéricos no se agregaron al lenguaje hasta la versión 2.0 . Por lo tanto, incluso si su propio código no necesita admitir versiones en idiomas antiguos, puede encontrarse utilizando clases heredadas que no están parametrizadas. Por ejemplo, alguna clase puede definirse para llevar una carga útil de tipo
Object
, que se ve obligado a lanzar al tipo que realmente sabe que es.fuente
Existe una compensación entre los idiomas estáticos y dinámicamente escritos. La escritura estática le da al compilador mucha información para poder hacer garantías bastante sólidas sobre la seguridad de (partes de) programas. Sin embargo, esto tiene un costo, porque no solo necesita saber que su programa es correcto, sino que debe escribir el código apropiado para convencer al compilador de que este es el caso. En otras palabras, hacer reclamos es más fácil que probarlos.
Hay construcciones "inseguras" que le ayudan a hacer afirmaciones no comprobadas al compilador. Por ejemplo, llamadas incondicionales
Nullable.Value
, rechazos incondicionales,dynamic
objetos, etc. Le permiten hacer valer un reclamo ("Afirmo que el objetoa
es unString
, si me equivoco, arrojar unInvalidCastException
") sin necesidad de probarlo. Esto puede ser útil en casos en los que demostrar que es significativamente más difícil de lo que vale la pena.Abusar de esto es arriesgado, razón por la cual existe la notación explícita de downcast y es obligatoria; Es una sal sintáctica destinada a llamar la atención sobre una operación insegura. El lenguaje podría haberse implementado con un downcast implícito (donde el tipo inferido no es ambiguo), pero eso ocultaría esta operación insegura, lo que no es deseable.
fuente
Downcasting se considera malo por un par de razones. Principalmente pienso porque es anti-OOP.
OOP realmente me gustaría si nunca tuvieras que abatirlo ya que su "razón de ser" es que el polimorfismo significa que no tienes que ir
solo lo haces
y el código automáticamente hace lo correcto.
Hay algunas razones "difíciles" para no rechazar:
Pero la alternativa al downcasting en algunos escenarios puede ser bastante fea.
El ejemplo clásico es el procesamiento de mensajes, donde no desea agregar la función de procesamiento al objeto, sino mantenerla en el procesador de mensajes. Luego tengo una tonelada
MessageBaseClass
en una matriz para procesar, pero también necesito cada una por subtipo para el procesamiento correcto.Puede usar Double Dispatch para solucionar el problema ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Pero eso también tiene sus problemas. O simplemente puede rechazar un código simple a riesgo de esas razones difíciles.
Pero esto fue antes de que se inventaran los genéricos. Ahora puede evitar el downcasting al proporcionar un tipo donde los detalles se especifiquen más adelante.
MessageProccesor<T>
puede variar sus parámetros de método y valores de retorno según el tipo especificado, pero aún así proporcionar funcionalidad genéricaPor supuesto, nadie te está obligando a escribir código OOP y hay muchas características de lenguaje que se proporcionan pero que están mal vistas, como la reflexión.
fuente
static_cast
lugar de hacerlodynamic_cast
.lParam
algo específico. Sin embargo, no estoy seguro de que sea un buen ejemplo de C #.static_cast
siempre es rápido ... pero no se garantiza que no falle de forma indefinida si comete un error y el tipo no es lo que espera.Además de todo lo anterior, imagine una propiedad Tag que sea de tipo objeto. Proporciona una manera de almacenar un objeto de su elección en otro objeto para su uso posterior según lo necesite. Necesita abatir esta propiedad.
En general, no usar algo hasta hoy no siempre es una indicación de algo inútil ;-)
fuente
Control
(en lugar de usar laobject Tag
propiedad) sería crear unaDictionary<Control, string>
en la que almacenar la etiqueta para cada control.Tag
es una propiedad pública de una clase de marco .Net, que muestra que se supone que (más o menos) debe usarla.System.Collections.ArrayList
) o desaprobadas (digamosIEnumerator.Reset
).El uso más común de downcasting en mi trabajo se debe a que un idiota rompe el principio de sustitución de Liskov.
Imagine que hay una interfaz en alguna biblioteca de terceros.
Y 2 clases que lo implementan.
Bar
yQux
.Bar
Se porta bien.Pero
Qux
no se porta bien.Bueno ... ahora no tengo otra opción. Me tengo que escribir cheque y (posiblemente) abatido con el fin de evitar tener mi programa vienen a un alto que se estrella.
fuente
var qux = foo as Qux;
.Otro ejemplo del mundo real es el de WPF
VisualTreeHelper
. Utiliza abatido a emitirDependencyObject
aVisual
yVisual3D
. Por ejemplo, VisualTreeHelper.GetParent . El downcast ocurre en VisualTreeUtils.AsVisualHelper :fuente