32 votes

Quelle est la justification de ce Nullable <T> comportement avec les opérateurs de conversion implicites

J'ai rencontré un certain comportement intéressant dans l'interaction entre l' Nullable et conversions implicites. J'ai trouvé que la fourniture d'une conversion implicite d'un type de référence à partir d'un type de valeur, il permet à l' Nullable type à être transmis à une fonction nécessitant le type de référence lorsque j'ai plutôt s'attendre à une erreur de compilation. Le code ci-dessous illustre cela:

static void Main(string[] args)
{
    PrintCatAge(new Cat(13));
    PrintCatAge(12);
    int? cat = null;
    PrintCatAge(cat);
}

private static void PrintCatAge(Cat cat)
{
    if (cat == null)
        System.Console.WriteLine("What cat?");
    else
        System.Console.WriteLine("The cat's age is {0} years", cat.Age);
}

class Cat
{
    public int Age { get; set; }
    public Cat(int age)
    {
        Age = age;
    }

    public static implicit operator Cat(int i)
    {
        System.Console.WriteLine("Implicit conversion from " + i);
        return new Cat(i);
    }
}

Sortie:

The cat's age is 13 years
Implicit conversion from 12
The cat's age is 12 years
What cat?

Si la conversion de code est supprimé à partir d' Cat , puis vous obtenez des erreurs attendues:

Error 3 The best overloaded method match for 'ConsoleApplication2.Program.PrintCatAge(ConsoleApplication2.Program.Cat)' has some invalid arguments

Error 4 Argument 1: cannot convert from 'int?' to 'ConsoleApplication2.Program.Cat

Si vous ouvrez le fichier exécutable avec ILSpy le code qui a été généré est comme suit

int? num = null;
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null);

Dans une expérience similaire, je l'ai enlevé de la conversion et de l'ajout d'une surcharge d' PrintCatAge qui prend un int (et non nullable) pour voir si le compilateur d'effectuer une opération similaire, mais il ne le fait pas.

Je comprends ce qui se passe, mais je ne comprends pas la justification. Ce comportement est inattendu pour moi, et me semble bizarre. Je n'ai pas eu tout le succès ne trouver aucune référence à ce comportement sur MSDN dans la documentation pour les conversions ou Nullable<T>.

La question que je pose est alors, est-ce intentionnel et s'il y a une explication pourquoi ce qui se passe?

29voto

Eric Lippert Points 300275

Je l'ai dit plus tôt que (1) c'est un bug du compilateur et (2) c'est une nouvelle. La première déclaration était exacte; la seconde a moi de se confondre dans ma hâte de se rendre à l'autobus à l'heure. (Le bug, je pensais à ce qui est nouveau pour moi, c'est beaucoup plus compliqué bug entraînant la levée de conversions et de la levée de l'incrément opérateurs.)

C'est un compilateur bug de longue date. Jon Skeet l'a porté à mon attention il y a quelques temps et je crois qu'il y a une question sur StackOverflow il quelque part; je ne me souviens pas où désinvolte. Peut-être que Jon n'.

Donc, le bug. Nous allons définir une "levée" de l'opérateur. Si un opérateur convertit à partir d'un non nullable valeur de type S, pour un non nullable valeur de type T, alors il existe aussi une "levée" de l'opérateur qui se convertit en S? pour T?, tels que un nul S? convertit une valeur null T? et une valeur non nulle S? convertit T? défaire S? S, la conversion de S à T, et l'emballage de T à T?.

La spécification indique que (1) la seule situation dans laquelle il y a une levée de l'opérateur est où S et T sont tous deux non nullable types de valeur, et (2) que la levée et de la non-levée de conversion les opérateurs sont à la fois considérées comme si elles sont applicables, les candidats à la conversion, et si les deux cas, la source et la cible des types de la de conversions, de la levée ou du unlifted, sont utilisés pour déterminer le meilleur type de source, le meilleur type de cible, et en fin de compte, meilleure conversion de toutes les conversions.

Malheureusement, la mise en œuvre soigneusement viole toutes ces règles, et fait en sorte que nous ne pouvons pas changer sans rupture de nombreux programmes existants.

Tout d'abord, nous enfreindre la règle sur l'existence de la levée des opérateurs. Une levée de l'opérateur est considéré par la mise en œuvre d'exister si S et T sont tous deux non nullable types de valeur, ou si S est un non nullable type de valeur et T est un type dans lequel une valeur null peut être attribué: type de référence, nullable type de valeur, ou de type pointeur. Dans tous ces cas, nous produisons une levée de l'opérateur.

Dans votre cas particulier, nous nous levons pour prendre la valeur null en disant que nous convertir un type nullable au type de référence de Chat en vérifiant la valeur null. Si la source n'est pas null, alors nous convertir normalement; si elle l'est, nous produisons une valeur null Chat.

Deuxièmement, nous violer de manière approfondie la règle sur la façon de déterminer la meilleure source et cible les types de les candidats applicables lorsque l'un de ces candidats est une levée de l'opérateur, et nous avons également violer les règles sur la façon de déterminer qui est le meilleur opérateur.

En bref, c'est un gros gâchis qui ne peut être fixé sans casser des vrais clients, et nous allons donc probablement consacrer le comportement de Roslyn. Je vais envisager de documenter exactement le comportement du compilateur dans mon blog à un certain point, mais je ne voudrais pas retenir mon souffle en attendant que la journée si j'étais vous.

Et bien sûr, beaucoup d'excuses pour les erreurs.

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