プリミティブ型について
厳密に言うと「プリミティブ型」という型は存在せず、「値型」か「参照型」しか存在しないのですが、コンパイラが認識して特別扱いをする型についてはプリミティブ型と言う場合があります。
"組み込みのデータ型"とも。(JIS X 3016で定義されている用語では"組み込みの値型""組み込みの参照型"とも)
System名前空間で宣言されているオブジェクトのエイリアスです。(System.Int32=>intとか)
なので、ある変数を宣言するときにSystem.Int32で宣言しても、int(C#の場合。VisualBasicではInteger)で宣言しても出力されるILはまったく同じになります。C#で書いている時にIntelliSenseで"String"と"string"が候補に挙がってくるのはこれが原因。恐らくC++とかに合わせてstringというエイリアスを設定したのだろうけど、どっちを使うべきか混乱する人が居るんですよね。
通常の値型や参照型と大きく異なる点として、リテラルとして記述可能という点があります。
例えば"123.ToString()"という記述が可能です。
using System; namespace LiteralTest { class Program { static void Main(string[] args) { string s = 123.ToString(); // これはOK Console.WriteLine(s); Console.WriteLine(123.GetType().Name); // これもOK } } }
コンソールに出力される内容は下記になります。
123 Int32
また、リテラルからなる式はコンパイル時に式の評価が行われます。
using System; namespace AtCompile { class Program { static void Main(string[] args) { Console.WriteLine(1 + 10 + 100); Console.WriteLine("A" + "b" + "C"); } } }
ILDasmで逆アセンブルすると該当のMainメソッドの内容は
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // コード サイズ 21 (0x15) .maxstack 8 IL_0000: nop IL_0001: ldc.i4.s 111 IL_0003: call void [mscorlib]System.Console::WriteLine(int32) IL_0008: nop IL_0009: ldstr "AbC" IL_000e: call void [mscorlib]System.Console::WriteLine(string) IL_0013: nop IL_0014: ret } // end of method Program::Main
となります。
IL_0001とIL_0009で評価後の値がリテラルとして設定されています。
余談ですが、IL命令に関してStandard ECMA-335(Common Language Infrastructure)を参照すると
ldc.i4.s | ldcはload numeric constant命令、i4は4バイト整数でInt32、sは符号あり |
ldstr | ldstrはload a literal string命令 |
と読むようです。
System.Decimal構造体はプリミティブ型の中でも扱いが特殊です。
using System.Numerics; namespace OpCode { class Program { static void Main(string[] args) { ValueType(); StructureType(); } private static void ValueType() { int i = 0; i++; // add IL命令に変換される long l = 0; l--; // sub IL命令に変換される } private static void StructureType() { decimal d = 0; d++; // op_Incrementメソッドの呼び出しに変換される(Decimal構造体で演算子オーバーロードされている) BigInteger b = 0; b--; // op_Decrementメソッドの呼び出しに変換される(BigInteger構造体で演算子オーバーロードされている) } } }
ILDasmで逆アセンブルすると
.method private hidebysig static void ValueType() cil managed { // コード サイズ 16 (0x10) .maxstack 2 .locals init ([0] int32 i, [1] int64 l) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: ldc.i4.1 IL_0005: add IL_0006: stloc.0 IL_0007: ldc.i4.0 IL_0008: conv.i8 IL_0009: stloc.1 IL_000a: ldloc.1 IL_000b: ldc.i4.1 IL_000c: conv.i8 IL_000d: sub IL_000e: stloc.1 IL_000f: ret } // end of method Program::ValueType
インクリメント=>add命令、デクリメント=>sub命令に変換されています。
これらInt32やInt64構造体は演算子の定義が行われていません。コンパイラが扱い方を知っているため、適切なIL命令に変換を行います。(例えばIL命令でsigned版とunsigned版がある場合、これらはコンパイラによって適切に変換されます。)
しかし、Decimal(とプリミティブ型ではないのですがBigInteger構造体)については
.method private hidebysig static void StructureType() cil managed { // コード サイズ 30 (0x1e) .maxstack 2 .locals init ([0] valuetype [mscorlib]System.Decimal d, [1] valuetype [System.Numerics]System.Numerics.BigInteger b) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: newobj instance void [mscorlib]System.Decimal::.ctor(int32) IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Increment(valuetype [mscorlib]System.Decimal) IL_000e: stloc.0 IL_000f: ldc.i4.0 IL_0010: call valuetype [System.Numerics]System.Numerics.BigInteger [System.Numerics]System.Numerics.BigInteger::op_Implicit(int32) IL_0015: stloc.1 IL_0016: ldloc.1 IL_0017: call valuetype [System.Numerics]System.Numerics.BigInteger [System.Numerics]System.Numerics.BigInteger::op_Decrement(valuetype [System.Numerics]System.Numerics.BigInteger) IL_001c: stloc.1 IL_001d: ret } // end of method Program::StructureType
インクリメント=>Decimal::op_Incrementメソッド、デクリメント=>BigInteger::op_Decrementメソッド呼び出しに変換されます。
DecimalやBigIntegerはmsdnライブラリを参照すると、インクリメントやデクリメントは演算子オーバーロードが実装されているので、オーバーロード呼び出しに変換された形になります。