C# は厳密に型指定された言語です。 すべての変数と定数には、値に評価されるすべての式と同様に、型があります。 すべてのメソッド宣言は、各入力パラメーターと戻り値の名前、型と種類 (値、参照、または出力) を指定します。 .NET クラス ライブラリは、さまざまなコンストラクトを表す組み込みの数値型と複合型を定義します。 これには、ファイル システム、ネットワーク接続、オブジェクトのコレクションと配列、および日付が含まれます。 一般的な 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
、interface
、enum
、および record
コンストラクトを使用します。 .NET クラス ライブラリ自体は、独自のアプリケーションで使用できるカスタム型のコレクションです。 既定では、クラス ライブラリで最もよく使用される型は、任意の C# プログラムで使用できます。 その他の参照は、それらを定義するアセンブリにプロジェクト参照を明示的に追加した場合にのみ使用できます。 コンパイラがアセンブリへの参照を持った後、そのアセンブリで宣言された型の変数 (および定数) をソース コードで宣言できます。 詳細については、「 .NET クラス ライブラリ」を参照してください。
型を定義するときに最初に行う決定の 1 つは、型に使用するコンストラクトを決定することです。 次の一覧は、その最初の決定に役立ちます。 選択肢には重複があります。 ほとんどのシナリオでは、複数のオプションが妥当な選択肢です。
- データ ストレージのサイズが小さく、64 バイト以下の場合は、
struct
またはrecord struct
を選択します。 - 型が変更できない場合、または非破壊的な変更が必要な場合は、
struct
またはrecord struct
を選択します。 - 型に等しい値セマンティクスが必要な場合は、
record class
またはrecord struct
を選択します。 - 型が主に動作ではなくデータの格納に使用される場合は、
record class
またはrecord struct
を選択します。 - 型が継承階層の一部である場合は、
record class
またはclass
を選択します。 - 型がポリモーフィズムを使用する場合は、
class
を選択します。 - 主な目的が動作の場合は、
class
を選択します。
共通型システム
.NET の型システムに関する 2 つの基本的なポイントを理解することが重要です。
- 継承の原則をサポートしています。 型は、基本型と呼ばれる他の 型から派生できます。 派生型は、基本型のメソッド、プロパティ、およびその他のメンバーを継承します (いくつかの制限があります)。 さらに、基本型は他の型から派生できます。その場合、派生型は継承階層内の両方の基本型のメンバーを継承します。
System.Int32 (C# キーワード:
int
) などの組み込みの数値型を含むすべての型は、最終的に 1 つの基本型 (System.Object (C# キーワード:object
) から派生します。 この統合型階層は、 共通型システム (CTS) と呼ばれます。 C# での継承の詳細については、「 継承」を参照してください。 - CTS の各型は、 値型 または 参照型として定義されます。 これらの型には、.NET クラス ライブラリ内のすべてのカスタム型と、独自のユーザー定義型が含まれます。
struct
キーワードを使用して定義する型は値型です。組み込みの数値型はすべてstructs
。class
キーワードまたはrecord
キーワードを使用して定義する型は、参照型です。 参照型と値型には、コンパイル時の規則が異なり、実行時の動作も異なります。
次の図は、CTS の値型と参照型の関係を示しています。
注
最も一般的に使用される型はすべて、 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
{
public int x, y;
public Coords(int p1, int p2)
{
x = p1;
y = p2;
}
}
構造体の詳細については、「 構造体の型」を参照してください。 値型の詳細については、「値型 」を参照してください。
値型の他のカテゴリは 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
、delegate
、配列、またはinterface
として定義されている型は、reference type
です。
reference type
の変数を宣言すると、その型のインスタンスで変数を割り当てるか、null
演算子を使用して変数を作成するまで、new
値が含まれます。 次の例では、クラスの作成と割り当てを示します。
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
interface
は、new
演算子を使用して直接インスタンス化することはできません。 代わりに、インターフェイスを実装するクラスのインスタンスを作成して割り当てます。 次の例を確認してください。
MyClass myClass = new MyClass();
// 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
オブジェクトに整数を追加しようとすると、コンパイル時にエラーが発生する可能性があるためです。 詳細については、「 ジェネリック」を参照してください。
暗黙の型、匿名型、および Null 許容値型
var
キーワードを使用して、ローカル変数を暗黙的に入力できます (クラス メンバーは入力できません)。 変数はコンパイル時に型を受け取りますが、型はコンパイラによって提供されます。 詳細については、「 暗黙的に型指定されたローカル変数」を参照してください。
メソッドの境界の外側に格納または渡す予定のない、関連する値の単純なセットの名前付き型を作成するのは不便な場合があります。 この目的で 匿名型 を作成できます。 詳細については、「 匿名型」を参照してください。
通常の値型には、 null
の値を指定することはできません。 ただし、型の後にを追加することで?
型を作成できます。 たとえば、int?
はint
型で、null
という値を持つことができます。 null 許容値型は、ジェネリック構造体型 System.Nullable<T>のインスタンスです。 Nullable 型は、数値が null になる可能性があるデータベースとの間でデータを渡す場合に特に便利です。 詳細については、「 Null 許容値型」を参照してください。
コンパイル時の型と実行時の型
変数には、コンパイル時と実行時の型が異なる場合があります。 コンパイル時の型は、ソース コード内の変数の宣言型または推論型です。 ランタイム型は、その変数によって参照されるインスタンスの型です。 多くの場合、これらの 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