Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 

C# 早わかりリファレンス

― 4/6ページ: メソッド/プロパティ/イベント/インデクサー/演算子オーバーロード/コンストラクターとデストラクター ―

2019年12月13日

メソッド

メソッドの宣言アクセス修飾子

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my.Run("text");
    MyClass.InternalRun();
  }
}

public class MyClass
{

  static internal void InternalRun()
  {
    Console.WriteLine("static internal Run");
  }

  public void Run(string text)
  {
    Console.WriteLine(text);
  }

  protected void ProtectedRun()
  {
    Console.WriteLine("ProtectedRun");
  }

  private void PrivateRun()
  {
    Console.WriteLine("InternaRun");
  }
}
リスト7-1 メソッドの宣言とアクセス修飾子

 クラスにはメソッドを含めることができる。メソッドはパラメーター(引数)リストを宣言できる。メソッドのアクセス修飾子は入れ子になった型と同じく、publicinternalprotectedprotected internalprivateを指定できる。またstaticなメソッドは静的メソッドと呼ばれ、その型のインスタンスではなく、クラス型に関連してメソッドを呼び出せる。

refパラメーターoutパラメーター

C#
class Program
{
  static void Main(string[] args)
  {
    var i = 1;
    var j = 2;
    Swap(ref i, ref j);
    // i=2
    // j=1
    var k = 0;
    int l;
    // 初期化済みの値、未初期化の値両方利用できる
    Out(out k, out l);
    // k=1
    // l=2
  }

  static void Swap(ref int x, ref int y)
  {
    var temp = x;
    x = y;
    y = temp;
  }

  static void Out(out int x, out int y)
  {
    x = 1;
    y = 2;
  }
}
リスト7-2 refパラメーターとoutパラメーター

 修飾子のないメソッドの引数は値パラメーターであり、メソッド内でパラメーターの値を代入してもメソッドを呼び出す側の変数には影響を与えない。

 しかし、refキーワードを指定して参照パラメーターとして宣言すると、メソッドを呼び出す側とメソッド内で同じ変数を参照することになる。つまりメソッド内でパラメーターを代入し直すと、その変更が呼び出し側の変数にも影響する。

 outキーワードは出力パラメーターとして宣言するときに使われ、参照パラメーターと同じような効果がある。出力パラメーターは複数の値を返す場合に利用され、メソッドを呼び出す時点で変数を初期化しておく必要がない点とメソッド内で必ず値を代入しないといけない点が異なる。

パラメーター配列

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var array = new[] { 1, 2, 3 };
    // 3個の引数が指定されました
    Param(array);
    // 2個の引数が指定されました
    Param(1, 2);
    // 0個の引数が指定されました
    Param();
  }

  static void Param(params int[] array)
  {
    Console.WriteLine($"{array.Length}個の引数が指定されました");
  }
}
リスト7-3 パラメーター配列

 paramsキーワードを指定することでパラメーター配列を定義し、可変長な引数を扱うことができる。パラメーター配列には、配列そのものの他、配列の要素型の値を,で区切ったパラメーターリストを指定できる。メソッド内ではパラメーター配列は配列として扱うことができ、パラメーター配列を指定せずに呼び出した場合は長さ0の配列が代入されている。

省略可能なパラメーター

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    // x=1 y=a
    Test(1);
    // x=2 y=b
    Test(2, "b");
  }

  static void Test(int x, string y = "a")
  {
    Console.WriteLine($"x={x} y={y}");
  }
}
リスト7-4 省略可能なパラメーター

 メソッドのパラメーターは、デフォルト値を指定することで省略可能なパラメーターにできる。デフォルト値は、コンパイル時に決定できる定数でなければならない。また、省略可能なパラメーターの後に、省略できない必須のパラメーターを宣言することはできない。

拡張メソッド

C#
class Program
{
  static void Main(string[] args)
  {
    var s = "abcd";
    var text = "CD";
    // true
    var flg = s.ContainsIgnoreCase(text);
  }
}
public static class MyExtensions
{
  public static bool ContainsIgnoreCase(this string s, string text)
  {
    return s.ToLower().Contains(text.ToLower());
  }
}
リスト7-5 拡張メソッド

 拡張メソッドを定義することにより、クラス定義を変更することなく、あたかもそのクラスにインスタンスメソッドを追加したかのように、メソッドを呼び出すことができる。静的クラスに宣言した静的メソッドの最初のパラメーターにthisキーワードを付けることで定義する。拡張メソッドを定義した静的クラスが別の名前空間に属する場合は、その名前空間をusingすることで利用可能になる。LINQ(Language INtegrated Query)のメソッド形式の多くは、拡張メソッドにより定義されており、using System.Linqにより参照可能になる。

