Question

Mise à jour!

Voir ma dissection d'une partie de la spécification C # ci-dessous; Je pense que je dois manquer quelque chose, parce que me il semble que le comportement que je décris dans cette question porte réellement la spécification.

Mise à jour 2!

OK, après réflexion, et sur la base des commentaires, je pense que je comprends maintenant ce qui se passe. Les mots « type de source » dans les spécifications se réfèrent au type d'être converti - c.-à-Type2 dans mon exemple ci-dessous - qui signifie simplement que le compilateur est capable de réduire les candidats jusqu'à deux opérateurs définis (depuis Type2 est le type de source pour les deux). Cependant, il ne peut pas restreindre les choix plus loin. Ainsi, les mots clés de la spécification (telle qu'elle s'applique à cette question) sont type "source" , qui je l'ai déjà mal interprété (je pense) signifie "déclarer le type."


Original Question

Dire que j'ai ces types définis:

class Type0
{
    public string Value { get; private set; }

    public Type0(string value)
    {
        Value = value;
    }
}

class Type1 : Type0
{
    public Type1(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type1's operator.");
    }
}

class Type2 : Type0
{
    public Type2(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type2's operator.");
    }
}

Alors dis que je fais ceci:

Type2 t2 = new Type2("B");
Type1 t1 = t2;

Il est évident que cela est ambigu, car il ne sait pas quel opérateur implicit doit être utilisé. Ma question est - puisque je ne peux pas voir tout façon de résoudre cette ambiguïté (il est pas comme je peux effectuer une conversion explicite pour préciser quelle version je veux), mais les définitions de classe faire ci-dessus compilation - pourquoi le compilateur permettre à ces opérateurs de implicit correspondant à tous


Dissection

OK, je vais pas dans l'extrait de la spécification C # cité par Hans pour tenter Passant de comprendre cela.

  

Trouvez l'ensemble des types, D, à partir de laquelle   opérateurs de conversion définis par l'utilisateur sera   être considéré. Cet ensemble se compose de S   (Si S est une classe ou struct), la base   classes de S (si S est une classe), et T   (Si T est une classe ou struct).

Nous convertissons de Type2 ( S ) Type1 ( T ). Il semble donc que ici D comprendrait tous les trois types dans l'exemple: Type0 (parce qu'il est une classe de base de S ), Type1 ( T ) et Type2 ( S ).

  

Trouvez l'ensemble de applicable   opérateurs de conversion définis par l'utilisateur, U.   Cet ensemble se compose de l'utilisateur défini   opérateurs de conversion implicites déclarés   par les classes ou structs en ce que D   convertir d'un type englobant S   un type englobé par T. Si U est   vide, la conversion est définie et   une erreur de compilation se produit.

D'accord, nous avons deux opérateurs satisfaisant à ces conditions. La version déclarée dans Type1 satisfait aux exigences car Type1 est D et le convertit de Type2 (qui, évidemment, englobe S ) pour Type1 (qui est évidemment entouré par T ). La version en Type2 aussi répond aux exigences pour les mêmes raisons. Donc, U inclut ces deux opérateurs.

Enfin, en ce qui concerne la recherche la plus spécifique "type de source" SX des opérateurs U :

  

Si l'un des opérateurs de convertir U de S, puis SX est S.

à la fois opérateurs U Convertir de S - si cela me dit que SX est < strong> S .

Est-ce que cela signifie pas que la version Type2 doit être utilisé?

Mais attendez! Je suis confus!

Je ne pourrais pas uniquement La version de Type1 définie de l'opérateur, auquel cas, le seul candidat restant serait la version Type1, mais selon la spec SX serait Type2? Cela semble être un scénario possible dans lequel le mandat spec quelque chose d'impossible (à savoir que la conversion déclarée en Type2 doit être utilisé, alors qu'en fait il n'existe pas).

Était-ce utile?

La solution

Nous ne voulons pas vraiment que ce soit une erreur de compilation juste pour définir les conversions qui peut cause de l'ambiguïté. Supposons que nous modifions Type0 pour stocker un double, et pour une raison quelconque, nous voulons fournir des conversions distinctes à entier signé et entier non signé.

class Type0
{
    public double Value { get; private set; }

    public Type0(double value)
    {
        Value = value;
    }

    public static implicit operator Int32(Type0 other)
    {
        return (Int32)other.Value;
    }

    public static implicit operator UInt32(Type0 other)
    {
        return (UInt32)Math.Abs(other.Value);
    }

}

