C Sharpのデータ型

この記事では、プログラミング言語C#のデータ型について説明する。
それぞれのデータ型が取り込まれたバージョンについては、親記事C#を参照のこと。C#のデータ型は共通言語基盤 (CLI) のデータ型と密接な関係があり、ターゲットとする.NET Frameworkあるいは.NET Coreのバージョンによっては使用できないものも存在する。
参照型と値型.
C#の任意の型は (後述するポインタ型を除いて) 参照型と値型に二分される。
参照型の変数が、実際のオブジェクトを格納する領域の参照のみを保持するのに対して、
値型の変数は、実際のオブジェクトの値全てを保持する。
この特徴により、値の代入や引数の受け渡しの挙動が大きく異なって見える。
参照型にも値型にも分別されない型.
ポインタ型はobject型と相互に変換することができず、参照型にも値型にも相当しない。
「任意の型について」記載している内容は、ポインタ型が当てはまらないことも多いことに留意されたい。例えば、#ジェネリクスの型引数にポインタ型を指定することはできない。
基本的なデータ型.
C#において、キーワード指定されたデータ型（組み込み型、）を下に示す。
decimal.
C#のcodice_6型は128bitの十進浮動小数点数であるが、以下の点で他の浮動小数点数とは大きく異なる。
主なデータ型.
クラス型.
クラス型は、関連するメンバーをカプセル化した参照型である。メンバーにはフィールド、メソッド、プロパティ、コンストラクタなどが含まれる。
構造体型.
構造体型は、関連するメンバーをカプセル化した値型である。
インターフェイス型.
インターフェイス型は、型の多重継承を実現するための抽象型である。
列挙型.
列挙型は、名前付き定数の集まりで構成される値型である。
codice_28, codice_29, codice_30, codice_31, codice_32, codice_33, codice_34, codice_35 のいずれかの整数型を基となる型として宣言できる。
/// <summary>光の三原色を1bitずつで表す</summary>
[Flags]
enum RgbColor : byte {
 Black = 0,
 Red = 1 « 0,
 Green = 1 « 1,
 Blue = 1 « 2,
 Cyan = Green | Blue,
 Magenta = Blue | Red,
 Yellow = Red | Green,
 White = Red | Green | Blue,
static class RgbColorExtension {
 /// <summary>White (0b111) とのXORを返す拡張メソッド。C# 3.0以降が必要。</summary>
static void Main() {
 // Magenta を定義していない場合、"Red, Blue" が表示される。
 Console.WriteLine(RgbColor.Green.GetComplementaryColor());
 // Yellow を定義していない場合、"Red, Green" が表示される。
 Console.WriteLine(RgbColor.Blue.GetComplementaryColor());
デリゲート型.
デリゲート型は、オブジェクトインスタンスへの参照とメソッドへの参照をまとめてカプセル化する参照型である。
配列型.
配列型は、初期化時に指定した型と長さを持つ参照型である。
ジャグ配列 (配列の配列) のほかに、真の多次元配列がサポートされる。
匿名型.
匿名型は一時的に使用される型を簡単に定義するためのクラス型。読み取り専用プロパティのみを持つ。
特定の型名を持たないが、codice_46キーワードで宣言したローカル変数に格納することができる。
dynamic型.
dynamic型は、動的型付け変数を表す型である。
コンパイル時の型チェックをバイパスし、実行時に演算が解決される。
ポインタ型.
ポインタ型はC++ライクなポインタ操作を可能とする型である。
通常の共通型システムの型とは異なり、共通言語ランタイムでは検査されない。
codice_47 キーワードによって指定された範囲でしか利用できないようになっており、
コンパイル時には codice_48 オプションを指定する必要がある。
ジェネリクス.
C#はジェネリックプログラミングに対応する。
クラス、構造体、インターフェイス、デリゲート、メソッドに対して、型引数を適用することができる。
型引数はcodice_49/codice_50パラメータと共に指定することで、共変性、反変性を持つ。
/// <summary>型引数を取るinterfaceの例</summary>
/// <typeparam name="TIn">反変な型引数</typeparam>
/// <typeparam name="TOut">共変な型引数</typeparam>
/// <typeparam name="T">不変な型引数</typeparam>
interface ISampleTypeParameter<in TIn, out TOut, T> {
 void Procedure(TIn input);
 TOut Supply();
 T Function(T input);
 void UseDelegate(Func<TOut, TIn> func);
 void UseDelegate(Func<T, T> func);
 Func<TIn, TOut> SupplyDelegate1();
 Func<T, T> SupplyDelegate2();
特別扱いされる型.
C#において、文法上特別扱いを受ける型の例を下に示す。
Exception型.
codice_51型および派生型のインスタンスは、
codice_52可能であり、
codice_53-codice_54構文で例外処理に対応する。
Attribute型.
codice_55型の派生型は、
コードの要素に対して属性を付けることができる。
例えばcodice_56であれば、クラス宣言の手前でcodice_57を記述することで、そのクラスが直列化可能であることを宣言できる。
IEnumerable<T>, IEnumerator<T>.
codice_58,codice_59インターフェイスを戻り値とするメソッドやgetアクセサは、
codice_60キーワードによるコルーチンに対応し、ジェネレータを簡潔に記述できる。
IDisposable.
codice_61インターフェイスを実装した型は、
codice_62ステートメントによるリソースの安全な破棄に対応する。
Nullable<T>.
型名の後ろにcodice_63を付けることで、codice_64型の糖衣構文となる。
また、値型であるにもかかわらずcodice_19を代入することができる。
int? maybeNum = null;
int correctlyNum = maybeNum ?? 0;
上記構文は下記の糖衣構文である。
Nullable<int> maybeNum = null;
int correctlyNum = maybeNum.HasValue ? maybeNum.Value : 0;
また、Null許容型では、一部を除いて各演算子が再定義される。
// Null許容int型の演算子再定義
int? x = null;
int? y = 123;
// nullを含む単項演算子、二項演算子 (NULL,NULL,NULL,NULL)
Console.WriteLine((x + y)?.ToString() ?? "NULL");
Console.WriteLine((x « y)?.ToString() ?? "NULL");
Console.WriteLine((-x)?.ToString() ?? "NULL");
Console.WriteLine((++x)?.ToString() ?? "NULL");
// 片方がnullの場合の比較演算子(false,false,false,true)
Console.WriteLine($"{x <= y},{x == y},{x >= y},{x != y}");
// 双方がnullの場合の比較演算子(false,true,false,false)
int? z = null;
Console.WriteLine($"{x <= z},{x == z},{x >= z},{x != z}");
// Null許容bool型の演算子再定義
bool? b1 = null;
bool? b2 = true;
// nullを含む単項演算子、二項演算子 (NULL,NULL)
Console.WriteLine((b1 & b2)?.ToString() ?? "NULL");
Console.WriteLine((!b1)?.ToString() ?? "NULL");
// ショートサーキットは再定義されない
//Console.WriteLine((b1 && b2)?.ToString() ?? "NULL");
// Null許容bool型のままでは条件式では使用できない
Task, Task<T>, ValueTask<T>.
async/awaitキーワードによる、非同期プログラミングに対応する。
ValueTuple<>.
2個以上の要素を持つValueTupleについては、例えばcodice_66と書くことでcodice_67型の糖衣構文となる。
また、例えばcodice_68と書くことで、リテラルを表記できる。
IFormattable, FormattableString.
挿入文字列リテラルから暗黙の型変換が可能である。
// 書式指定可能(IFormattable)型で受け取ることができる。
IFormattable fmt = $"id: {id:D}, money: {money:C}";
var str = fmt.ToString(null, new System.Globalization.CultureInfo("ja-JP"));
Expression<T>.
式形式のラムダから暗黙の型変換が可能である。これによって、式木をシンプルに記述できる。
// 本体が式のラムダ式をExpression型に格納することができる。
System.Linq.Expressions.Expression<Func<int, int» expression = x => x * x;
// 本体がステートメントのラムダ式はExpressionとして扱えない。
//System.Linq.Expressions.Expression<Func<int, int» expression = x => { return x * x; };
Span<T>.
codice_69で割り当てたメモリの代入が可能である。
// C# 7.2から可能
Span<int> span1 = stackalloc int[4];
// C# 7.3から初期化子にも対応
Span<int> span2 = stackalloc[] { 10, 20, 30, 40 };
特別扱いされるメソッド.
C#において、ダック・タイピング的な扱いを受ける例を下に示す。
特定のメソッドが定義されていれば、クラス間の継承関係に関わらずC#の文法の恩恵を受けることができる。
Add().
codice_70が実装されcodice_71メソッドが定義された型では、コレクション初期化の構文を利用することができる。
/// <summary>IEnumerableを実装、かつ、Addメソッドを持つ</summary>
class MultiplyList : IEnumerable {
 List<int> _list = new List<int>();
 public void Add(int x) => _list.Add(x);
 public void Add(int x, int y) => _list.Add(x * y);
 public void Add(int x, int y, int z) => _list.Add(x * y * z);
 IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
// Add()のオーバーロードがそれぞれ呼び出される。
var list = new MultiplyList() {
 { 1 },
 { 2, 3 },
 { 4, 5, 6 },
GetEnumerator().
codice_72メソッドが定義された型では、codice_73構文を利用することができる。
/// <summary>GetEnumeratorメソッドを実装する型</summary>
class DaysInMonths {
 public IEnumerator<int> GetEnumerator() {
 yield return 31; yield return 28; yield return 31;
 yield return 30; yield return 31; yield return 30;
 yield return 31; yield return 31; yield return 30;
 yield return 31; yield return 30; yield return 31;
var months = new DaysInMonths();
// foreach構文
foreach (var days in months) {
 Console.WriteLine(days);
Deconstruct().
codice_74メソッドが定義された型では、分解構文を利用することができる。拡張メソッドによる定義でも構わない。
/// <summary>Deconstructメソッドを実装する型</summary>
class Person {
 /// <summary>分解メソッド</summary>
 public void Deconstruct(out string name, out int age) {
 name = Name;
 age = Age;
var person = new Person() { Name = "Taro", Age = 18 };
// 分解構文
var (name, age) = person;
// 以下のDeconstructメソッド呼び出しと同じ
person.Deconstruct(out var name, out var age);
GetAwaiter().
codice_75メソッドが定義された型では、codice_76演算子を利用することができる。拡張メソッドによる定義でも構わない。
GetPinnableReference().
codice_77メソッドが定義された型では、codice_78ステートメント内でのポインタ指定時に構文糖の恩恵を得る。
型情報.
codice_79型を利用することで、様々な型情報にアクセスすることができる。
この型情報からリフレクションによるプログラミングに対応する。
次のコードはcodice_79型の例である。
// コンパイル時型情報
Type typeAtCompiling = typeof(int); /* System.Int32 */
// 実行時型情報
object obj = "Hello!";
Type typeAtExecuting = obj.GetType(); /* System.String */
// 指定した名前の型情報を取得
Type typeByNominate = Type.GetType("System.EventHandler");
CheckType(typeAtCompiling);
CheckType(typeAtExecuting);
CheckType(typeByNominate);
static void CheckType(Type type) {
 Console.WriteLine(type);
 Console.WriteLine("* 参照型: " + type.IsClass);
 Console.WriteLine("** インターフェイス型: " + type.IsInterface);
 Console.WriteLine("** 配列型: " + type.IsArray);
 Console.WriteLine("** デリゲート型: " + type.IsSubclassOf(typeof(MulticastDelegate)));
 Console.WriteLine("* 値型: " + type.IsValueType);
 Console.WriteLine("** 列挙型: " + type.IsEnum);
 Console.WriteLine("** プリミティブ: " + type.IsPrimitive);
 Console.WriteLine("* 総称型: " + type.IsGenericType);
 Console.WriteLine("* ポインタ型: " + type.IsPointer);