4 votes

Pourquoi le compilateur C# utilise-t-il le type parent d'une valeur renvoyée par un opérateur implicite pour invoquer des opérateurs surchargés ?

J'ai les deux classes suivantes :

public class Parent
{
    public static Parent operator +(Parent l, Parent r)
    {
        return new Parent(); //do something meaningful
    }
}

public class Child: Parent
{
    public static Child operator +(Child l, Parent r)
    {
        return new Child(); //do something meaningful, child related
    }
}

J'ai ensuite une classe qui utilise implicit pour renvoyer la valeur enveloppée :

public class Wrapper<T>
{
    private T value;

    public T Value => value;

    public static implicit operator T(Wrapper<T> wrapper)
    {
        return wrapper.value;
    }
}

Ensuite, je combine les deux comme suit :

public class Usage
{
    private Parent someField;
    private Wrapper<Child> wrappedValue;

    public void UseOperatorWithImplicitConversion()
    {
        //Child sum1 = wrappedValue + someField; //<-- compilation error
        Parent sum2 = wrappedValue + someField;

        Child temp = wrappedValue; //works but defeats the purpose of reduced verbosity
        Child sum3 = temp + someField;
    }
}

Je m'attendais à ce que le sum1 pour travailler. J'ai jeté un coup d'œil dans l'IL généré et il semble que les types soient présents :

IL_0001: ldarg.0      // this
IL_0002: ldfld        class Example.Wrapper`1<class Example.Child> Example.Usage::wrappedValue
IL_0007: call         !0/*class Example.Child*/ class Example.Wrapper`1<class Example.Child>::op_Implicit(class Example.Wrapper`1<!0/*class Example.Child*/>)
IL_000c: ldarg.0      // this
IL_000d: ldfld        class Example.Parent Example.Usage::someField
IL_0012: call         class Example.Parent Example.Parent::op_Addition(class Example.Parent, class Example.Parent)
IL_0017: stloc.0      // sum2

Bien que le IL_0012 est un appel à op_Addition de la Parent et non le Child .

Y a-t-il quelque chose qui m'échappe ?

J'utilise .NET Framework 4.6.1 C# 7.2

0voto

Servy Points 93720

C# ne prend pas en compte toutes les surcharges d'opérateurs possibles définies par l'utilisateur dans chaque classe lorsqu'il essaie de déterminer quelle surcharge d'opérateur appeler dans une situation particulière. Il ne prend en compte que les surcharges d'opérateurs définies dans les types (au moment de la compilation) de l'un des opérandes. Il ne prend pas en compte les opérateurs définis dans chaque type vers lequel l'un des opérandes a une conversion implicite.

0voto

Iliar Turdushev Points 3910

Je pense que la réponse de @Servy est correcte. Je souhaite simplement l'étendre en fournissant des liens vers la spécification C# et en ajoutant une explication.

Résolution des surcharges des opérateurs binaires est utilisé pour déterminer un ensemble d'opérateurs candidats :

Une opération de la forme x op y donde op est une poubelle surchargée surchargeable, x est une expression de type X y y est une expression de Y est traité comme suit :

  • L'ensemble des opérateurs candidats définis par l'utilisateur et fournis par X y Y f l'opération operator op(x,y) est déterminée. T l'union des opérateurs candidats fournis par X a fournis par Y , chacune étant déterminée selon les règles de C opérateurs définis par l'utilisateur . Si X y Y sont du même type, ou si X y Y sont dérivés d'un type de base commun, alors les candidats partagés n'apparaissent qu'une seule fois dans l'ensemble combiné.
  • (Les autres éléments ne sont pas importants)

Dans cette ligne de code

Parent sum2 = wrappedValue + someField;

x est une expression de type Wrapper<Child> y y est une expression de type Parent .

Conformément aux règles de résolution de la surcharge des opérateurs binaires, un ensemble d'opérateurs candidats est une union des opérateurs fournis par ces deux types. Pour chacun de ces types, un ensemble d'opérateurs candidats est déterminé à l'aide des règles suivantes Opérateurs candidats définis par l'utilisateur :

Étant donné un type T et une opération operator op(A) donde op i est un opérateur surchargeable et A est une liste d'arguments, candidats définis par l'utilisateur et fournis par T para operator op(A) i comme suit :

  • Pour tous les operator op dans les déclarations T et toutes les formes augmentées de ces opérateurs, si au moins un opérateur est applicable ( A membre de la fonction ) par rapport à la liste des arguments A , opérateurs candidats est constitué de tous les opérateurs applicables dans T .
  • (Les autres éléments ne sont pas importants)

y Membre de la fonction concernée :

Un membre d'une fonction est dit être une fonction applicable par rapport à une liste d'arguments A lorsque toutes les conditions suivantes sont réunies :

  • Pour chaque argument dans A le mode de passage des paramètres de l'argument (c'est-à-dire, value , ref ou out ) est identique au passage du paramètre m de l'outil correspo
    • pour un paramètre de valeur ou un tableau de paramètres, une conversion implicite ( Conversions implicites ) existe à partir de l'a du paramètre correspondant.
    • (Les autres éléments ne sont pas importants)

En utilisant ces règles, nous pouvons conclure ce qui suit :

  • Pour le type Wrapper<Child> un ensemble d'opérateurs candidats à l'opération operator +(Wrapper<Child>, Parent) est vide.
  • Pour le type Parent un ensemble d'opérateurs candidats à l'opération operator +(Wrapper<Child>, Parent) consiste en un seul opérateur défini dans Parent : operator +(Parent, Parent) . Cet opérateur est applicable (selon le Membre de la fonction concernée ) comme opérateur candidat parce qu'il y a une conversion implicite de Wrapper<Child> à Parent .

Nous avons donc un opérateur candidat operator +(Parent, Parent) Il est donc appliqué dans notre cas.

Nous pouvons également conclure ce qui suit :

  • Un ensemble d'opérateurs candidats n'est défini que pour les types réels des opérandes utilisés dans l'opération. Les types vers lesquels les opérandes peuvent être implicitement convertis ne sont pas pris en compte dans ce processus. Par conséquent operator +(Child, Parent) de la classe Child n'a pas été considéré comme un opérateur candidat.
  • La conversion implicite est utilisée pour déterminer si un opérateur est applicable à ses arguments. Par conséquent, la conversion implicite est utilisée pour définir si un opérateur est applicable à ses arguments. operator +(Parent, Parent) a été définie comme applicable au cas operator +(Wrapper<Child>, Parent) .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X