C Sharpのデータ型
この記事では、プログラミング言語C#のデータ型について説明する。
それぞれのデータ型が取り込まれたバージョンについては、親記事C#を参照のこと。C#のデータ型は共通言語基盤 (CLI) のデータ型と密接な関係があり、ターゲットとする.NET Frameworkあるいは.NET Coreのバージョンによっては使用できないものも存在する。
C#の任意の型は (後述するポインタ型を除いて) 参照型と値型に二分される。
参照型の変数が、実際のオブジェクトを格納する領域の参照のみを保持するのに対して、
値型の変数は、実際のオブジェクトの値全てを保持する。
この特徴により、値の代入や引数の受け渡しの挙動が大きく異なって見える。
object
dynamic
string
bool
char
decimal
ポインタ型はobject型と相互に変換することができず、参照型にも値型にも相当しない。
「任意の型について」記載している内容は、ポインタ型が当てはまらないことも多いことに留意されたい。例えば、#ジェネリクスの型引数にポインタ型を指定することはできない。
C#において、キーワード指定されたデータ型(組み込み型、built-in types)を下に示す。
C#のdecimal型は128bitの十進浮動小数点数であるが、以下の点で他の浮動小数点数とは大きく異なる。
非数NaN, 負の無限大NegativeInfinity, 正の無限大PositiveInfinityは表現できない。
ゼロ除算では、DivideByZeroExceptionをthrowする。
decimal.MaxValueを上回る、あるいはdecimal.MinValueを下回る演算では、OverFlowExceptionをthrowする。
指数部は常に非正数 [-28, 0] を取る。
仮数部は単に96bit符号無し整数として扱われる。
符号部は 0 ならば正、1 ならば負を表現する。
指数部は 0 〜 28 の値を取り、実際にはその反数の値を表現する。基数は10である。
仮数部は 0 〜 79,228,162,514,264,337,593,543,950,335 の値を表現する。
クラス型は、関連するメンバーをカプセル化した参照型である。メンバーにはフィールド、メソッド、プロパティ、コンストラクタなどが含まれる。
クラスはclassキーワードを使用して宣言する。
クラスは1個の基底クラスを継承する。 継承する基底クラスを指定しない場合、System.Objectが直接基底クラスとなる。
クラスは0個以上の複数のインターフェイスを実装できる。
abstract指定されたクラスは「抽象クラス」と呼ばれ、インスタンス化できない。派生クラスはabstractでない限りインスタンス化できる。
sealed指定されたクラスは、これ以上派生できない。
static指定されたクラスは「静的クラス」と呼ばれ、インスタンス化も派生もできない。静的なメンバーしか持たず、System.Object以外を継承することはない。
クラス型変数の初期値はnullとなる。
構造体型は、関連するメンバーをカプセル化した値型である。
構造体はstructキーワードを使用して宣言する。
構造体は明示的に基底クラスを継承することができない。全ての構造体はSystem.ValueTypeが直接基底クラスとなる。
構造体は0個以上の複数のインターフェイスを実装できる。
構造体は派生できない。
構造体は暗黙にデフォルトコンストラクタを持ち、構造体型変数の全てのフィールドはdefaultで初期化される。
readonly指定された構造体では、フィールドは全てreadonly指定されている必要がある(C# 7.2以降)。
ref指定された構造体は常にスタックに確保され、ボックス化されない(C# 7.2以降)。
インターフェイス型は、型の多重継承を実現するための抽象型である。
インターフェイスはinterfaceキーワードを使用して宣言する。
インターフェイスは0個以上の複数のインターフェイスを継承できる。
インターフェイスはインスタンスフィールドを持たない。
インターフェイスはメソッド、プロパティ、インデクサ、イベントの宣言を含むことができるが、実装を持つことはできない。ただしC# 8.0以降では、これらのデフォルト実装を持つことができる。
インターフェイスは定数および静的メンバーを持たない。ただしC# 8.0以降では、これらを持つことができる。
インターフェイス型変数の初期値はnullとなる。
列挙型は、名前付き定数の集まりで構成される値型である。
byte, sbyte, short, ushort, int, uint, long, ulong のいずれかの整数型を基となる型として宣言できる。
列挙型はenumキーワードを使用して宣言する。
列挙型は明示的に基底クラスを継承することができない。全ての列挙型はSystem.Enumが直接基底クラスとなる。
列挙型は派生できない。
列挙型変数の初期値は基となる整数型の0を表す名前付き定数となる。
FlagsAttribute属性が付いた列挙型はビット演算可能となり、値の組合せを表現できる。
デリゲート型は、オブジェクトインスタンスへの参照とメソッドへの参照をまとめてカプセル化する参照型である。
デリゲート型はdelegateキーワードを使用して宣言する。
デリゲート型は明示的に基底クラスを継承することができない。全てのデリゲート型はSystem.Delegateが直接基底クラスとなる。
デリゲート型は派生できない。
デリゲート型変数の初期値はnullとなる。
デリゲート型変数は2項演算子+および加算代入演算子+=で複数の値を連結できる。2項演算子-および減算代入演算子-=でデリゲートを削除できる。
配列型は、初期化時に指定した型と長さを持つ参照型である。
ジャグ配列 (配列の配列) のほかに、真の多次元配列がサポートされる。
匿名型は一時的に使用される型を簡単に定義するためのクラス型。読み取り専用プロパティのみを持つ。
特定の型名を持たないが、varキーワードで宣言したローカル変数に格納することができる。
dynamic型は、動的型付け変数を表す型である。
コンパイル時の型チェックをバイパスし、実行時に演算が解決される。
ポインタ型はC++ライクなポインタ操作を可能とする型である。
通常の共通型システムの型とは異なり、共通言語ランタイムでは検査されない。
unsafe キーワードによって指定された範囲でしか利用できないようになっており、
コンパイル時には /unsafe オプションを指定する必要がある。
C#はジェネリックプログラミングに対応する。
クラス、構造体、インターフェイス、デリゲート、メソッドに対して、型引数を適用することができる。
型引数はout/inパラメータと共に指定することで、共変性、反変性を持つ。
C#において、文法上特別扱いを受ける型の例を下に示す。
Exception型および派生型のインスタンスは、
throw可能であり、
try-catch構文で例外処理に対応する。
Attribute型の派生型は、
コードの要素に対して属性を付けることができる。
例えばSerializableAttributeであれば、クラス宣言の手前で[Serializable]を記述することで、そのクラスが直列化可能であることを宣言できる。
IEnumerable<T>,IEnumerator<T>インターフェイスを戻り値とするメソッドやgetアクセサは、
yieldキーワードによるコルーチンに対応し、ジェネレータを簡潔に記述できる。
IDisposableインターフェイスを実装した型は、
usingステートメントによるリソースの安全な破棄に対応する。
型名の後ろに?を付けることで、Nullable<T>型の糖衣構文となる。
また、値型であるにも関わらずnullを代入することができる。
上記構文は下記の糖衣構文である。
また、Null許容型では、一部を除いて各演算子が再定義される。
async/awaitキーワードによる、非同期プログラミングに対応する。
2個以上の要素を持つValueTupleについては、例えば(string s, int n)と書くことでValueTuple型の糖衣構文となる。
また、例えば("answer", 42)と書くことで、リテラルを表記できる。
挿入文字列リテラルから暗黙の型変換が可能である。
式形式のラムダから暗黙の型変換が可能である。これによって、式木をシンプルに記述できる。
stackallocで割り当てたメモリの代入が可能である。
C#において、ダック・タイピング的な扱いを受ける例を下に示す。
特定のメソッドが定義されていれば、クラス間の継承関係に関わらずC#の文法の恩恵を受けることができる。
IEnumerableが実装されAdd()メソッドが定義された型では、コレクション初期化の構文を利用することができる。
GetEnumerator()メソッドが定義された型では、foreach構文を利用することができる。
Deconstruct()メソッドが定義された型では、分解構文を利用することができる。拡張メソッドによる定義でも構わない。
GetAwaiter()メソッドが定義された型では、await演算子を利用することができる。拡張メソッドによる定義でも構わない。
GetPinnableReference()メソッドが定義された型では、fixedステートメント内でのポインタ指定時に構文糖の恩恵を得る。
System.Type型を利用することで、様々な型情報にアクセスすることができる。
この型情報からリフレクションによるプログラミングに対応する。
次のコードはSystem.Type型の例である。
typeof(T)はTの型情報を取得する。
obj.GetType()はobjの実行時型情報を取得する。
Type.GetType(完全修飾型名)は指定した完全修飾型名の型情報を取得する。