53 votes

Pourquoi un opérateur de conversion implicite de <T> <U>l'accepter <T?>?</T?> </U> </T>

C'est un comportement bizarre que je ne peut pas donner un sens. Dans mon exemple j'ai une classe Sample<T> et une conversion implicite de l'opérateur de T de Sample<T>.

private class Sample<T>
{
   public readonly T Value;

   public Sample(T value)
   {
      Value = value;
   }

   public static implicit operator Sample<T>(T value) => new Sample<T>(value);
}

Le problème se produit lors de l'utilisation d'un nullable type de valeur pour l' T comme int?.

{
   int? a = 3;
   Sample<int> sampleA = a;
}

Ici est la partie de la clé:
À mon avis, cela ne devrait pas compiler, car Sample<int> définit une conversion de l' int de Sample<int> mais pas de int? de Sample<int>. Mais il compile et s'exécute avec succès! (Je veux dire l'opérateur de conversion est invoquée et 3 seront affectés à l' readonly champ.)

Et c'est même encore pire. Ici, l'opérateur de conversion n'est pas invoquée et sampleB sera mis à l' null:

{
   int? b = null;
   Sample<int> sampleB = b;
}

Une grande réponse serait probablement divisé en deux parties:

  1. Pourquoi le code dans le premier extrait de la compilation?
  2. Puis-je empêcher le code à partir de la compilation dans ce scénario?

43voto

arekzyla Points 2540

Vous pouvez prendre un coup d'oeil à la façon dont le compilateur fait baisser ce code:

int? a = 3;
Sample<int> sampleA = a;

dans ce:

int? nullable = 3;
int? nullable2 = nullable;
Sample<int> sample = nullable2.HasValue ? ((Sample<int>)nullable2.GetValueOrDefault()) : null;

Parce qu' Sample<int> est une classe à une instance peut être affecté à une valeur nulle et avec un tel opérateur implicite du sous-jacent type d'un objet nullable peuvent également être affectés. Si les tâches comme ceux-ci sont valides:

int? a = 3;
int? b = null;
Sample<int> sampleA = a; 
Sample<int> sampleB = b;

Si Sample<int> serait un struct, ce serait donner une erreur.

EDIT: Alors pourquoi est-ce possible? Je ne pouvais pas le trouver dans spec parce que c'est une volonté délibérée de spec violation et ce ne sont conservés que pour la compatibilité descendante. Vous pouvez lire à ce sujet dans le code:

DÉLIBÉRÉ SPEC VIOLATION:
Le compilateur natif permet une "levée" de la conversion, même lorsque le type de retour de la conversion n'est pas un non-nullable type de valeur. Par exemple, si nous avons une conversion de la struct S à la chaîne, puis une "levée" de la conversion de S? de chaîne est considéré par le compilateur natif d'exister, avec la sémantique de la "s.HasValue ? (string)s.Valeur : (string)null". Le compilateur Roslyn perpétue cette erreur pour des raisons de rétro-compatibilité.

C'est la façon dont cette "erreur" est mis en œuvre dans Roslyn:

Sinon, si le type de retour de la conversion est nullable type de la valeur, le type de référence ou un pointeur de type P, on baisse ensuite ce que:

temp = operand
temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)

Ainsi, selon les spec pour un utilisateur donné défini par l'opérateur de conversion T -> U il existe une levée de l'opérateur T? -> U?T et U sont non nullable types de valeur. Cependant une telle logique est également mise en œuvre pour un opérateur de conversion où l' U est un type de référence en raison de la au-dessus de la raison.

PARTIE 2 Comment prévenir le code à partir de la compilation dans ce scénario? Il y a bien un moyen. Vous pouvez définir un supplémentaire opérateur implicite spécifiquement pour un type nullable et de la décorer avec un attribut Obsolete. Exiger que le paramètre de type T d'être limitée à des struct:

public class Sample<T> where T : struct
{
    ...

    [Obsolete("Some error message", error: true)]
    public static implicit operator Sample<T>(T? value) => throw new NotImplementedException();
}

Cet opérateur sera choisi en tant que premier opérateur de conversion de type nullable, car il est plus spécifique.