Cette compile bien, et je peux utiliser utiliser les deux conversions avec

Type0 t = new Type0(0.9);
int i = t;
UInt32 u = t;

Cependant, il est une erreur de compilation pour essayer float f = t parce que l'une des conversions implicites pourraient être utilisées pour arriver à un type entier qui peut ensuite être converti en float.

Nous voulons que le compilateur pour se plaindre de ces ambiguïtés plus complexes quand ils sont effectivement utilisés, puisque nous aimerions que le Type0 ci-dessus pour la compilation. Par souci de cohérence, l'ambiguïté plus simple devrait aussi provoquer une erreur au point où vous l'utilisez plutôt que lorsque vous définissez.

EDIT

Depuis Hans retiré sa réponse qui a cité les spécifications, voici une course rapide à travers la partie de la spécification C # qui détermine si une conversion est ambiguë, U étant défini comme l'ensemble de toutes les conversions qui pourraient éventuellement faire le travail:

  
      
  • Trouvez le plus type de source spécifique, SX, des opérateurs en U:      
        
    • Si l'un des opérateurs de convertir U de S, puis SX est S.
    •   
    • Dans le cas contraire, SX est le type le plus englobé dans l'ensemble combiné de types cibles des opérateurs U. Si aucun type le plus englobé peut être trouvé, la conversion est ambiguë et une erreur de compilation se produit.
    •   
  •   

Paraphrase, nous préférons une conversion qui convertit directement à partir de S, sinon nous préférons le type qui est « plus facile » pour convertir S. Dans les deux exemples, nous avons deux conversions de S disponibles. S'il n'y avait pas de conversions Type2, nous préférerions une conversion de plus d'un Type0 de object. Si aucun type est évidemment le meilleur choix à convertir, nous ne parvenons pas ici.

  
      
  • Trouver le plus type de cible spécifique, TX, des opérateurs en U:      
        
    • Si l'un des opérateurs de convertir U à T, TX est T.
    •   
    • Sinon, TX est le type le plus englobant dans l'ensemble combiné de types cibles des opérateurs U. Si aucun type le plus englobant peut être trouvé, la conversion est ambiguë et une erreur de compilation se produit.
    •   
  •   

Encore une fois, nous préférons convertir directement à T, mais nous nous contenterons pour le type qui est « plus facile » de se convertir à l'exemple de T. Dan, nous avons deux conversions à T disponibles. Dans mon exemple, les cibles possibles sont Int32 et UInt32, et ni est une meilleure correspondance que l'autre, c'est donc là la conversion échoue. Le compilateur n'a aucun moyen de savoir si des moyens de float f = t float f = (float)(Int32)t ou float f = (float)(UInt32)t.

  
      
  • Si U contient exactement un opérateur de conversion défini par l'utilisateur qui convertit de SX à TX, alors c'est le plus opérateur de conversion spécifique. Si aucun tel opérateur existe, ou si plus d'un tel opérateur existe, la conversion est ambiguë et une erreur de compilation se produit.
  •   

Dans l'exemple de Dan, nous ne parvenons pas ici parce que nous avons deux conversions gauche de SX à TX. Nous pourrions avoir aucune conversion de SX à TX si nous avons choisi différentes conversions au moment de décider SX et TX. Par exemple, si nous avions un Type1a dérivé de Type1, nous pourrions avoir des conversions de Type2 à Type1a et de Type0 à Type1 Ces donnerait encore nous SX = Type2 et TX = Type1, mais nous n'avons pas vraiment une conversion de type2 Type1. Ceci est OK, parce que cela est vraiment ambigu. Le compilateur ne sait pas si convertir Type2 à Type1a puis jeté à Type1, ou coulée à Type0 premier afin qu'il puisse utiliser cette conversion Type1.

Autres conseils

En fin de compte, il ne peut pas être prohibitted avec succès. Vous et je pourrais publier deux assemblées. Ils nous pourrions commencer à utiliser l'autre de assemble, en mettant à jour notre propre. Ensuite, nous pourrions fournir à chaque implicite entre les types moulages définis dans chaque assemblée. Seulement lorsque nous publierons la prochaine version, cela pourrait-il être pris, plutôt qu'au moment de la compilation.

Il y a un avantage à ne pas essayer d'interdire les choses qui ne peuvent être interdits, car il fait pour plus de clarté et de cohérence (et il y a une leçon pour les législateurs que).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top