C# は厳密に型指定された言語です。 すべての変数と定数には、値に評価されるすべての式と同様に、型があります。 C# は、主に 規範的な型システムを使用します。
標準型システムでは、名前を使用して各型を識別します。 C# では、 struct、 class、 interface 型 ( record 型を含む) はすべて、その名前で識別されます。 すべてのメソッド宣言では、各パラメーターと戻り値の名前、型、および種類 (値、参照、または出力) を指定します。 .NET クラス ライブラリは、さまざまなコンストラクトを表す組み込みの数値型と複合型を定義します。 これらのコンストラクトには、ファイル システム、ネットワーク接続、オブジェクトのコレクションと配列、および日付が含まれます。 一般的な C# プログラムでは、クラス ライブラリの型と、プログラムの問題ドメインに固有の概念をモデル化するユーザー定義型が使用されます。
C# では、タプルや匿名 型などの構造型もサポートされています。 構造型 は、各メンバーの名前と型、および式内のメンバーの順序によって定義されます。 構造型には一意の名前がありません。
型に格納される情報には、次の項目を含めることができます。
- 型の変数に必要なストレージ領域。
- 表すことができる最大値と最小値。
- 含まれるメンバー (メソッド、フィールド、イベントなど)。
- 継承元となった基本型。
- 実装するインターフェイス。
- 許可される操作。
コンパイラは型情報を使用して、コードで実行されるすべての操作が 型セーフであることを確認します。 たとえば、 int型の変数を宣言すると、コンパイラでは加算演算と減算演算で変数を使用できます。
bool型の変数に対して同じ操作を実行しようとすると、次の例に示すように、コンパイラによってエラーが生成されます。
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
注
C および C++ 開発者は、C# では、 bool が intに変換できないことに注意してください。
コンパイラは、型情報をメタデータとして実行可能ファイルに埋め込みます。 共通言語ランタイム (CLR) は、実行時にそのメタデータを使用して、メモリの割り当てと再利用時の型の安全性をさらに保証します。
変数宣言での型の指定
プログラムで変数または定数を宣言する場合は、その型を指定するか、 var キーワードを使用してコンパイラが型を推論できるようにする必要があります。 次の例は、組み込みの数値型と複雑なユーザー定義型の両方を使用するいくつかの変数宣言を示しています。
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = [0, 1, 2, 3, 4, 5];
var query = from item in source
where item <= limit
select item;
メソッド宣言では、メソッド パラメーターの型と戻り値を指定します。 次のシグネチャは、入力引数として int を必要とし、文字列を返すメソッドを示しています。
public string GetName(int ID)
{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = ["Spencer", "Sally", "Doug"];
変数を宣言した後は、新しい型で再宣言することはできません。また、宣言された型と互換性のない値を割り当てることはできません。 たとえば、 int を宣言してから、 trueのブール値を割り当てることはできません。 ただし、新しい変数に値を割り当てたり、メソッド引数として渡したりする場合など、値を他の型に変換できます。 コンパイラは、データ損失を引き起こさない 型変換 を自動的に実行します。 データ損失の原因となる可能性のある変換には、ソース コードへの キャスト が必要です。
詳細については、「 キャストと型変換」を参照してください。
組み込み型
C# には、標準の組み込み型のセットが用意されています。 これらの型は、整数、浮動小数点値、ブール式、テキスト文字、10 進値、およびその他のデータ型を表します。 言語には、組み込みの string 型と object 型も含まれています。 これらの型は、任意の C# プログラムで使用できます。 組み込み型の完全な一覧については、「 組み込み型」を参照してください。
カスタム型
関連するデータ メンバーを格納するために タプルを 使用して構造型を作成します。 これらの型は、複数のメンバーを保持する構造体を提供します。 タプルの動作は制限されています。 これらは値のコンテナーです。 これらは、作成できる最も簡単な型です。 後で動作が必要になると判断することがあるかもしれません。 その場合は、タプルを struct または classに変換できます。
独自のカスタム型を作成するには、 struct、 class、 interface、 enum、および record コンストラクトを使用します。 .NET クラス ライブラリ自体は、独自のアプリケーションで使用できるカスタム型のコレクションです。 既定では、クラス ライブラリで最もよく使用される型は、任意の C# プログラムで使用できます。 他の型を使用できるようにするには、それらを提供するパッケージへのパッケージ参照を明示的に追加します。 コンパイラがパッケージへの参照を持ったら、そのパッケージのアセンブリで宣言されている型の変数と定数をソース コードで宣言できます。
型を定義するときに最初に行う決定の 1 つは、型に使用するコンストラクトを決定することです。 次の一覧は、その最初の決定に役立ちます。 一部の選択肢は重複しています。 ほとんどのシナリオでは、複数のオプションが妥当な選択肢です。
- データ型がアプリ ドメインの一部ではなく、動作を含まない場合は、構造型を使用します。
- データ ストレージのサイズが小さく、64 バイト以下の場合は、
structまたはrecord structを選択します。 - 型が変更できない場合、または非破壊的な変更が必要な場合は、
structまたはrecord structを選択します。 - 型に等しい値セマンティクスが必要な場合は、
record classまたはrecord structを選択します。 - 型が主にデータを格納する場合は、最小限の動作で、
record classまたはrecord structを選択します。 - 型が継承階層の一部である場合は、
record classまたはclassを選択します。 - 型がポリモーフィズムを使用する場合は、
classを選択します。 - 主な目的が動作の場合は、
classを選択します。
コントラクトをモデル化する interface を選択 することもできます。これは、関連のない型によって実装できるメンバーによって記述される動作です。 インターフェイスは抽象であり、そのインターフェイスから継承するすべての class または struct 型によって実装される必要があるメンバーを宣言します。
共通型システム
共通型システムでは、継承の原則がサポートされています。 型は、基本型と呼ばれる他の 型から派生できます。 派生型は、基本型のメソッド、プロパティ、およびその他のメンバーを継承します (いくつかの制限があります)。 さらに、基本型は他の型から派生できます。その場合、派生型は継承階層内の両方の基本型のメンバーを継承します。
System.Int32 (C# キーワード: int) などの組み込みの数値型を含むすべての型は、最終的には単一の基本型 (System.Object (C# キーワード: object) から派生します。 この統合型階層は、 共通型システム (CTS) と呼ばれます。 C# での継承の詳細については、「 継承」を参照してください。
CTS の各型は、 値型 または 参照型として定義されます。 これらの型には、.NET クラス ライブラリ内のすべてのカスタム型と、独自のユーザー定義型が含まれます。
-
structキーワードまたはrecord structキーワードを使用して定義する型は、値型です。 組み込みの数値型はすべてstructs。 -
class、record class、またはrecordキーワードを使用して定義する型は参照型です。
参照型と値型には、コンパイル時の規則と実行時の動作が異なります。
注
最も一般的に使用される型はすべて、 System 名前空間に編成されています。 ただし、型が含まれる名前空間には、値型と参照型のどちらであるかは関係ありません。
クラスと構造体は、.NET の共通型システムの基本的なコンストラクトの 2 つです。 各コンストラクトは、基本的には、論理ユニットとして一緒に属する一連のデータと動作をカプセル化するデータ構造です。 データと動作は、クラス、構造体、またはレコードの メンバー です。 メンバーには、この記事で後述するように、メソッド、プロパティ、イベントなどが含まれます。
クラス、構造体、またはレコード宣言は、実行時にインスタンスまたはオブジェクトを作成するために使用するブループリントのようなものです。
Personという名前のクラス、構造体、またはレコードを定義する場合、Personは型の名前です。
p型の変数 Person を宣言して初期化すると、p は Personのオブジェクトまたはインスタンスと言われます。 同じ Person 型の複数のインスタンスを作成でき、各インスタンスのプロパティとフィールドに異なる値を指定できます。
クラスは参照型です。 型のオブジェクトを作成すると、オブジェクトを割り当てる変数は、そのメモリへの参照のみを保持します。 オブジェクト参照を新しい変数に割り当てると、新しい変数は元のオブジェクトを参照します。 1 つの変数で行った変更は、両方とも同じデータを参照するため、もう一方の変数に反映されます。
構造体は値型です。 構造体を作成すると、構造体を割り当てる変数に構造体の実際のデータが保持されます。 新しい変数に構造体を割り当てると、その構造体がコピーされます。 したがって、新しい変数と元の変数には、同じデータの 2 つの個別のコピーが含まれます。 1 つのコピーに加えた変更は、もう一方のコピーには影響しません。
レコード型には、参照型 (record class) または値型 (record struct) のいずれかを指定できます。 レコード型には、値の等価性をサポートするメソッドが含まれています。
一般に、クラスを使用して、より複雑な動作をモデル化します。 通常、クラスには、クラス オブジェクトの作成後に変更するデータが格納されます。 構造体は、小さなデータ構造に最適です。 通常、構造体には、構造体の作成後に変更しないデータが格納されます。 レコード型は、追加のコンパイラ合成メンバーを持つデータ構造です。 通常、レコードには、オブジェクトの作成後に変更しないデータが格納されます。
値型
値型は System.ValueTypeから、そして System.ValueTypeは から派生します。
System.ValueTypeから派生した型は、CLR で特別な動作をします。 値型変数には、その値が直接含まれています。 構造体のメモリは、変数が宣言されているコンテキストでインラインで割り当てられます。 値型 record struct 型を宣言し、 レコードの合成メンバーを含めることができます。
値型には、 struct と enumの 2 つのカテゴリがあります。
組み込みの数値型は構造体であり、アクセスできるフィールドとメソッドがあります。
// constant field on type byte.
byte b = byte.MaxValue;
ただし、単純な非集計型であるかのように、値を宣言してそれらに割り当てます。
byte num = 0xA;
int i = 5;
char c = 'Z';
値型は 封印されます。 System.Int32など、どの値型からも型を派生させることはありません。 構造体は System.ValueTypeからのみ継承できるため、ユーザー定義クラスまたは構造体から継承する構造体を定義することはできません。 ただし、構造体は 1 つ以上のインターフェイスを実装できます。 構造体型は、実装されている任意のインターフェイス型にキャストできます。 このキャストにより、マネージド ヒープ上の参照型オブジェクト内で構造体をラップ するボックス化 操作が発生します。 ボックス化操作は、 System.Object または任意のインターフェイス型を入力パラメーターとして受け取るメソッドに値型を渡すと発生します。 詳細については、「ボックス化とボックス化解除」を参照してください。
独自のカスタム値型を作成するには、 struct キーワードを使用します。 通常、構造体は、次の例に示すように、関連する変数の小さなセットのコンテナーとして使用されます。
public struct Coords(int x, int y)
{
public int X { get; init; } = x;
public int Y { get; init; } = y;
}
構造体の詳細については、「 構造体の型」を参照してください。 値型の詳細については、「値型 」を参照してください。
値型の他のカテゴリは enumです。 列挙型は、名前付き整数定数のセットを定義します。 たとえば、.NET クラス ライブラリの System.IO.FileMode 列挙には、ファイルを開く方法を指定する名前付き定数整数のセットが含まれています。 これは、次の例に示すように定義されています。
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
System.IO.FileMode.Create 定数の値は 2 です。 ただし、人間がソース コードを読む場合は名前の方がはるかに意味があります。そのため、定数リテラル番号の代わりに列挙型を使用することをお勧めします。 詳細については、System.IO.FileModeを参照してください。
すべての列挙体は、System.Enum の派生型である System.ValueType から派生します。 構造体に適用されるすべての規則は、列挙型にも適用されます。 詳細については、「列挙型」を参照してください。
参照型
class、record class、record、delegate、配列、またはinterfaceとして定義する型は、reference typeです。
reference typeの変数を宣言すると、その型のインスタンスで変数を割り当てるか、null演算子を使用して変数を作成するまで、new値が含まれます。 次の例では、クラスの作成と割り当てを示します。
MyClass myClass = new();
MyClass myClass2 = myClass;
interface演算子を使用してnewを直接インスタンス化することはできません。 代わりに、インターフェイスを実装するクラスのインスタンスを作成して割り当てます。 次の例を確認してください。
MyClass myClass = new();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
オブジェクトを作成すると、システムはマネージド ヒープにメモリを割り当てます。 変数は、オブジェクトの場所への参照のみを保持します。 マネージド ヒープの型では、割り当て時と再要求時の両方でオーバーヘッドが必要です。 ガベージ コレクション は、再利用を実行する CLR の自動メモリ管理機能です。 ただし、ガベージ コレクションも高度に最適化されており、ほとんどのシナリオではパフォーマンスの問題は発生しません。 ガベージコレクションについて詳しくは自動メモリ管理を参照してください。
要素が値型の場合でも、すべての配列は参照型です。 配列は、 System.Array クラスから暗黙的に派生します。 次の例に示すように、C# が提供する簡略化された構文を使用して宣言して使用します。
// Declare and initialize an array of integers.
int[] nums = [1, 2, 3, 4, 5];
// Access an instance property of System.Array.
int len = nums.Length;
参照型は継承を完全にサポートします。 クラスを作成するときに、 シールとして定義されていない他のインターフェイスまたはクラスから継承できます。 他のクラスは、あなたのクラスから継承し、あなたの仮想メソッドをオーバーライドできます。 独自のクラスを作成する方法の詳細については、「 クラス、構造体、およびレコード」を参照してください。 継承と仮想メソッドの詳細については、「 継承」を参照してください。
リテラル値の型
C# では、コンパイラはリテラル値に型を割り当てます。 数値の末尾に文字を追加することで、数値リテラルの入力方法を指定できます。 たとえば、 4.56 値を floatとして扱う必要があることを指定するには、数値の後に "f" または "F" を追加します: 4.56f。 文字を追加しない場合、コンパイラはリテラルの型を推論します。 文字サフィックスを使用して指定できる型の詳細については、「 整数数値型 と 浮動小数点数値型」を参照してください。
リテラルは型指定され、すべての型は最終的に System.Objectから派生するため、次のコードなどのコードを記述してコンパイルできます。
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
ジェネリック型
実際の型 (具象型) のプレースホルダーとして機能する 1 つ以上の型パラメーターを持つ型を宣言します。 クライアント コードは、型のインスタンスを作成するときに具象型を提供します。 これらの型は ジェネリック型と呼ばれます。 たとえば、.NET 型 System.Collections.Generic.List<T> には、規則によって T という名前の 1 つの型パラメーターがあります。 型のインスタンスを作成するときは、リストに含まれるオブジェクトの型 ( stringなど) を指定します。
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
型パラメーターを使用すると、各要素をオブジェクトに変換しなくても、同じクラスを再利用して任意の型の要素を保持 できます。 ジェネリック コレクション クラスは 厳密に型指定されたコレクション です。これは、コンパイラがコレクションの要素の特定の型を認識しているためです。たとえば、前の例で stringList オブジェクトに整数を追加しようとすると、コンパイル時にエラーが発生する可能性があるためです。 詳細については、「 ジェネリック」を参照してください。
タプルと匿名型
パブリック API を使用してこれらの値を格納または渡さない場合、関連する値の単純なセットの型を作成するのは不便な場合があります。 この目的のために タプル または 匿名型 を作成できます。 詳細については、「タプルと匿名型」を参照してください。
null 許容値型
通常の値型には、 nullの値を指定することはできません。 ただし、型の後にを追加することで?型を作成できます。 たとえば、int?はint型で、nullという値を持つことができます。 null 許容値型は、ジェネリック構造体型 System.Nullable<T>のインスタンスです。 Nullable 型は、数値が null になる可能性があるデータベースとの間でデータを渡す場合に特に便利です。 詳細については、「 Null 許容値型」を参照してください。
暗黙的な型宣言
var キーワードを使用して、ローカル変数 (クラス メンバーではない) を暗黙的に入力します。 変数はコンパイル時に型を受け取りますが、コンパイラは型を提供します。 詳細については、「 暗黙的に型指定されたローカル変数」を参照してください。
コンパイル時の型と実行時の型
変数には、コンパイル時と実行時の型が異なる場合があります。 コンパイル時の型は、ソース コード内の変数の宣言型または推論型です。 ランタイム型は、その変数によって参照されるインスタンスの型です。 多くの場合、これらの 2 つの型は、次の例のように同じです。
string message = "This is a string of characters";
次の 2 つの例に示すように、コンパイル時の型が異なる場合があります。
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
上記のどちらの例でも、実行時の種類は stringです。 コンパイル時の型は最初の行に object され、2 行目に IEnumerable<char> 。
変数に対して 2 つの型が異なる場合は、コンパイル時の型と実行時の型がいつ適用されるかを理解することが重要です。 コンパイル時の型は、コンパイラが実行するすべてのアクションを決定します。 これらのコンパイラ アクションには、メソッド呼び出しの解決、オーバーロードの解決、および使用可能な暗黙的キャストと明示的キャストが含まれます。 実行時の種類は、実行時に解決されるすべてのアクションを決定します。 これらの実行時アクションには、仮想メソッド呼び出しのディスパッチ、 is 式と switch 式の評価、その他の型テスト API が含まれます。 コードが型とどのように対話するかを理解するには、どのアクションがどの型に適用されるかを認識します。
関連セクション
詳細については、次の記事を参照してください。
C# 言語仕様
詳細については、C# 言語仕様のを参照してください。 言語仕様は、C# の構文と使用法の決定的なソースです。
.NET