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?
où 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"