C Sharpのデータ型

参照型と値型

C#の任意の型は (後述するポインタ型を除いて) 参照型と値型に二分される。
参照型の変数が、実際のオブジェクトを格納する領域の参照のみを保持するのに対して、
値型の変数は、実際のオブジェクトの値全てを保持する。
この特徴により、値の代入や引数の受け渡しの挙動が大きく異なって見える。

配列型

配列型は、初期化時に指定した型と長さを持つ参照型である。
ジャグ配列 (配列の配列) のほかに、真の多次元配列がサポートされる。

匿名型

匿名型は一時的に使用される型を簡単に定義するためのクラス型。読み取り専用プロパティのみを持つ。
特定の型名を持たないが、varキーワードで宣言したローカル変数に格納することができる。

dynamic型

dynamic型は、動的型付け変数を表す型である。
コンパイル時の型チェックをバイパスし、実行時に演算が解決される。

ポインタ型

ポインタ型はC++ライクなポインタ操作を可能とする型である。
通常の共通型システムの型とは異なり、共通言語ランタイムでは検査されない。
unsafe キーワードによって指定された範囲でしか利用できないようになっており、
コンパイル時には /unsafe オプションを指定する必要がある。

ジェネリクス

C#はジェネリックプログラミングに対応する。
クラス、構造体、インターフェイス、デリゲート、メソッドに対して、型引数を適用することができる。
型引数はout/inパラメータと共に指定することで、共変性、反変性を持つ。
/// <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型

Exception型および派生型のインスタンスは、
throw可能であり、
try-catch構文で例外処理に対応する。

Attribute型

Attribute型の派生型は、
コードの要素に対して属性を付けることができる。
例えばSerializableAttributeであれば、クラス宣言の手前で[Serializable]を記述することで、そのクラスが直列化可能であることを宣言できる。

IEnumerable<T>, IEnumerator<T>

IEnumerable<T>,IEnumerator<T>インターフェイスを戻り値とするメソッドやgetアクセサは、
yieldキーワードによるコルーチンに対応し、ジェネレータを簡潔に記述できる。

IDisposable

IDisposableインターフェイスを実装した型は、
usingステートメントによるリソースの安全な破棄に対応する。

Nullable<T>

型名の後ろに?を付けることで、Nullable<T>型の糖衣構文となる。
また、値型であるにも関わらずnullを代入することができる。
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型のままでは条件式では使用できない//if (b1) { DoSomething(); }if (b1 ?? false) { DoSomething(); }if (b1 == true) { DoSomething(); }

Task, Task<T>, ValueTask<T>

async/awaitキーワードによる、非同期プログラミングに対応する。

ValueTuple<>

2個以上の要素を持つValueTupleについては、例えば(string s, int n)と書くことでValueTuple<string, int>型の糖衣構文となる。
また、例えば("answer", 42)と書くことで、リテラルを表記できる。

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>

stackallocで割り当てたメモリの代入が可能である。
// C# 7.2から可能Span<int> span1 = stackalloc int[4];// C# 7.3から初期化子にも対応Span<int> span2 = stackalloc[] { 10, 20, 30, 40 };

特別扱いされるメソッド

C#において、ダック・タイピング的な扱いを受ける例を下に示す。
特定のメソッドが定義されていれば、クラス間の継承関係に関わらずC#の文法の恩恵を受けることができる。

Add()

IEnumerableが実装されAdd()メソッドが定義された型では、コレクション初期化の構文を利用することができる。
/// <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()

GetEnumerator()メソッドが定義された型では、foreach構文を利用することができる。
/// <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()

Deconstruct()メソッドが定義された型では、分解構文を利用することができる。拡張メソッドによる定義でも構わない。
/// <summary>Deconstructメソッドを実装する型</summary>class Person {
    public string Name { get; set; }
    public int Age { get; set; }
    /// <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()

GetAwaiter()メソッドが定義された型では、await演算子を利用することができる。拡張メソッドによる定義でも構わない。

GetPinnableReference()

GetPinnableReference()メソッドが定義された型では、fixedステートメント内でのポインタ指定時に構文糖の恩恵を得る。