プロパティ

プロパティの宣言

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my.Value1 = 3;
  }
}

public class MyClass
{
  private int value1;
  public int Value1
  {
    get
    {
      return value1;
    }
    set
    {
      if (value != value1)
      {
        value1 = value;
        ValueChanged();
      }
    }
  }

  private void ValueChanged()
  {
    Console.WriteLine("ValueChanged");
  }
}
リスト8-1 プロパティ

 プロパティは、クラスの構成要素の1つでフィールドを拡張したものだ。フィールドのようにアクセスできるが、gettersetterとして処理を定義できる。setter内では、プロパティに代入された値をvalueキーワードで参照できる。

自動実装プロパティ

C#
class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my.UpdateValue1();
    my.Value2 = 12;
  }
}
public class MyClass
{
  public int Value1 { get; private set; }

  public int Value2 { get; set; } = 2;  // 自動実装プロパティの初期化

  public int Value3 { get; }

  public MyClass()
  {
    // getter onlyプロパティはコンストラクターでも代入可能
    Value3 = 3;
  }

  public void UpdateValue1()
  {
    // privateなsetterなのでインスタンス内から変更可能
    // Value3は更新できない
    Value1 = 1;
  }
}
リスト8-2 自動実装プロパティ

 自動実装プロパティは、単純にフィールドの値を読み書きするだけのプロパティの記述を簡単に記述できるようにしたものである。C# 6.0で自動実装プロパティの初期化がフィールドと同じような形式でできるようになったのに加え、getterのみの自動実装プロパティが記述できるようになったため、変更不可能なプロパティが記述しやすくなった。C# 6.0より前から定義できたprivateなsetterを持つ自動実装プロパティは、同じクラスの別のメソッドなどから変更可能であることに注意してほしい。

イベント

イベントの定義と呼び出し

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    new Program().Execute();
  }

  void Execute()
  {
    // EventHandlerのインスタンスを追加する、もしくは直接追加可能。
    // 意味合いとしては同じだが、別のインスタンスとして追加される
    Changed += new EventHandler(OnChanged);
    Changed += OnChanged;
    // EventHandlerに1つも購読のハンドラーが登録されていないとnullであるため、
    // null条件演算子経由でイベントを発火させている
    Changed?.Invoke(this, new EventArgs());
    // イベントの購読を解除できる
    Changed -= OnChanged;
    Changed?.Invoke(this, new EventArgs());

    ValueChanged += OnValueChanged;
    ValueChanged.Invoke(this, new MyEventArgs(3));
  }

  void OnChanged(object sender, EventArgs ea)
  {
    Console.WriteLine("OnChanged");
  }

  void OnValueChanged(object sender, MyEventArgs ea)
  {
    Console.WriteLine($"OnValueChanged {ea.Value}");
  }

  event EventHandler Changed;
  event EventHandler<MyEventArgs> ValueChanged;

  class MyEventArgs : EventArgs
  {
    public int Value { get; }
    public MyEventArgs(int value)
    {
      Value = value;
    }
  }

  // event構文はaddとremove処理を明示的に記述できる。
  // 省略した場合のコードは意味合い的には下記のコードと同様だが、
  // 実際にはスレッドセーフなコードが生成されている
  private EventHandler customHandler;
  event EventHandler CustomHandler
  {
    add
    {
      customHandler += value;
    }
    remove
    {
      customHandler -= value;
    }
  }

  // 自前で定義したdelegateをイベントの型として利用することもできる
  public delegate void MyEventHandler();
  public event MyEventHandler MyChanged;
}
リスト9-1 イベント

 イベントは、オブジェクトが通知を発行・購読できるようにした仕組みである。通知を発行したい側はeventキーワードによりイベントを公開する。通常、EventHandlerデリゲート(delegate)もしくはそのジェネリクス型のEventHandler<TEventArgs>デリゲート(いずれもSystem名前空間)を利用するが、自前で定義したdelegate型を利用することもできる。イベント引数の型はEventArgsを継承して自作することが一般的だ。

 購読する側はdelegateと同じ型のメソッドを登録する。後述するラムダ式を利用することもできる。購読は+=演算子、解除は-=演算子で行うが、購読したインスタンスと同一のインスタンスを指定しない限り解除はできない。また、通知を送信する側が購読側を解除する仕組みはない。

 通知する側はイベントハンドラーを呼び出すことでイベントを通知できる。イベントハンドラーにイベントが登録されていないとnull呼び出しで実行時エラーになるため、null条件演算子を利用するなどしてnullチェックを行う必要がある。

 また、イベントハンドラーはイベントを追加・削除する時の処理を明示的に記述できる。しかしスレッドセーフに実装するべき箇所であるため、明示的に記述する際はその点を考慮するのがいいだろう。

