Quand dois-je utiliser l'opérateur de conversion de type implicite de C #?

14

En C #, nous pouvons surcharger l'opérateur de conversion implicite comme ceci (exemple de MSDN ):

struct Digit
{
    /* ... */
    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        /* ... */
    }
}

Ainsi, nous pouvons avoir un type, un type de valeur personnalisé , se convertissant comme par magie en un autre type (non lié), laissant le public perplexe (jusqu'à ce qu'ils regardent dans les coulisses et voient l'opérateur de conversion implicite, c'est-à-dire).

Je n'aime pas laisser quiconque lit mon code avec perplexité. Je ne pense pas que beaucoup de gens le fassent.

La question est, quels sont les cas d'utilisation de l'opérateur de conversion de type implicite qui ne rendront pas mon code beaucoup plus difficile à comprendre?

Menthes97
la source
1
Sensationnel. En fait, je ne savais pas que cela existait. Ce n'est pas nécessairement une bonne chose à utiliser; Je sais que les gens se sont vraiment ennuyés de ce genre de fonctionnalité cachée en C ++.
Katana314
@ Katana314: Ce n'était pas ce qui agaçait les gens, mais quelqu'un ajoutant une surcharge (que ce soit un opérateur, une fonction de conversion, un constructeur, une fonction libre ou une fonction membre) avec un comportement surprenant, de préférence subtilement surprenant.
Déduplicateur
Je vous recommande de lire sur la "surcharge d'opérateur" en C ++, en particulier les opérateurs de "casting". Je soupçonne que bon nombre des mêmes arguments pour / contre sont les mêmes, sauf que le débat se poursuit trois fois depuis que C # a existé avec beaucoup plus à lire.

Réponses:

18

Je ne recommanderais que des conversions implicites entre des types qui représentent à peu près les mêmes valeurs de différentes manières. Par exemple:

  • Différents types de couleur comme RGB, HSL, HSVet CMYK.
  • Différentes unités pour la même quantité physique ( Metervs Inch).
  • Différents systèmes de coordonnées (polaire vs cartésien).

Cependant, il existe des directives strictes qui indiquent quand il n'est pas approprié de définir une conversion implicite:

  • Si la conversion entraîne une perte significative de précision ou de plage, elle ne doit pas être implicite (par exemple: de float64 à float32 ou de long à int).
  • Si la conversion peut lever une InvalidCastexception ( ), elle ne devrait pas être implicite.
  • Si la conversion provoque une allocation de segment de mémoire chaque fois qu'elle est effectuée, elle ne doit pas être implicite.
  • Si la conversion n'est pas une O(1)opération, elle ne devrait pas être implicite.
  • Si le type source ou le type cible est modifiable, la conversion ne doit pas être implicite.
  • Si la conversion dépend d'un certain type de contexte (base de données, paramètres de culture, configuration, système de fichiers, etc.), elle ne devrait pas être implicite (je découragerais également un opérateur de conversion explicite dans ce cas).

Supposons maintenant que votre opérateur de conversion f: T1 -> T2ne viole aucune des règles ci-dessus, alors le comportement suivant indique fortement que la conversion peut être implicite:

  • Si a == balors f(a) == f(b).
  • Si a != balors f(a) != f(b).
  • Si a.ToString() == b.ToString()alors f(a).ToString() == f(b).ToString().
  • Etc. pour les autres opérations définies sur T1et T2.
Elian Ebbing
la source
Tous vos exemples sont probablement avec perte. Qu'ils soient assez précis de toute façon, ...
Déduplicateur
Ouais j'ai réalisé ça :-). Je ne pouvais pas penser à un meilleur terme pour "avec perte". Ce que j'entendais par «avec perte», ce sont des conversions où la plage ou la précision est considérablement réduite. Par exemple, de float64 à float32 ou de long à int.
Elian Ebbing
Je pense que a! = B => f (a)! = F (b), ne devrait probablement pas s'appliquer. Il y a beaucoup de fonctions qui peuvent retourner la même valeur pour différentes entrées, floor () et ceil () par exemple du côté mathématique
cdkMoose
@cdkMoose Vous avez bien sûr raison, et c'est pourquoi je considère ces propriétés plutôt comme des "points bonus", pas comme des règles. La deuxième propriété signifie simplement que la fonction de conversion est injective. C'est souvent le cas lorsque vous convertissez en un type qui a une plage strictement plus grande, par exemple de int32 à int64.
Elian Ebbing
@cdkMoose D'un autre côté, la première propriété indique simplement que deux valeurs dans la même classe d'équivalence de T1(impliquées par la ==relation on T1) sont toujours mappées à deux valeurs dans la même classe d'équivalence de T2. Maintenant que j'y pense, je suppose que la première propriété devrait être requise pour une conversion implicite.
Elian Ebbing
6

La question est, quels sont les cas d'utilisation de l'opérateur de conversion de type implicite qui ne rendront pas mon code beaucoup plus difficile à comprendre?

Lorsque les types ne sont pas indépendants (des programmeurs). Il existe de (rares) scénarios où vous avez deux types non liés (en ce qui concerne le code), qui sont réellement liés (en ce qui concerne le domaine ou les programmeurs raisonnables).

Par exemple, du code pour effectuer une correspondance de chaîne. Un scénario courant consiste à faire correspondre un littéral de chaîne. Plutôt que d'appeler IsMatch(input, new Literal("some string")), une conversion implicite vous permet de vous débarrasser de cette cérémonie - le bruit dans le code - et de vous concentrer sur le littéral de la chaîne.

La plupart des programmeurs verront IsMatch(input, "some string")et comprendront rapidement ce qui se passe. Cela rend votre code plus clair sur le site de l'appel. En bref, cela permet de comprendre un peu plus facilement ce qui se passe, au détriment de la façon dont cela se passe.

Maintenant, vous pourriez dire qu'une simple surcharge de fonction pour faire la même chose serait mieux. Et c'est. Mais si ce genre de chose est omniprésent, alors avoir une conversion est plus propre (moins de code, une cohérence accrue) que de faire une pile de surcharges de fonctions.

Et vous pourriez dire qu'il vaut mieux exiger des programmeurs qu'ils créent explicitement le type intermédiaire pour qu'ils voient "ce qui se passe réellement". C'est moins simple. Personnellement, je pense que l'exemple de correspondance de chaîne littérale est très clair sur "ce qui se passe vraiment" - le programmeur n'a pas besoin de connaître la mécanique de la façon dont tout se passe. Savez-vous comment tout votre code est exécuté par les différents processeurs sur lesquels votre code s'exécute? Il y a toujours une ligne d'abstraction où les programmeurs cessent de se soucier du fonctionnement de quelque chose. Si vous pensez que les étapes de conversion implicite sont importantes, n'utilisez pas la conversion implicite. Si vous pensez que c'est juste une cérémonie pour garder l'ordinateur heureux, et que le programmeur ferait mieux de ne pas voir ce bruit partout,

Telastyn
la source
Votre dernier point peut et doit être poussé encore plus loin: il y a aussi une ligne au-delà de laquelle un programmeur aurait bien mieux fait de ne pas se soucier de la façon dont quelque chose est fait, car ce n'est pas contractuel.
Déduplicateur