Qu'est-ce qu'une utilisation appropriée du downcasting?

Le downcasting signifie la conversion d'une classe de base (ou interface) vers une classe sous-classe ou feuille.

Un exemple de downcast peut être si vous convertissez de System.Object vers un autre type.

Le downcasting est impopulaire, peut-être une odeur de code: la doctrine orientée objet consiste à préférer, par exemple, la définition et l’appel de méthodes virtuelles ou abstraites au lieu d’un downcasting.

  • Quels sont, le cas échéant, des cas d'utilisation appropriés et appropriés pour le downcasting? C’est-à-dire, dans quelles circonstances est-il approprié d’écrire du code qui diffuse en aval?
  • Si votre réponse est "none", alors pourquoi le downcasting est-il pris en charge par la langue?
63

9 Réponses

Le downcasting est impopulaire, peut-être une odeur de code

Je ne suis pas d'accord. Le downcasting est extrêmement populaire ; un grand nombre de programmes du monde réel contiennent une ou plusieurs diffusions à la baisse. Et ce n'est peut-être pas une odeur de code. C'est certainement une odeur de code. C’est pourquoi il est nécessaire que l’opération de downcasting se manifeste dans le texte du programme . C'est pour que vous puissiez plus facilement remarquer l'odeur et consacrer votre attention à la révision de code.

dans quelle circonstance [s] est-il approprié d'écrire du code qui a été diffusé?

Dans toutes les circonstances où:

  • vous avez une connaissance 100% correcte d'un fait sur le type d'exécution d'une expression plus spécifique que le type à la compilation de l'expression et
  • vous devez tirer parti de ce fait pour utiliser une fonctionnalité de l'objet non disponible sur le type à la compilation, et
  • c’est une meilleure utilisation du temps et des efforts nécessaires pour écrire le casting que pour refactoriser le programme afin d’éliminer l’un ou l’autre des deux premiers points.

Si vous pouvez refactoriser un programme à moindre coût de manière à ce que le compilateur puisse déduire le type d'exécution, ou le refactoriser afin que vous n'ayez pas besoin de la capacité du type plus dérivé, faites-le. Des diffusions en aval ont été ajoutées à la langue pour les cas où il est difficile et coûteux de restructurer ainsi le programme.

pourquoi le downcasting est-il pris en charge par la langue?

C # a été inventé par des programmeurs pragmatiques qui ont un travail à faire, pour des programmeurs pragmatiques qui ont un travail à faire. Les concepteurs C# ne sont pas des puristes OO. Et le système de type C# n’est pas parfait; de par sa conception, il sous-estime les restrictions pouvant être imposées au type d’exécution d’une variable.

En outre, le downcasting est très sûr en C #. Nous avons une forte garantie que le downcast sera vérifié au moment de l'exécution. S'il ne peut pas être vérifié, le programme réagit correctement et se bloque. C'est merveilleux; cela signifie que si votre compréhension correcte à 100% de la sémantique de type s'avère correcte à 99,99%, votre programme fonctionne 99,99% du temps et se bloque le reste du temps, au lieu de se comporter de manière imprévisible et de corrompre les données utilisateur. .


EXERCICE: Il existe au moins un moyen de produire un downcast en C# sans en utilisant un opérateur de conversion explicite. Pouvez-vous penser à de tels scénarios? Etant donné que ce sont aussi des odeurs potentielles de code, quels facteurs ont été, selon vous, dans la conception d’une fonctionnalité susceptible de provoquer un plantage sans affichage du code?

127
ajouté

Les gestionnaires d'événements ont généralement la signature MethodName (expéditeur d'objet, EventArgs e) . Dans certains cas, il est possible de gérer l'événement sans se soucier du type sender , ou même sans utiliser sender , mais dans d'autres sender . doit être converti dans un type plus spécifique pour gérer l'événement.

Par exemple, vous pouvez avoir deux TextBox et vous souhaitez utiliser un seul délégué pour gérer un événement de chacun d'eux. Ensuite, vous devrez convertir expéditeur en TextBox pour pouvoir accéder aux propriétés nécessaires à la gestion de l'événement:

