Occurrence de boxe en C #

85

J'essaie de rassembler toutes les situations dans lesquelles la boxe se produit en C #:

  • Conversion du type de valeur en System.Objecttype:

    struct S { }
    object box = new S();
    
  • Conversion du type de valeur en System.ValueTypetype:

    struct S { }
    System.ValueType box = new S();
    
  • Conversion de la valeur du type d'énumération en System.Enumtype:

    enum E { A }
    System.Enum box = E.A;
    
  • Conversion du type de valeur en référence d'interface:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Utilisation de types valeur dans la concaténation de chaînes C #:

    char c = F();
    string s1 = "char value will box" + c;
    

    note: les constantes de chartype sont concaténées au moment de la compilation

    Remarque: le compilateur depuis la version 6.0 C # Optimise concaténation impliquant bool, char, IntPtr, UIntPtrtypes

  • Création d'un délégué à partir d'une méthode d'instance de type valeur:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Appel de méthodes virtuelles non remplacées sur des types de valeur:

    enum E { A }
    E.A.GetHashCode();
    
  • Utilisation de modèles constants C # 7.0 sous isexpression:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Boxing dans les conversions de types de tuple C #:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Paramètres facultatifs de objecttype avec des valeurs par défaut de type valeur:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Vérification de la valeur du type générique sans contrainte pour null:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    note: cela peut être optimisé par JIT dans certains runtimes .NET

  • Valeur de test de structtype de type non contraint ou générique avec les opérateurs is/ as:

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    note: cela peut être optimisé par JIT dans certains runtimes .NET

Y a-t-il d'autres situations de boxe, peut-être cachées, que vous connaissez?

flux de contrôle
la source
2
J'ai traité de cela il y a quelque temps et j'ai trouvé cela assez intéressant: Détection de (dé) boxe à l'aide de FxCop
George Duckett
Très belles manières de conversions que vous avez montrées. En fait, je ne connaissais pas le 2ème ou peut-être ne l'ai-je jamais essayé :) Merci
Zenwalker
12
ça devrait être une question wiki de la communauté
Sly
2
Qu'en est-il des types Nullable? private int? nullableInteger
allansson
1
@allansson, les types Nullables ne sont que des types de valeurs
controlflow

Réponses:

42

C'est une excellente question!

La boxe se produit pour exactement une raison: lorsque nous avons besoin d'une référence à un type valeur . Tout ce que vous avez énuméré relève de cette règle.

Par exemple, étant donné que l'objet est un type de référence, la conversion d'un type de valeur en objet nécessite une référence à un type de valeur, ce qui provoque un boxing.

Si vous souhaitez répertorier tous les scénarios possibles, vous devez également inclure des dérivés, tels que le renvoi d'un type de valeur à partir d'une méthode qui renvoie un objet ou un type d'interface, car cela convertit automatiquement le type de valeur vers l'objet / l'interface.

À propos, le cas de concaténation de chaînes que vous avez astucieusement identifié dérive également de la conversion en objet. L'opérateur + est traduit par le compilateur en un appel à la méthode Concat de string, qui accepte un objet pour le type de valeur que vous passez, donc la conversion en objet et donc la boxe se produit.

Au fil des ans, j'ai toujours conseillé aux développeurs de se souvenir de la raison unique de la boxe (que j'ai spécifiée ci-dessus) au lieu de mémoriser chaque cas, car la liste est longue et difficile à retenir. Cela favorise également la compréhension du code IL généré par le compilateur pour notre code C # (par exemple + sur string génère un appel à String.Concat). Lorsque vous avez des doutes sur ce que le compilateur génère et si la boxe se produit, vous pouvez utiliser IL Disassembler (ILDASM.exe). En règle générale, vous devriez rechercher l'opcode box (il n'y a qu'un seul cas où la boxing peut se produire même si l'IL n'inclut pas l'opcode box, plus de détails ci-dessous).

Mais je conviens que certaines occurrences de boxe sont moins évidentes. Vous en avez répertorié une: appeler une méthode non substituée d'un type valeur. En fait, c'est moins évident pour une autre raison: lorsque vous vérifiez le code IL, vous ne voyez pas l'opcode box, mais l'opcode contrainte, donc même dans l'IL ce n'est pas évident que la boxe se produit! Je n'entrerai pas dans le détail exact pourquoi éviter que cette réponse ne devienne encore plus longue ...

