11
votes

Quand devrais-je définir un opérateur de conversion (explicite ou implicite) en C #?

une caractéristique quelque peu peu connue de C # est la possibilité de créer implicite ou explicite conversions de type défini par l'utilisateur . J'ai écrit C # Code pendant 6 ans maintenant, et je ne l'ai jamais utilisé. Donc, j'ai peur d'avoir manqué de bonnes opportunités.

Quelles sont les utilisations légitimes et bonnes des conversions définies par l'utilisateur? Avez-vous des exemples où ils sont meilleurs que de définir une méthode personnalisée?

-

s'avère, Microsoft dispose de Directives de conception sur les conversions, la le plus pertinent est:

ne fournissez pas d'opérateur de conversion si cette conversion n'est pas clairement attendu par les utilisateurs finaux.

Mais quand une conversion est-elle "attendue"? En dehors des classes de nombres de jouets, je ne peux pas comprendre un cas d'utilisation du monde réel.


Voici un résumé des exemples fournis dans les réponses:

  • Radians / degrés / Double
  • Polar / Point2D
  • Kelvin / Farenheit / Celsius

    Le motif semble être: les conversions implicites sont principalement (seulement?) utiles lors de la définition de types numériques / de valeur, la conversion étant définie par une formule. En rétrospectivement, c'est une sorte d'évidence. Néanmoins, je me demande si des classes non numériques pouvaient également bénéficier de conversions implicites.


3 commentaires

Un exemple en termes d'applications pratiques: à mon travail, nous avons créé des distincts et radians classes pour gérer les angles. Ils ont des conversions implicites sur / des uns des autres et des double et ont Immenselement simplifiée notre utilisation des angles et tous sauf les cas éliminés d'utilisation accidentelle à la place des radians (et inversement ) dans diverses fonctions trigonométriques.


Très agréable! C'est comme si vous recréez les unités de mesure de F # avec les conversions définies par l'utilisateur de C #.


J'évite les opérateurs de conversion car ils ne sont pas découverts via Intellisense comme des méthodes et des constructeurs, ce qui facilite la définition d'une conversion.


6 Réponses :


1
votes

Dites que vous avez une classe pour un produit (par exemple un jouet) que vous utilisez pour une application Shops:

public static explicit operator string(Product p)
{
    return (p.price * 100).ToString();
    //...
}


3 commentaires

Il y a déjà une méthode Tostring () pour cela. Comment une conversion est-elle meilleure? Cela ne semble pas être un cas d'utilisation convaincante. Je pense que la SP signifie que vous devriez vous abstenir de définir une conversion pour cette classe.


Je ne suis pas un gros fan de la myProduct exemple, car on s'attendrait à ce que cela s'attendrait à ce que cela soit manipulé par Tostring. Un meilleur exemple serait si vous étiez à la place des chaînes aux produits.


Je viens d'utiliser la conversion de chaîne comme exemple; Vous pouvez utiliser n'importe quel type de conversion que vous pensez que votre classe pourrait être convertie. Par exemple, pour la classe , vous pouvez définir un opérateur Explicit int qui retournerait le prix.



0
votes

Généralement, si deux choses sont logiquement convertibles. Je les utilise dans de telles situations pour fournir plus de code fluide. Je les utilise aussi parfois pour contourner les fonctionnalités de la langue qui ne fonctionnent pas vraiment comme je l'attends.

Voici un exemple très simple et artificiel qui illustre la dernière idée qui ressemble à quelque chose que j'ai utilisé dans la production. .. xxx


2 commentaires

Oui, il semble un peu conçu ... et ici, la conversion implicite permet également d'écrire "int n = code1 + code2;" ce qui n'a aucun sens. Je pense que simplement écrire "Switch (code1.Id)" est ok.


Vous avez raison. Mais c'est une chance que vous devez prendre parfois. Peu importe ce que vous faites, vous ne pouvez pas vaincre la stupidité.



9
votes

Vous pouvez utiliser un opérateur de conversion lorsqu'il y a une conversion naturelle et claire vers ou depuis un type différent.

Dites par exemple que vous avez un type de données pour représenter des températures: P>

Temperature a = new Temperature(100, TemperatureScale.Celcius);
Temperature b = 373.15; // Kelvin is default


3 commentaires

En général, toute unité de mesure est un bon candidat à ce sujet.


Ne devriez-vous pas réellement mettre en œuvre cela comme 3 classes distinctes (Kelvin, Farenheit, Celsius) avec des conversions implicites entre eux et double?


