web-dev-qa-db-fra.com

Obtenir la taille d'un champ en octets avec C #

J'ai une classe, et je veux inspecter ses champs et signaler éventuellement le nombre d'octets de chaque champ. Je suppose que tous les champs sont de type Int32, octet, etc.

Comment savoir facilement combien d'octets le champ prend-il?

J'ai besoin de quelque chose comme:

Int32 a;
// int a_size = a.GetSizeInBytes;
// a_size should be 4
59
Elazar Leibovich

Vous ne pouvez pas, fondamentalement. Cela dépendra du remplissage, qui peut très bien être basé sur la version CLR que vous utilisez et le processeur, etc. Il est plus facile de calculer la taille totale d'un objet, en supposant qu'il n'a aucune référence à d'autres objets: créez un grand tableau utilisez GC.GetTotalMemory pour un point de base, remplissez le tableau avec des références à de nouvelles instances de votre type, puis appelez à nouveau GetTotalMemory. Retirez une valeur de l'autre et divisez par le nombre d'instances. Vous devez probablement créer une seule instance au préalable pour vous assurer qu'aucun nouveau code JITted ne contribue au nombre. Oui, c'est aussi hacky que cela puisse paraître - mais je l'ai utilisé à bon escient avant maintenant.

Pas plus tard qu'hier, je pensais que ce serait une bonne idée d'écrire un petit cours d'aide pour cela. Faites-moi savoir si vous seriez intéressé.

EDIT: Il y a deux autres suggestions, et j'aimerais les aborder toutes les deux.

Premièrement, l'opérateur sizeof : cela montre seulement combien d'espace le type occupe dans l'abstrait, sans aucun remplissage appliqué autour. (Il inclut le remplissage dans une structure, mais pas le remplissage appliqué à une variable de ce type dans un autre type.)

Ensuite, Marshal.SizeOf : cela montre uniquement la taille non gérée après le marshaling, pas la taille réelle en mémoire. Comme la documentation l'indique explicitement:

La taille renvoyée est en fait la taille du type non managé. Les tailles non gérées et gérées d'un objet peuvent différer. Pour les types de caractères, la taille est affectée par la valeur CharSet appliquée à cette classe.

Et encore une fois, le rembourrage peut faire la différence.

Juste pour clarifier ce que je veux dire sur le rembourrage étant pertinent, considérez ces deux classes:

class FourBytes { byte a, b, c, d; }
class FiveBytes { byte a, b, c, d, e; }

Sur ma boîte x86, une instance de FourBytes prend 12 octets (y compris la surcharge). Une instance de FiveBytes prend 16 octets. La seule différence est la variable "e" - cela prend-il donc 4 octets? Eh bien, en quelque sorte ... et en quelque sorte non. Évidemment, vous pouvez supprimer n'importe quelle variable unique de FiveBytes pour ramener la taille à 12 octets, mais cela ne signifie pas que chacune des variables prend 4 octets (pensez à les supprimer tous!). Le coût d'une seule variable n'est tout simplement pas un concept qui a beaucoup de sens ici.

98
Jon Skeet

Selon les besoins de la personne interrogée, Marshal.SizeOf peut ou non vous donner ce que vous voulez. (Modifié après que Jon Skeet a publié sa réponse).

using System;
using System.Runtime.InteropServices;

public class MyClass
{
    public static void Main()
    {
        Int32 a = 10;
        Console.WriteLine(Marshal.SizeOf(a));
        Console.ReadLine();
    }
}

Notez que, comme le dit jkersch, sizeof peut être utilisé, mais malheureusement uniquement avec les types de valeur. Si vous avez besoin de la taille d'une classe, Marshal.SizeOf est le chemin à parcourir.

Jon Skeet a expliqué pourquoi ni sizeof ni Marshal.SizeOf ne sont parfaits. Je suppose que la personne interrogée doit décider si l'une ou l'autre est acceptable pour son problème.

D'après la recette de Jon Skeets dans sa réponse, j'ai essayé de faire le cours d'aide auquel il faisait référence. Les suggestions d'améliorations sont les bienvenues.

public class MeasureSize<T>
{
    private readonly Func<T> _generator;
    private const int NumberOfInstances = 10000;
    private readonly T[] _memArray;

    public MeasureSize(Func<T> generator)
    {
        _generator = generator;
        _memArray = new T[NumberOfInstances];
    }

    public long GetByteSize()
    {
        //Make one to make sure it is jitted
        _generator();

        long oldSize = GC.GetTotalMemory(false);
        for(int i=0; i < NumberOfInstances; i++)
        {
            _memArray[i] = _generator();
        }
        long newSize = GC.GetTotalMemory(false);
        return (newSize - oldSize) / NumberOfInstances;
    }
}