private void TextBox_TextChanged(object sender, EventArgs e)
{
   TextBox box = (TextBox)sender;
   box.BackColor = string.IsNullOrEmpty(box.Text) ? Color.Red : Color.White;
}

Oui, dans cet exemple, vous pouvez créer votre propre sous-classe de TextBox qui l'a fait en interne. Pas toujours pratique ou possible, cependant.

32
ajouté

Vous avez besoin d'un downcasting lorsque quelque chose vous donne un supertype et que vous devez le gérer différemment en fonction du sous-type.

decimal value;
Donation d = user.getDonation();
if (d is CashDonation) {
     value = ((CashDonation)d).getValue();
}
if (d is ItemDonation) {
     value = itemValueEstimator.estimateValueOf(((ItemDonation)d).getItem());
}
System.out.println("Thank you for your generous donation of $" + value);

Non, ça ne sent pas bon. Dans un monde parfait, Donation aurait une méthode abstraite getValue et chaque sous-type de mise en œuvre aurait une implémentation appropriée.

Mais que se passe-t-il si ces classes proviennent d'une bibliothèque que vous ne pouvez pas ou ne voulez pas changer? Alors il n'y a pas d'autre option.

17
ajouté

Pour ajouter à la réponse d'Eric Lippert car je ne peux pas commenter ...

Vous pouvez souvent éviter le downcasting en restructurant une API pour qu'elle utilise des génériques. Cependant, les versions génériques n'ont été ajoutées à la langue que dans la version 2.0 . Ainsi, même si votre propre code n'a pas besoin de prendre en charge les versions de langue ancienne, vous pouvez vous retrouver à utiliser des classes héritées qui ne sont pas paramétrées. Par exemple, certaines classes peuvent être définies pour transporter une charge utile de type Object , que vous êtes obligé de transtyper vers le type que vous connaissez réellement.

12
ajouté

Voici quelques utilisations appropriées du downcasting.

Et j’ai respectueusement véhémentement des désaccords avec d’autres ici qui disent que l’utilisation des retransmissions est définitivement une odeur de code, car j’estime qu’il n’ya pas d’autre moyen raisonnable pour résoudre les problèmes correspondants.

Equals:

class A
{
   //Nothing here, but if you're a C++ programmer who dislikes object.Equals(object),
   //pretend it's not there and we have abstract bool A.Equals(A) instead.
}
class B : A
{
    private int x;
    public override bool Equals(object other)
    {
        var casted = other as B; //cautious downcast (dynamic_cast in C++)
        return casted != null && this.x == casted.x;
    }
}

Clone:

class A : ICloneable
{
   //Again, if you dislike ICloneable, that's not the point.
   //Just ignore that and pretend this method returns type A instead of object.
    public virtual object Clone()
    {
        return this.MemberwiseClone(); //some sane default behavior, whatever
    }
}
class B : A
{
    private int[] x;
    public override object Clone()
    {
        var copy = (B)base.Clone(); //known downcast (static_cast in C++)
        copy.x = (int[])this.x.Clone(); //oh hey, another downcast!!
        return copy;
    }
}

Stream.EndRead/Write:

class MyStream : Stream
{
    private class AsyncResult : IAsyncResult
    {
       //...
    }
    public override int EndRead(IAsyncResult other)
    {
        return Blah((AsyncResult)other); //another downcast (likely ~static_cast)
    }
}

Si vous dites que ce sont des odeurs de code, vous devez leur fournir de meilleures solutions (même si elles sont évitées en raison de problèmes). Je ne crois pas qu'il existe de meilleures solutions.

7
ajouté

Il existe un compromis entre les langages statiques et à typage dynamique. Le typage statique fournit au compilateur beaucoup d'informations pour pouvoir fournir des garanties assez fortes sur la sécurité des (parties de) programmes. Cela a toutefois un coût, car non seulement vous devez savoir que votre programme est correct, mais vous devez écrire le code approprié pour convaincre le compilateur que tel est le cas. En d’autres termes, il est plus facile de faire des réclamations que de les prouver.