@EldritchConundrum: Cela dépend de ce que vous décidez que l'objet décrit. Je dirais que cela décrit essentiellement un niveau d'énergie, et Kelvin / Farenheit / Celsius ne sont que des façons différentes de la décrire. Comparez comment DateTime décrit un point à temps et local / Universal est des moyens différents de la décrire.



5
votes

Comme mentionné dans les commentaires, les degrés et les rotations sont un bon exemple pour éviter de mélanger des valeurs doubles, en particulier entre API.

J'ai tiré des radians et degrés Classes Nous utilisons actuellement et ils sont ici. Regardez-les maintenant (après si longtemps), je veux les nettoyer (surtout les commentaires / documentation) et assurez-vous qu'ils sont correctement testés. Heureusement, j'ai réussi à avoir du temps dans la planification de le faire. En tout cas, utilisez-les à vos risques et périls, je ne peux pas garantir si tous les les mathématiques ici sont corrects car je suis sûr que nous n'avons pas vraiment utilisé / testé Toutes les fonctionnalités que nous avons écrites.

radians xxx

degrés xxx

Quelques échantillons d'utilisation

Ces avantages sont vraiment en cours d'utilisation d'une API. Bien que, en interne, votre organisation pourrait décider de coller strictement avec des radians ou pour éviter les mélanges, au moins avec ces classes, vous pouvez utiliser le type qui a le plus de sens. Par exemple, des API ou des API d'interface graphique publiquement consommées peuvent utiliser degrés alors que votre usage de mathématiques / trigeurs ou internes lourds peut utiliser radians . Considérant les classes / la fonction d'impression suivantes: xxx

ouais, le code est assez artificiel (et terriblement ambigu) mais ça va! Va juste pour montrer comment il peut aider à réduire les mélanges accidentels. xxx

alors ils peuvent être vraiment utiles pour la mise en œuvre Autres concepts mathématiques basés sur des angles, tels que En tant que coordonnée Polar: xxx

enfin, vous pouvez mettre en œuvre une classe point2d (ou utiliser le système.windows.point) avec des conversions implicites sur / de polaire : xxx

Comme je l'ai dit, je veux prendre une autre passe à ces classes et éliminer probablement le double Conversions implicites sur Radians Pour éviter les mélanges de cas d'angle et les ambiguïtés du compilateur possibles. C'étaient en fait là avant que nous ayons créé le static one_pi , demi_pi (et ainsi de suite) et que nous étions convertis de certains multiples du MATH.PI Double.

Edit: Voici la classe polaire une démonstration de conversions implicites supplémentaires. Il tire parti de la classe radians (et donc de ses conversions implicites) et des méthodes d'assistance sur elle et la classe point2d . Je ne l'ai pas inclus ici, mais la classe polaire peut facilement mettre en œuvre des opérateurs interagir avec la classe point2d , mais ceux-ci ne sont pas pertinents pour cette discussion. < Pré> xxx


8 commentaires

Je pense que votre réduction () ne fonctionne pas comme prévu sur des angles négatifs.


Les radians / degrés / Double et Polar / Point2D sont de bons exemples d'utilisation de conversions implicites pour rendre le code plus sûr, donc je marquais cela comme réponse. Je me demande si des classes non numériques peuvent également bénéficier de conversions implicites.


@Eldritchconundrum Je ne serais pas surpris. C'est une ancienne méthode portée d'un vieux système flash ancien que nous avions. Il est destiné à convertir n'importe quel angle en un équivalent compris entre 0 et 2pi (ce qui signifie convertir un angle négatif en un positif équivalent). Mes tests le montrent de fonctionner pour des angles négatifs, pouvez-vous fournir un angle que vous avez utilisé ou que le comportement que vous avez obtenu (qui est destiné)? À Anyre, c'est un exemple d'une méthode que je veux réécrire pour être plus claire de toute façon.


@ElDitchConundrum Certains exemples rapides de non-numériques peuvent être des chaînes de localisation "EN-US" à un objet personnalisé, ou nous avons déjà ajouté aux autres pour convertir couleur à un équivalent solidcolorbrush (pour la commodité). C'est certainement l'un de ces outils que Microsoft a ajouté que, sans que strictement nécessaire est destiné à rendre le code plus lisible et maintenu (s'il n'est pas abusé!). Je rappelle juste une question à une question utilisant des opérateurs implicites pour désigner les "types compatibles" qui ont été vérifiés: Stackoverflow.com/Questtions/10622694/Mandling-unsupporteDetty pes / ...