Un autre cas de boxe moins évident est lors de l'appel d'une méthode de classe de base à partir d'un struct. Exemple:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Ici, ToString est remplacé, donc appeler ToString sur MyValType ne générera pas de boxe. Cependant, l'implémentation appelle la base ToString et cela provoque la boxe (vérifiez l'IL!).

À propos, ces deux scénarios de boxe non évidents découlent également de la règle unique ci-dessus. Lorsqu'une méthode est appelée sur la classe de base d'un type valeur, il doit y avoir quelque chose auquel le mot - clé this doit faire référence. Étant donné que la classe de base d'un type valeur est (toujours) un type référence, le mot - clé this doit faire référence à un type référence, nous avons donc besoin d'une référence à un type valeur et la boxe se produit donc en raison de la règle unique.

Voici un lien direct vers la section de mon cours en ligne .NET qui traite de la boxe en détail: http://motti.me/mq

Si vous n'êtes intéressé que par des scénarios de boxe plus avancés, voici un lien direct (bien que le lien ci-dessus vous y mènera également une fois qu'il abordera les choses plus basiques): http://motti.me/mu

J'espère que ça aide!

Motti

Motti secoué
la source
1
Si a ToString()est appelé sur un type de valeur particulier qui ne le remplace pas, le type de valeur sera-t-il encadré sur le site d'appel, ou la méthode sera-t-elle envoyée (sans encadrement) vers un remplacement généré automatiquement qui ne fait que chaîner (avec boxe) à la méthode de base?
supercat
@supercat L'appel de toute méthode qui appelle baseun type valeur provoquera une boxe. Cela inclut les méthodes virtuelles qui ne sont pas remplacées par la structure et les Objectméthodes qui ne sont pas du tout virtuelles (comme GetType()). Voir cette question .
Şafak Gür
@ ŞafakGür: Je sais que le résultat final sera la boxe. Je m'interrogeais sur le mécanisme exact par lequel cela se produit. Étant donné que le compilateur générant IL peut ne pas savoir si le type est un type valeur ou une référence (cela peut être générique), il va malgré tout générer un callvirt. Le JITter saurait si le type est un type valeur, et s'il remplace ToString, afin qu'il puisse générer un code de site d'appel pour faire la boxe; Alternativement, il pourrait se générer automatiquement pour chaque structure qui ne remplace pas ToStringun mehtod public override void ToString() { return base.ToString(); }et ...
supercat
... que la boxe se produise dans cette méthode. Puisque la méthode serait très courte, elle pourrait alors être intégrée. Faire les choses avec cette dernière approche permettrait ToString()d'accéder à une méthode struct via Reflection comme n'importe quelle autre et utilisée pour créer un délégué statique qui prend le type struct comme refparamètre [une telle chose fonctionne avec des méthodes struct non héritées], mais je j'ai juste essayé de créer un tel délégué et cela n'a pas fonctionné. Est-il possible de créer un délégué statique pour la ToString()méthode d' une structure , et si oui, quel doit être le type de paramètre?
supercat le
Les liens sont rompus.
OfirD
5

Appel de la méthode GetType () non virtuelle sur le type de valeur:

struct S { };
S s = new S();
s.GetType();
Viacheslav Ivanov
la source
2
GetTypenécessite le boxing non seulement parce qu'il n'est pas virtuel, mais parce que les emplacements de stockage de type valeur, contrairement aux objets de tas, n'ont pas de champ "caché" qui GetType()peut être utilisé pour identifier leur type.
supercat
@supercat Hmmm. 1. Boxing ajouté par le compilateur et champ caché utilisé par le runtime. Peut être le compilateur ajoute la boxe parce qu'il connaît le runtime… 2. Lorsque nous appelons ToString (chaîne) non virtuelle sur la valeur enum, cela nécessite également la boxe et je ne crois pas que le compilateur ajoute cela car il connaît les détails de l'implémentation Enum.ToString (chaîne) . Ainsi, je pense, je peux dire que la boxe a toujours eu lieu lorsque la méthode non virtuelle sur le "type de valeur de base" est appelée.
Viacheslav Ivanov
Je n'avais pas envisagé d' Enumavoir ses propres méthodes non virtuelles, bien qu'une ToString()méthode pour un Enumaurait besoin d'avoir accès aux informations de type. Je me demande si Object, ValueTypeou Enumpossède des méthodes non virtuelles qui pourraient effectuer leur travail sans informations de type.
supercat
3

Mentionné dans la réponse de Motti, illustrant simplement avec des exemples de code:

Paramètres impliqués

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

Mais c'est sûr:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

Type de retour

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

Vérification de T sans contrainte par rapport à null

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

Utilisation de dynamique

dynamic x = 42; (boxes)

Un autre

enumValue.HasFlag

nawfal
la source
0
  • Utilisation des collections non génériques System.Collectionstelles que ArrayListou HashTable.

Certes, ce sont des exemples spécifiques de votre premier cas, mais ils peuvent être des pièges cachés. C'est incroyable la quantité de code que je rencontre encore aujourd'hui qui utilise ces derniers au lieu de List<T>et Dictionary<TKey,TValue>.

Jesse C. Slicer
la source
0

L'ajout de n'importe quelle valeur de type valeur dans ArrayList provoque un boxing:

ArrayList items = ...
numbers.Add(1); // boxing to object
sll
la source