web-dev-qa-db-ja.com

C#でフィールドのサイズをバイト単位で取得する

クラスがあり、そのフィールドを調べて、最終的に各フィールドに必要なバイト数を報告します。すべてのフィールドがInt32、byteなどのタイプであると仮定します。

フィールドが何バイト必要かを簡単に調べるにはどうすればよいですか?

私のようなものが必要です:

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

基本的にできません。パディングに依存します。これは、使用しているCLRバージョンやプロセッサなどに基づいている可能性があります。他のオブジェクトへの参照がないと仮定すると、オブジェクトの合計サイズを計算するのは簡単です。 GC.GetTotalMemory を基点に使用し、配列に型の新しいインスタンスへの参照を入力してから、GetTotalMemoryを再度呼び出します。 1つの値を他の値から離し、インスタンスの数で割ります。おそらく、単一のインスタンスを事前に作成して、新しいJITtedコードが数値に寄与しないようにする必要があります。はい、それは聞こえるほどハックです-しかし、私は今までそれを良い効果に使用しました。

ちょうど昨日、このための小さなヘルパークラスを作成することをお勧めします。興味があれば教えてください。

編集:他に2つの提案があり、私はそれらの両方に対処したいと思います。

まず、 sizeof 演算子:これは、抽象の中で型が占めるスペースの大きさのみを示し、その周りにパディングは適用されません。 (構造内のパディングは含まれますが、別の型内のその型の変数に適用されるパディングは含まれません。)

次に、 Marshal.SizeOf :これは、マーシャリング後のアンマネージサイズのみを表示し、メモリ内の実際のサイズは表示しません。ドキュメントが明示的に述べているように:

返されるサイズは、実際にはアンマネージ型のサイズです。オブジェクトの非管理サイズと管理サイズは異なる場合があります。文字タイプの場合、サイズはそのクラスに適用されるCharSet値の影響を受けます。

繰り返しますが、パディングは違いを生むことができます。

パディングが関連するということの意味を明確にするために、次の2つのクラスを検討してください。

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

X86ボックスでは、FourBytesのインスタンスは12バイト(オーバーヘッドを含む)かかります。 FiveBytesのインスタンスは16バイトを取ります。唯一の違いは「e」変数です。したがって、4バイトかかりますか?まあ、ある種の...そしてない種の。かなり明らかに、FiveBytesから単一の変数を削除してサイズを12バイトに戻すことができますが、それは変数のeachを意味するものではありません4バイトを使用します(それらすべてを削除することを考えてください!)。単一の変数のコストは、ここではあまり意味のある概念ではありません。

98
Jon Skeet

質問者のニーズに応じて、Marshal.SizeOfは必要なものを提供する場合と提供しない場合があります。 (Jon Skeetが答えを投稿した後に編集)。

using System;
using System.Runtime.InteropServices;

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

Jkerschが言うように、sizeofは使用できますが、残念なことに値の型でのみ使用できることに注意してください。クラスのサイズが必要な場合は、Marshal.SizeOfが最適です。

Jon Skeetは、sizeofもMarshal.SizeOfも完璧ではない理由を説明しました。質問者は、どちらが彼の問題に受け入れられるかを決める必要があると思います。

Jon Skeetsの彼の答えのレシピから、彼が参照しているヘルパークラスを作成しようとしました。改善のための提案を歓迎します。

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;
    }
}

使用法:

Tの新しいインスタンスを生成するFuncを使用して作成する必要があります。同じインスタンスが毎回返されないようにしてください。例えば。これは大丈夫です:

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

私はこれをILレベルまで徹底的に煮詰めなければなりませんでしたが、ついにこの機能を非常に小さなライブラリでC#に組み込みました。

bitbucket で入手できます(BSDライセンス)

サンプルコード:

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;
}

基本的に、私がしたことは、sizeof IL命令の簡単なクラスメソッドラッパーを書くことでした。この命令は、オブジェクトへの参照が使用するメモリの生の量を取得します。たとえば、Tの配列がある場合、sizeof命令は各配列要素が何バイト離れているかを示します。

これは、C#のsizeof演算子とは大きく異なります。たとえば、静的な方法で他のサイズを取得することは実際には不可能であるため、C#では純粋な値の型のみが許可されます。対照的に、sizeof命令はランタイムレベルで機能します。したがって、この特定のインスタンス中に型への参照が使用するメモリが多く返されます。

blog で、さらに詳しい情報ともう少し詳細なサンプルコードを見ることができます。

5
Earlz

アライメントを考慮することなく、間接的に行うことができます。タイプインスタンスを参照するバイト数は、サービスフィールドサイズ+タイプフィールドサイズです。サービスフィールド(32xではそれぞれ4バイト、64x 8バイト):

  1. SysblockIndex
  2. メソッドテーブルへのポインタ
  3. +オプション(配列のみ)配列サイズ

したがって、フィールドがないクラスの場合、彼のインスタンスは32xマシンで8バイトを使用します。 1つのフィールドを持つクラスの場合、同じクラスインスタンスを参照するため、このクラスは(64x)を取ります。

Sysblockindex + pMthdTable +クラスの参照= 8 + 8 + 8 = 24バイト

値型の場合、インスタンスフィールドはありません。したがって、フィールドサイズのみを取ります。たとえば、1つのintフィールドを持つ構造体がある場合、32xマシンでは4バイトのメモリしか必要ありません。

4
Sergey Popov

タイプがある場合は、sizeof演算子を使用します。タイプのサイズをバイト単位で返します。例えば.

Console.WriteLine(sizeof(int));

出力されます:

4

最も簡単な方法は:int size = *((int*)type.TypeHandle.Value + 1)

これは実装の詳細であることを知っていますが、GCはこれに依存しており、効率のためにメソッドテーブルの開始に近い必要があり、GCコードの複雑さを将来誰も変更しないことを考慮に入れる必要があります。実際、.net framework + .netコアのすべてのマイナー/メジャーバージョンで機能します。 (現在、1.0をテストすることはできません)
より信頼性の高い方法が必要な場合は、[StructLayout(LayoutKind.Auto)]が同じ順序で同じフィールドを持つ動的アセンブリで構造体を発行し、そのサイズを sizeof IL命令で取得します。この値を返すだけのstruct内の静的メソッドを発行することもできます。次に、オブジェクトヘッダーに2 * IntPtr.Sizeを追加します。これにより、正確な値が得られます。
ただし、クラスが別のクラスから派生している場合は、基本クラスの各サイズを個別に見つけて、ヘッダー用に再度+ 2 * Inptr.Sizeを追加する必要があります。これを行うには、BindingFlags.DeclaredOnlyフラグを使用してフィールドを取得します。

0
TakeMeAsAGuest

フィールドのサイズを決定するためのトリックとして、メソッドのオーバーロードを使用できます。

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