Votre utilisation de conversions implicites dans la syntaxe d'initialisation de la liste est un abus intéressant, de la sorte de «utilisation C # comme DSL». Je suis plus incertain des conversions de la couleur à la solidcolorbrush, ils peuvent être mieux partis explicites. Et je pense que votre réduction () sur les valeurs inférieures à -2 * PI retourne toujours un nombre négatif, mais cela ne me dérange pas car je n'ai pas l'intention de l'utiliser :)


@EldritchConundrum vient de faire un test rapide et la méthode de réduction fonctionne correctement pour les valeurs inférieures à -2PI, mais cela va simplement montrer pourquoi je veux réécrire pour être plus compréhensible. Je conviens totalement que les conversions implicites dans l'initialisation de la liste pour des types compatibles sont les abus et ne l'utiliseraient probablement pas moi-même. De même, je conviens que les conversions de la couleur à la solidcolorbrush. Je pense qu'ils sont des cas de ce qui a du sens pour chaque utilisation et chaque entreprise. Pour nos développeurs, les conversions de brosse nous ont effectivement sauvé des maux de tête et ont facilité des choses (d'une manière ou d'une autre).


Laissez-nous Continuez cette discussion en chat


J'aime cet exemple, en particulier parce qu'une structure de rotation peut limiter / modulo ses valeurs comprises entre 0 et 360 degrés (ou 2pi) dans le constructeur, ce qui signifie que vous pouvez augmenter une valeur de rotation infiniment et ne jamais obtenir un débordement.



2
votes

Je l'utilise pour avoir une conversion transparente de DateTime à "yyyymmdd" ou à sa valeur int (yyyymmdd).

Par exemple: xxx


1 commentaires

Intéressant! Cela vous permet de définir orthfrom une seule fois, en lui donnant plusieurs types de retour possibles. Normalement, j'écrirais une surcharge courte pour chaque type de retour souhaité, mais votre approche échoue mieux avec le nombre de fonctions pour définir. De plus, il ne perd pas de vérification des erreurs de type-temps compilé.



0
votes

Il n'y a pas de réponse générale. Je l'utiliserais soigneusement, et seulement si le code conserve le code compréhensible et droit (c'est-à-dire qu'il affiche le comportement attendu).

Pour que je puisse vous donner une réponse basée sur un exemple pratique, si vous le suivez, vous comprendrez Quand utiliser et quand il est préférable de ne pas utiliser les opérateurs de conversion: P>

Je voulais récemment avoir un moyen plus simple de gérer les GUID. Mes objectifs de conception étaient les suivants: Simplifiez la syntaxe et l'initialisation, simplifier les conversions et les assignations variables. P>

Comme vous le savez, si vous devez créer des GUID, l'utilisation est un peu encombrante: p>

Exemple 1: strong> p>

hors de la boîte: p> xxx pré>

Et si vous pouviez simplement convertir implicitement la chaîne de guidage en chaîne, comme: P>

var eg1 = new EasyGuid();  // simple case: new Guid
Guid g = eg1; g.Dump();    // straight-forward conversion
    
EasyGuid eg2 = null;       // null-handling
Guid g2 = eg2; g2.Dump();  // converted to 00000000-0000-0000-0000-000000000000
Guid? g3 = eg2; g3.Dump(); // will be null


4 commentaires

Changer le comportement du constructeur par défaut d'un type, à l'aide d'un type d'emballage qui convertit implicitement sur le type emballé ... Oui, cela fonctionne. Mais je ne pense pas que ce soit une bonne idée de cacher les conversions, cela ajoute une complexité ... Sauf si vous utilisez C # en tant que DSL, peut-être.


@Eldritch: Comme je l'ai écrit, je l'utiliserais soigneusement. Et il n'y a pas beaucoup de cas d'utilisation où vous avez vraiment besoin de l'opérateur de conversion. Ici, il n'est pas non plus nécessaire, cela simplifie simplement la syntaxe. Que ce soit utiliser ou non, est une question de préférences personnelles. Parfois, il est bon d'avoir un exemple, de savoir comment on dirait - ce qui vous permet d'estimer si vous voulez aller cette voie ou non.


Spécifiquement pour l'initialisation de la matrice, l'attente en C # est que l'allocation d'une matrice de taille permettra à tous les éléments de la matrice, quelle que soit la valeur par défaut du type - souvent 0 . Comme un piège ait un type qui produit une valeur aléatoire pour chaque élément à la place.


@Oskarberggren - a accepté, j'ai changé le code, il attribue maintenant un GUID vide dans l'exemple de la matrice.