Downcasting significa conversão de uma classe base (ou interface) para uma subclasse ou folha.
Um exemplo de downcast pode ser se você transmitir System.Object
para outro tipo.
O downcasting é impopular, talvez um cheiro de código: a doutrina orientada a objetos é preferir, por exemplo, definir e chamar métodos virtuais ou abstratos em vez do downcasting.
- Quais são, se houver, casos de uso adequados e adequados para downcasting? Ou seja, em que circunstância (s) é apropriado escrever código que faça downcasts?
- Se sua resposta for "nenhuma", por que o downcast é suportado pelo idioma?
c#
object-oriented
ChrisW
fonte
fonte
ToString
); seu outro exemplo é "contêineres Java" porque o Java não suporta genéricos (o que o C # faz). stackoverflow.com/questions/1524197/downcast-and-upcast diz o que é downcasting , mas sem exemplos de quando é apropriado .before Java had support for generics
não ébecause Java doesn't support generics
. E amon deu a resposta - quando o sistema de tipos com o qual você trabalha é muito restritivo e você não está em condições de alterá-lo.Respostas:
Aqui estão alguns usos adequados de downcasting.
E eu respeitosamente discordo veementemente de outras pessoas aqui que dizem que o uso de downcasts é definitivamente um cheiro de código, porque acredito que não há outra maneira razoável de resolver os problemas correspondentes.
Equals
:Clone
:Stream.EndRead/Write
:Se você quiser dizer que são odores de código, precisará fornecer soluções melhores para eles (mesmo que sejam evitados devido a inconveniências). Não acredito que existam melhores soluções.
fonte
object.Equals
se encaixa muito bem nessa descrição (tente fornecer corretamente um contrato igual em uma superclasse que permita que subclasses o implementem corretamente - é absolutamente não trivial). Que como programadores de aplicativos não podemos resolvê-lo (em alguns casos, podemos evitá-lo) é um tópico diferente.Object.Equals is horrid. Equality computation in C# is completely messed up
(comentário de Eric sobre sua resposta). É engraçado como você não quer reconsiderar sua opinião, mesmo quando um dos principais designers da linguagem diz que você está errado.Discordo. Downcasting é extremamente popular ; um grande número de programas do mundo real contém um ou mais downcasts. E talvez não seja um cheiro de código. Definitivamente, é um cheiro de código. É por isso que a operação de downcast deve se manifestar no texto do programa . É para que você possa notar mais facilmente o cheiro e passar a atenção da revisão do código.
Em qualquer circunstância em que:
Se você pode refatorar um programa de forma barata para que o tipo de tempo de execução possa ser deduzido pelo compilador ou refatorar o programa para que você não precise da capacidade do tipo mais derivado, faça-o. Os downcasts foram adicionados ao idioma nas circunstâncias em que é difícil e caro refatorar o programa.
O C # foi inventado por programadores pragmáticos que têm trabalhos a fazer, para programadores pragmáticos que têm trabalhos a fazer. Os designers de C # não são puristas de OO. E o sistema do tipo C # não é perfeito; por design, subestima as restrições que podem ser colocadas no tipo de tempo de execução de uma variável.
Além disso, o downcasting é muito seguro em c #. Temos uma forte garantia de que o downcast será verificado no tempo de execução e, se não puder ser verificado, o programa fará a coisa certa e trava. Isso é maravilhoso; significa que, se seu entendimento 100% correto da semântica de tipos estiver 99,99% correto, seu programa funcionará 99,99% do tempo e trava o resto do tempo, em vez de se comportar de forma imprevisível e corromper os dados do usuário em 0,01% .
EXERCÍCIO: Há pelo menos uma maneira de produzir um downcast em C # sem usar um operador de conversão explícito. Você consegue pensar em tais cenários? Como esses também são odores de código em potencial, que fatores de design você acha que foram para o design de um recurso que poderia produzir uma falha de downcast sem ter um manifesto convertido no código?
fonte
Object.Equals
é horrível . O cálculo da igualdade em C # é completamente confuso , confuso demais para explicar em um comentário. Existem muitas maneiras de representar a igualdade; é muito fácil torná-los inconsistentes; é muito fácil, de fato, necessário violar as propriedades exigidas de um operador de igualdade (simetria, reflexividade e transitividade). Se eu estivesse projetando um sistema de tipos do zero hoje, não haveriaEquals
(ouGetHashcode
ouToString
)Object
ativado.Manipuladores de eventos geralmente têm a assinatura
MethodName(object sender, EventArgs e)
. Em alguns casos, é possível manipular o evento sem considerar o tiposender
, ou mesmo sem usásender
-lo, mas em outrossender
deve ser convertido para um tipo mais específico para manipular o evento.Por exemplo, você pode ter dois
TextBox
se deseja usar um único representante para manipular um evento de cada um deles. Em seguida, você precisaria convertersender
para aTextBox
para poder acessar as propriedades necessárias para manipular o evento:Sim, neste exemplo, você pode criar sua própria subclasse, a
TextBox
qual fez isso internamente. Nem sempre é prático ou possível, no entanto.fonte
TextChanged
evento é membro deControl
- eu me pergunto por quesender
não é do tipo, eControl
não do tipoobject
? Também me pergunto se (teoricamente) a estrutura poderia ter redefinido o evento para cada subclasse (para que, por exemplo,TextBox
pudesse ter uma nova versão do evento, usandoTextBox
como o tipo de remetente) ... que pode (ou não) exigir downcasting ( paraTextBox
) dentro daTextBox
implementação (para implementar o novo tipo de evento), mas evitaria exigir um downcast no manipulador de eventos do código do aplicativo.TextBox sender
ao invés de umobject sender
sem complicar demais o código, tenho certeza que isso seria feito.Você precisa fazer downcasting quando algo lhe der um supertipo e precisar lidar com isso de maneira diferente, dependendo do subtipo.
Não, isso não cheira bem. Em um mundo perfeito
Donation
, teria um método abstratogetValue
e cada subtipo de implementação teria uma implementação apropriada.Mas e se essas classes forem de uma biblioteca que você não pode ou não deseja alterar? Então não há outra opção.
fonte
dynamic
palavra - chave que alcança o mesmo resultado.void accept(IDonationVisitor visitor)
, que suas subclasses implementam chamando métodos específicos (por exemplo, sobrecarregados) deIDonationVisitor
.dynamic
palavra - chave.dynamic
ainda inativo , apenas mais lento.Para adicionar à resposta de Eric Lippert, já que não posso comentar ...
Muitas vezes, você pode evitar downcasting refatorando uma API para usar genéricos. Mas os genéricos não foram adicionados ao idioma até a versão 2.0 . Portanto, mesmo que seu próprio código não precise suportar versões de idiomas antigos, você poderá usar classes herdadas que não são parametrizadas. Por exemplo, algumas classes podem ser definidas para transportar uma carga útil do tipo
Object
, que você é forçado a converter no tipo que realmente é.fonte
Há uma troca entre linguagens estáticas e dinamicamente tipadas. A digitação estática fornece ao compilador muitas informações para poder oferecer garantias bastante fortes sobre a segurança de (partes de) programas. Isso tem um custo, no entanto, porque você não apenas precisa saber que seu programa está correto, mas também deve escrever o código apropriado para convencer o compilador de que esse é o caso. Em outras palavras, fazer reivindicações é mais fácil do que prová-las.
Existem construções "inseguras" que ajudam a fazer afirmações não comprovadas para o compilador. Por exemplo, chamadas incondicionais para
Nullable.Value
, downcasts incondicionais,dynamic
objetos etc. Eles permitem que você afirme uma reivindicação ("Eu afirmo que o objetoa
éString
, se estiver errado, lance umInvalidCastException
") sem a necessidade de provar. Isso pode ser útil nos casos em que provar que é significativamente mais difícil do que valer a pena.Abusar disso é arriscado, e é exatamente por isso que a notação explícita de downcast existe e é obrigatória; é sal sintático destinado a chamar a atenção para uma operação insegura. O idioma poderia ter sido implementado com downcasting implícito (onde o tipo inferido é inequívoco), mas isso ocultaria essa operação insegura, o que não é desejável.
fonte
O downcasting é considerado ruim por alguns motivos. Penso principalmente porque é anti-OOP.
OOP realmente gostaria se você nunca tivesse que se abaixar, pois sua "razão de ser" é que o polimorfismo significa que você não precisa ir
você apenas faz
e o código automaticamente faz a coisa certa.
Existem algumas razões 'difíceis' para não fazer downcast:
Mas a alternativa ao downcasting em alguns cenários pode ser bastante feia.
O exemplo clássico é o processamento de mensagens, no qual você não deseja adicionar a função de processamento ao objeto, mas a mantém no processador de mensagens. Em seguida, tenho uma tonelada de
MessageBaseClass
em uma matriz para processar, mas também preciso de cada uma por subtipo para o processamento correto.Você pode usar o Double Dispatch para solucionar o problema ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Mas isso também tem seus problemas. Ou você pode simplesmente fazer o downcast por algum código simples, correndo o risco desses motivos difíceis.
Mas isso foi antes da criação dos genéricos. Agora você pode evitar o downcasting fornecendo um tipo em que os detalhes especificados posteriormente.
MessageProccesor<T>
pode variar seus parâmetros de método e retornar valores pelo tipo especificado, mas ainda fornecer funcionalidade genéricaÉ claro que ninguém está forçando você a escrever código OOP e há muitos recursos de linguagem que são fornecidos, mas que são mal vistos, como reflexão.
fonte
static_cast
vez dedynamic_cast
.lParam
para algo específico. Não tenho certeza se esse é um bom exemplo de C #.static_cast
é sempre rápido ... mas não é garantido que não falhe de maneira indefinida se você cometer um erro e o tipo não for o que você está esperando.Além de tudo o que foi dito anteriormente, imagine uma propriedade Tag que seja do tipo objeto. Ele fornece uma maneira de armazenar um objeto de sua escolha em outro objeto para uso posterior conforme necessário. Você precisa fazer o downcast dessa propriedade.
Em geral, não usar algo até hoje nem sempre é uma indicação de algo inútil ;-)
fonte
Control
(em vez de usar aobject Tag
propriedade) seria criar umaDictionary<Control, string>
maneira de armazenar o rótulo para cada controle.Tag
é uma propriedade pública de uma classe de estrutura .Net, que mostra que você (mais ou menos) deveria usá-la.System.Collections.ArrayList
) ou de outra forma obsoleta (por exemplo,IEnumerator.Reset
).O uso mais comum de downcasting no meu trabalho é devido a algum idiota que quebra o princípio da substituição de Liskov.
Imagine que há uma interface em alguma biblioteca de terceiros.
E 2 classes que o implementam.
Bar
eQux
.Bar
é bem comportado.Mas
Qux
não é bem comportado.Bem ... agora não tenho escolha. Eu tenho que digitar check e (possivelmente) downcast para evitar que meu programa pare.
fonte
var qux = foo as Qux;
.Outro exemplo do mundo real são os WPFs
VisualTreeHelper
. Ele usa downcast para transmitirDependencyObject
paraVisual
eVisual3D
. Por exemplo, VisualTreeHelper.GetParent . O downcast acontece em VisualTreeUtils.AsVisualHelper :fonte