Usage:

Doit être créé avec un Func qui génère de nouvelles instances de T. Assurez-vous que la même instance n'est pas renvoyée à chaque fois. Par exemple. Ce serait bien:

    public long SizeOfSomeObject()
    {
        var measure = new MeasureSize<SomeObject>(() => new SomeObject());
        return measure.GetByteSize();
    }
8
Jesper Niedermann

J'ai dû résumer cela jusqu'au niveau IL, mais j'ai finalement obtenu cette fonctionnalité en C # avec une très petite bibliothèque.

Vous pouvez l'obtenir (licence BSD) sur bitbucket

Exemple de code:

using Earlz.BareMetal;

...
Console.WriteLine(BareMetal.SizeOf<int>()); //returns 4 everywhere I've tested
Console.WriteLine(BareMetal.SizeOf<string>()); //returns 8 on 64-bit platforms and 4 on 32-bit
Console.WriteLine(BareMetal.SizeOf<Foo>()); //returns 16 in some places, 24 in others. Varies by platform and framework version

...

struct Foo
{
  int a, b;
  byte c;
  object foo;
}

Fondamentalement, ce que j'ai fait a été d'écrire un wrapper de méthode de classe rapide autour de l'instruction sizeof IL. Cette instruction obtiendra la quantité brute de mémoire utilisée par une référence à un objet. Par exemple, si vous aviez un tableau de T, alors l'instruction sizeof vous dirait combien d'octets sont séparés pour chaque élément du tableau.

Ceci est extrêmement différent de l'opérateur sizeof de C #. D'une part, C # n'autorise que les types de valeur purs car il n'est pas vraiment possible d'obtenir la taille de quoi que ce soit d'autre de manière statique. En revanche, l'instruction sizeof fonctionne au niveau de l'exécution. Ainsi, quelle que soit la quantité de mémoire utilisée par une référence à un type lors de cette instance particulière, elle sera renvoyée.

Vous pouvez voir plus d'informations et un exemple de code un peu plus approfondi sur mon blog

5
Earlz

Cela peut être fait indirectement, sans tenir compte de l'alignement. Le nombre d'octets qui font référence à l'instance de type est égal à la taille des champs de service + la taille des champs de type. Champs de service (en 32x prend 4 octets chacun, 64x 8 octets):

  1. Sysblockindex
  2. Pointeur vers la table des méthodes
  3. + Taille de tableau facultative (uniquement pour les tableaux)

Ainsi, pour une classe sans aucun fichier, son instance prend 8 octets sur une machine 32x. Si c'est une classe avec un champ, référence sur la même instance de classe, donc, cette classe prend (64x):

Sysblockindex + pMthdTable + référence sur la classe = 8 + 8 + 8 = 24 octets

S'il s'agit d'un type de valeur, il n'a pas de champs d'instance, donc in ne prend que sa taille de fichier. Par exemple, si nous avons struct avec un champ int, alors sur une machine 32x, cela ne prend que 4 octets de mémoire.

4
Sergey Popov

si vous avez le type, utilisez l'opérateur sizeof. il renverra la taille du type en octets. par exemple.

Console.WriteLine (sizeof (int));

affichera:

4

1

La manière la plus simple est: int size = *((int*)type.TypeHandle.Value + 1)

Je sais que c'est un détail d'implémentation, mais GC en dépend et il doit être aussi proche du début de la table de méthode pour plus d'efficacité, en tenant compte de la complexité du code GC, personne n'osera le changer à l'avenir. En fait, cela fonctionne pour toutes les versions mineures/majeures du framework .net + du noyau .net. (Actuellement impossible de tester 1.0)
Si vous voulez un moyen plus fiable, émettez une structure dans un Assembly dynamique avec [StructLayout(LayoutKind.Auto)] avec exactement les mêmes champs dans le même ordre, prenez sa taille avec sizeof IL instruction. Vous voudrez peut-être émettre une méthode statique dans struct qui renvoie simplement cette valeur. Ajoutez ensuite 2 * IntPtr.Size pour l'en-tête d'objet. Cela devrait vous donner une valeur exacte.
Mais si votre classe dérive d'une autre classe, vous devez rechercher chaque taille de classe de base séparément et les ajouter à nouveau + 2 * Inptr.Size pour l'en-tête. Vous pouvez le faire en obtenant des champs avec BindingFlags.DeclaredOnly drapeau.

0
TakeMeAsAGuest

Vous pouvez utiliser la surcharge de méthode comme une astuce pour déterminer la taille du champ:

public static int FieldSize(int Field) { return sizeof(int); }
public static int FieldSize(bool Field) { return sizeof(bool); }
public static int FieldSize(SomeStructType Field) { return sizeof(SomeStructType); }
0
David Chen