Il existe des constructions "non sûres" qui vous aident à faire des assertions non prouvées au compilateur. Par exemple, des appels inconditionnels à Nullable. Valeur , downcasts inconditionnels, objets dynamiques , etc. Ils vous permettent d'affirmer une revendication ("J'affirme que l'objet un est un String , si je me trompe, lancez une InvalidCastException ") sans que n'ait à le prouver. Cela peut être utile dans les cas où il est beaucoup plus difficile de prouver que cela vaut la peine.

Abuser de cela est risqué, c'est précisément pourquoi la notation explicite à abaissement existe et est obligatoire; Il s'agit de sel syntaxique destiné à attirer l'attention sur une opération non sécurisée. Le langage aurait pu être implémenté avec le downcasting implicite (où le type inféré est sans ambiguïté), mais cela masquerait cette opération non sécurisée, ce qui n'est pas souhaitable.

4
ajouté

La décadence est considérée comme mauvaise pour plusieurs raisons. Principalement je pense parce que c'est anti-POO.

OOP aimerait vraiment si vous n’avez jamais à baisser les bras, car sa raison d’être est que le polymorphisme signifie que vous n’êtes pas obligé de partir.

if(object is X)
{
    //do the thing we do with X's
}
else
{
    //of the thing we do with Y's
}

tu fais juste

x.DoThing()

et le code fait comme par magie la bonne chose.

Il y a des raisons «difficiles» de ne pas abaisser:

  • En C ++, c'est lent.
  • Vous obtenez une erreur d'exécution si vous choisissez un type incorrect.

Mais l’alternative au downcasting dans certains scénarios peut être assez laide.

L'exemple classique est le traitement des messages, dans lequel vous ne souhaitez pas ajouter la fonction de traitement à l'objet, mais la conserver dans le processeur de messages. J'ai ensuite une tonne de MessageBaseClass dans un tableau à traiter, mais j'ai également besoin de chaque sous-type pour un traitement correct.

Vous pouvez utiliser Double Dispatch pour résoudre le problème ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Mais cela a aussi ses problèmes. Vous pouvez également vous contenter d'un code simple au risque de ces raisons difficiles.

Mais c'était avant l'invention des génériques. Maintenant, vous pouvez éviter le downcasting en fournissant un type où les détails spécifiés ultérieurement.

MessageProccesor can vary its method parameters and return values by the specified type but still provide generic functionality

Bien sûr, personne ne vous oblige à écrire du code POO et de nombreuses fonctionnalités linguistiques sont fournies, mais mal vues, telles que la réflexion.

3
ajouté

L’utilisation la plus courante de la décroissance dans mon travail est due à un idiot qui a brisé le principe de substitution de Liskov.

Imaginez qu’il existe une interface dans une bibliothèque tierce.

public interface Foo
{
     void SayHello();
     void SayGoodbye();
 }

Et 2 classes qui l'implémentent. Bar et Qux . Barre se comporte bien.

public class Bar
{
    void SayHello()
    {
        Console.WriteLine(“Bar says hello.”);
    }
    void SayGoodbye()
    {
         Console.WriteLine(“Bar says goodbye”);
    }
}

Mais Qux ne s’est pas bien comporté.

public class Qux
{
    void SayHello()
    {
        Console.WriteLine(“Qux says hello.”);
    }
    void SayGoodbye()
    {
        throw new NotImplementedException();
    }
}

Eh bien ... maintenant je n'ai pas le choix. Je dois saisir dactylographier check et (éventuellement) downcast afin d’éviter que mon programme ne s’arrête complètement.

0
ajouté

En plus de tout ce qui a été dit précédemment, imaginez une propriété Tag de type objet. Il vous permet de stocker un objet de votre choix dans un autre objet pour un usage ultérieur, selon vos besoins. Vous avez besoin d'abaisser cette propriété.

En général, ne pas utiliser quelque chose jusqu'à ce jour n'est pas toujours une indication d'inutile ;-)

0
ajouté