Si vous ne pouvez pas faire une telle restriction, vous devez définir chaque opérateur pour chaque type de valeur séparément (si vous êtes vraiment déterminé, vous pouvez profiter de réflexion et de générer du code à l'aide de modèles):

[Obsolete("Some error message", error: true)]
public static implicit operator Sample<T>(int? value) => throw new NotImplementedException();

Qui donne une erreur si référencé dans n'importe quel endroit dans le code:

Erreur CS0619 l'Échantillon.opérateur implicite de l'Échantillon(int?)' est obsolète: "Certains message d'erreur"

20voto

Evk Points 17804

Je pense que c'est levé opérateur de conversion en action. Spécification indique que:

Compte tenu de l'utilisateur défini par l'opérateur de conversion qui convertit à partir d'un non nullable valeur de type S, pour un non nullable valeur de type T, a levé opérateur de conversion existe qui se convertit en S? pour T?. Cette levée de la conversion de l'opérateur effectue un déballage de S? pour S suivie par la conversion définie par l'utilisateur à partir de S à T suivi par un emballage de T pour T?, sauf que un nul valorisés S? convertit directement à une valeur nulle apprécié T?.

On dirait qu'il n'est pas applicable ici, parce que tout type S de la valeur tapez ici (int), tapez T n'est pas le type de valeur (Sample de la classe). Cependant cette question dans Roslyn référentiel unis qu'en fait c'est un bug dans la spécification. Et Roslyn code de la documentation confirme:

Comme mentionné ci-dessus, ici, nous divergent à partir de la spécification, dans les deux façons. Tout d'abord, nous avons seulement la levée de forme si la forme normale a été inapplicable. Deuxièmement, nous sommes censés appliquer de levage de la sémantique seulement si le paramètre de conversion et les types de retour sont à la foisnon nullable types de valeur.

En fait, le compilateur natif détermine si la vérification pour une levée de formulaire sur la base de:

  • Est le type nous sont, à la fin de la conversion d'une nullable le type de valeur?
  • Est le type de paramètre de la conversion d'un non nullable le type de valeur?
  • Est le type nous sommes en fin de compte la conversion de nullable type de valeur de type pointeur ou d'une référence de type?

Si la réponse à toutes ces questions est "oui", puis nous nous levons à nullable et voir si l'opérateur est applicable.

Si compilateur suivre les spécifications qu'il produirait une erreur dans ce cas que vous attendez (et dans certaines anciennes versions il l'a fait), mais maintenant il ne le fait pas.

Donc, pour résumer: je pense que le compilateur utilise levé forme de votre opérateur implicite, ce qui devrait être impossible selon la spécification, mais le compilateur diverge à partir de la spécification ici, parce que:

  • Il est considéré comme un bug dans la spécification, pas de compilateur.
  • Spécification a déjà été violé par les anciens, pré-compilateur roslyn, et il est bon de maintenir la compatibilité ascendante.

Comme décrit dans la première citation décrivant comment levée de l'opérateur (avec plus que nous permettons T à type de référence) - vous pouvez le constater, il décrit exactement ce qui se passe dans votre cas. null valorisés S (int?) est attribuée directement à l' T (Sample) sans opérateur de conversion, et non null est déballé pour int et exécuter par le biais de votre opérateur (habillage T? n'est évidemment pas nécessaire si T est le type de référence).

8voto

Fabjan Points 2873

Pourquoi le code dans le premier extrait de la compilation?

Un exemple de code à partir d'un code source d' Nullable<T> qui peut être trouvé ici:

[System.Runtime.Versioning.NonVersionable]
public static explicit operator T(Nullable<T> value) {
    return value.Value;
}

[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault(T defaultValue) {
    return hasValue ? value : defaultValue;
}

La struct Nullable<int> a un surchargée explicite de l'opérateur ainsi que la méthode d' GetValueOrDefault l'un de ces deux est utilisé par le compilateur pour convertir int? de T.

Après qu'elle s'exécute à l' implicit operator Sample<T>(T value).

Rugueux, à l'image de ce qui se passe est ceci:

Sample<int> sampleA = (Sample<int>)(int)a;

Si nous imprimer typeof(T) à l'intérieur de l' Sample<T> opérateur implicite, il affiche: System.Int32.

Dans votre deuxième scénario compilateur ne pas utiliser l' implicit operator Sample<T> et assigne simplement null de sampleB.

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