インデクサー

インデクサーの定義

C#
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my[1] = 100;
    var v1 = my[1];
    var v2 = my["test"];
  }
}


public class MyClass
{
  private int[] array = new int[100];
  private Dictionary<string, int> dict = new Dictionary<string, int>();

  // int型のインデクサー
  public int this[int i]
  {
    get
    {
      return array[i];
    }
    set
    {
      array[i] = value;
    }
  }

  // getterのみの場合は、式形式で記述可能
  public int this[string s] => dict.ContainsKey(s) ? dict[s] : 0;
}
リスト10-1 インデクサー

 インデクサーは、配列アクセスのようにインデックスを使用してオブジェクト内部のコレクション要素などにアクセスするための仕組みである。インデックスにはint型や文字列型を使うことが多いが、それ以外の型も利用可能であり、複数の型の組み合わせを指定することもできる。プロパティ同様、getterとsetterを記述するが、getterのみの場合は、式形式の記述をすることもできる。

演算子オーバーロード

演算子オーバーロードの定義

C#
class Program
{
  static void Main(string[] args)
  {
    var c1 = new Complex(1, 3) + new Complex(2, 4); // 3 + 7i

    var c2 = -new Complex(2, -2); // -2 + 2i
  }
}

public class Complex
{
  public double Real { get; }
  public double Imaginary { get; }

  public Complex(double real, double imaginary)
  {
    Real = real;
    Imaginary = imaginary;
  }

  public static Complex operator -(Complex c1)
  {
    return new Complex(-c1.Real, -c1.Imaginary);
  }

  public static Complex operator +(Complex c1, Complex c2)
  {
    return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
  }
}
リスト11-1 演算子オーバーロード

 ユーザー定義型に対して、+などの演算子をオーバーロードにより定義できる。しかし、任意の型に対して演算子を定義すると複雑になりやすいため、複素数やベクトルといった限られた用途の型に対して定義することになるだろう。サンプルコードでは単項演算子-と二項演算子+のみをオーバーロードしている。

コンストラクターとデストラクター

インスタンス・コンストラクター

C#
class Program
{
  static void Main(string[] args)
  {
    var my1 = new MyClass();
    // 0
    var v1 = my1.Value;

    var my2 = new MyClass(2);
    // 2
    var v2 = my2.Value;
  }
}

public class MyClass
{
  public int Value { get; set; }

  // 引数なしのインスタンス・コンストラクター
  public MyClass() : this(0)
  { }

  // 2つ目のコンストラクター。引数あり
  public MyClass(int v)
  {
    Value = v;
  }

}
リスト12-1 インスタンス・コンストラクター

 ユーザー定義型をインスタンス化する処理としてインスタンス・コンストラクターを定義できる。インスタンス・コンストラクターは何も定義しない場合、引数なしのコンストラクターを利用できる。

 インスタンス内に複数のコンストラクターがある場合、循環参照にならない限り、別のコンストラクターをthisキーワードで呼び出すことができる。

静的コンストラクター

C#
class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    // 5
    var v = my.Value;
  }
}

public class MyClass
{
  // 静的なメンバー
  static int DefaultValue;

  // 静的コンストラクター
  static MyClass()
  {
    // 代入するだけであればフィールドの初期化子として書けるが
    // より複雑な初期化も可能
    DefaultValue = 5;
  }

  public int Value { get; set; } = DefaultValue;
}
リスト12-2 静的コンストラクター

 ユーザー定義型の静的なメンバーを初期化する際に、静的コンストラクターを利用できる。静的コンストラクター内では静的なメンバーの初期値を代入できるため、インラインでは記述ができないような初期値の計算に利用できる。

 なお、静的コンストラクターはクラスがインスタンスされる時もしくは静的メンバーが参照される時に1回だけ実行される。

デストラクター

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my = null;
    GC.Collect();
    GC.WaitForPendingFinalizers(); // Destruct
  }
}

public class MyClass
{
  // デストラクター
  ~MyClass()
  {
    Console.WriteLine("Destruct");
  }
}
リスト12-3 デストラクター

 デストラクターはインスタンスが破棄される時に呼び出される処理を記述できる仕組みである。正確にはオブジェクトがGC(ガベージ・コレクション)により回収され、ファイナライザーを呼び出す際に実行されるため、実行されるタイミングを制御するのは困難だ。そのため、リソースを解放するような処理が必要な場合は、後述するusing構文を利用するためにIDisposableインターフェースを実装する方がいいだろう。

サイトからのお知らせ

Twitterでつぶやこう!