30 votes

Quand exactement les types nullables lèvent-ils des exceptions ?

Considérons le code suivant :

int? x = null;
Console.Write ("Hashcode: ");
Console.WriteLine(x.GetHashCode());
Console.Write("Type: ");
Console.WriteLine(x.GetType());

Lorsqu'il est exécuté, il écrit que Hashcode est 0 mais échoue avec NullReferenceException pour tenter de déterminer le type de x . Je sais que les méthodes appelées sur les types nullables sont en fait appelées sur les valeurs sous-jacentes, et je m'attendais donc à ce que le programme échoue lors de l'exécution du programme. x.GetHashCode() .

Alors, quelle est la différence fondamentale entre ces deux méthodes et pourquoi la première n'échoue-t-elle pas ?

0 votes

La seule différence que je peux trouver, c'est que GetHashCode est virtual ...

1 votes

ILSpy est un petit outil pratique qui permet de répondre à ce genre de questions.

3 votes

Je trouve étrange que GetType() d'un Nullable<int> renvoie à System.Int32 et non System.Nullable<System.Int32> .

34voto

Danny Chen Points 14781

Cela s'explique par le fait que int? x = null; crée essentiellement une instance du type de valeur System.Nullable<int> avec un "intérieur" null (vous pouvez le vérifier via .HasVaue Propriété). Lorsque GetHashCode est invoquée, la dérogation Nullable<int>.GetHashCode est la méthode candidate (puisque la méthode est virtuelle), nous avons maintenant une instance de Nullable<int> et exécute sa méthode d'instance, parfaite.

Lorsque vous invoquez GetType la méthode est non virtuelle, donc l'instance de Nullable<int> est mis en boîte pour System.Object d'abord, selon le document et la valeur encadrée est null d'où le NullReferenceException .

1 votes

Tout cela correspond au comportement que nous observons, mais où est-il documenté ? La page à laquelle vous avez renvoyé détaille le fonctionnement du comportement de la boîte pour Nullable<T> mais cela ne dit pas que GetType() nécessitera l'opération de mise en boîte. Je suppose que nous devons trouver la documentation sur la façon dont les méthodes héritées fonctionnent sur les types de valeurs pour trouver les éléments pertinents.

1 votes

@LasseVågsætherKarlsen c'est parce que GetType est défini sur object au lieu de struct ce qui provoque une mise en boîte implicite. Vous pouvez vérifier en assignant int? x = 1 et ensuite appeler GetType() . Le résultat est System.Int32 , pas Nullable<T> .

1 votes

@LasseVågsætherKarlsen Vous pouvez lire la section 4.3 "boxing and unboxing" des spécifications du langage C# pour plus de détails.

17voto

Eric Lippert Points 300275

Pour clarifier la réponse correcte de Danny Chen :

  • Nullable<T> est un type de valeur. Le type de valeur se compose d'un bool, qui indique la nullité (false signifie null) et d'un T, la valeur.
  • Contrairement à tous les autres types de valeurs, les types nullables ne sont pas encadrés par une boîte. Nullable<T> . Il s'agit soit d'une boîte T ou une référence nulle.
  • Une méthode mise en œuvre par un type de valeur S est implémenté comme s'il avait un ref S argument ; c'est ainsi que this est adoptée.
  • Une méthode mise en œuvre par un type de référence C est mis en œuvre comme s'il existait un C argument ; c'est ainsi que this est adoptée.
  • Le cas intéressant est alors une méthode virtuelle définie dans une classe de base de référence et surchargée par une structure qui hérite de la classe de base.

Vous avez maintenant suffisamment d'informations pour déduire ce qui se passe. GetHashCode est virtuel et remplacée par Nullable<T> donc quand vous l'appelez, vous l'appelez comme s'il y avait une invisible ref Nullable<T> argument pour this . Il n'y a pas de boxe.

GetType n'est pas virtuelle et ne peut donc pas être surchargée, elle est définie sur object . Il s'attend donc à un object pour this Lorsqu'il est appelé sur un Nullable<T> le receveur doit être boxé, et donc peut boxer vers le nul, et donc peut lancer.

Si vous avez appelé ((object)x).GetHashCode() alors vous verriez une exception.

0 votes

Les troisième et quatrième points concernant l'invisible ref S et C sont un peu flous. Pouvez-vous les développer ?

3 votes

@Nisarg : Juste que lorsque vous avez class C { void M(int x) { ... } } et vous avez C c = new C(); c.M(123); alors d'une manière ou d'une autre c doit devenir this à l'intérieur de M . La façon dont cela se produit est que l'appel est logiquement le même que si vous aviez class C { static void M(C _this, int x) { ... }} et un appel C.M(c, 123) . this est logiquement juste un autre argument . De même pour les structs, sauf pour les structs this est un ref alias à une variable.

5voto

Sunil Points 3148

La mise en œuvre de Nullable<T>.GetHashCode() est le suivant :

public override int GetHashCode()
{
    if (!this.HasValue)
    {
        return 0;
    }
    return this.value.GetHashCode();
}

Ainsi, lorsque la valeur est nulle, vous obtiendrez toujours 0 .

x.GetType() est identique à null.GetType() qui lancera Object reference not set to an instance of an object

0 votes

Ainsi, GetHashCode est appelé sur Nullable, et non sur la valeur qu'il contient ? Pourquoi ?

0 votes

Pourquoi est-ce que x.GetType() la même chose que null.GetType() puisqu'en réalité vous avez (struct).GetType() ? Nullable<T> est une structure, de type valeur.

0 votes

Pouvez-vous également afficher l'implémentation de GetType juste au cas où cela ajouterait plus de détails à la réponse ?

1voto

Captain0 Points 2333

Il semble que GetHashCode ait une vérification de nullité. (J'ai utilisé JetBrains pour voir la définition)

public override int GetHashCode()
{
  if (!this.hasValue)
    return 0;
  return this.value.GetHashCode();
}

0 votes

La mise en œuvre de GetHashCode() n'entre en jeu qu'une fois qu'il est établi que GetHashCode() ne peut pas être appelé du tout.

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