++C++; // 未確認飛行 C 避けて通れない「非同期処理」を克服しよう

Top総合 目次C# によるプログラミング入門

クラス

このエントリーをはてなブックマークに追加

目次

キーワード

概要

クラスとはオブジェクトを作るための設計図のようなもので、 オブジェクト指向プログラミングの中心となるものです。

ポイント
  • クラス: オブジェクトを作るための設計図。
  • インスタンス: 設計図を基に作られた個々の実体。
  • 例えば、
    • class Point { public int X; public int Y; } でクラスを作って、
    • Point p = new Point(); でインスタンスを作る。
  • 構造体との違いは「値型」か「参照型」か、継承できるかどうか。

クラスとインスタンス

オブジェクト指向とは」 で述べたように、 操作の対象となるものをオブジェクトといいます。 オブジェクトを作る場合、まず設計図が必要になります。 内部がどういう構造になっているのか、外部からどのような操作をすることが出来るのかを決めてやるわけです。 このようなオブジェクトの設計図のことをクラス(class)といいます。 それに対し、設計図を元に作られたオブジェクトの実体のことをインスタンス(instance)といいます。

表1: クラスとインスタンスの比喩的例

クラスインスタンス
製品規格個々の製品
人間松井君、空知君、畑君、田辺さん・・・
実数全体 R 実数値 x, y, ・・・
初等関数 sin, cos, exp, log, ・・・

クラス定義

C#では以下のようにしてクラスを定義します。

class クラス名
{
  クラスの実装
}

クラスの実装にはメンバ変数の定義とメソッド(メンバー関数)の定義などをします。 メンバー変数とはクラスの内部で宣言される変数のことで、 メソッドの定義はクラスの内部で宣言される関数のことだと思ってもらって結構です。 以下に例を示します。

class Sample
{
  // メンバー変数の定義 ここから↓
  private int x;
  private int y;
  // メンバー変数の定義 ここまで↑

  // メソッドの定義 ここから↓
  public int GetX()
  {
    return x;
  }

  public int GetY()
  {
    return y;
  }

  public void Set(int a, int b)
  {
    x = a;
    y = b;
  }
  // メソッドの定義 ここまで↑
}

privatepublicといったキーワードについては 「実装の隠蔽」 で解説します。

メンバー変数によってオブジェクトの内部の実装を記述し、メソッドによって外部から行える操作を記述するわけです。 以下、具体的な例を挙げるために複素数を表すクラスを作ってみましょう。 まず、複素数に対する操作には何があるかを列挙してみましょう。

  • 実部の取り出し・変更
  • 虚部の取り出し・変更
  • 絶対値の取り出し・変更
  • 偏角の取り出し・変更
  • 四則演算
  • 共役複素数の計算

次に、複素数を実装する方法を考えて見ましょう。

  • 実部と虚部を記憶しておく
  • 絶対値と偏角を記憶しておく

いきなりすべてを実装するのは大変ですから、 まず、実部と虚部の取り出し・変更と、絶対値の取り出しを、 実部と虚部を記憶しておく方式で実装してみます。

class Complex
{
  public double re; // 実部を記憶しておく(外部からの読み出し・書き換えも可能)
  public double im; // 虚部を記憶しておく(外部からの読み出し・書き換えも可能)

  // 絶対値を取り出す
  public double Abs()
  {
    return Math.Sqrt(re*re + im*im);// Math.Sqrt は平方根を求める関数
  }
}

最初ということで、シンプルになるように実装しましたが、今後、徐々にちゃんとした形のものにしていきます。

クラスの利用

クラスを利用するためには、 インスタンスを作成しなければなりません。 そのためにまず、インスタンスを格納するための変数を定義します。 変数定義の仕方は以下のような構文になります。

クラス名 変数名;

次に、new キーワードでインスタンスを作成し、用意した変数に格納します。

ここで注意すべきことは、C# において、変数というのはただの入れ物であって、 変数を宣言しただけではインスタンスは作成されません。 (空っぽの入れ物だけができる。) 以下のように、new して始めてインスタンスが生成されます。

変数 = new クラス名();

また、インスタンスが生成されておらず、空っぽの状態を表すために、null(ヌル: 空っぽ、0)という特殊な値があります。 変数だけを用意する(インスタンスは後で生成する)場合には、null 値を変数に格納しておきます。

変数 = null;

そして、以下のように変数の後に「 . 」で区切ってメンバー名を書くことでメンバー変数やメンバー関数を利用できます。

変数名.メンバー名

例として先ほど作成した複素数クラスのインスタンスを生成し、利用してみましょう。

Complex z;         // インスタンスを格納するための変数を定義
z = new Complex(); // new を使ってインスタンスを生成

z.re = 3;             // 実部の値を変更
z.im = 4;             // 虚部の値を変更
double abs = z.Abs(); // z の絶対値を取得

Console.Write("abs = {0}\n", abs); // abs = 5 と表示される

また、組込み型や配列と同様に変数の宣言と同時にインスタンスを作成して初期化することも出来ます。

int n = 5;
string s = "abcde";
int[] array = new int[]{1, 2, 3, 4, 5};
Complex z = new Complex();

サンプル

using System;

/// <summary>
/// 複素数クラス
/// </summary>
class Complex
{
  public double re; // 実部
  public double im; // 虚部

  /// <summary>
  /// 絶対値を返す
  /// </summary>
  public double Abs()
  {
    return Math.Sqrt(re*re + im*im);
  }

  /// <summary>
  /// 文字列化する
  /// </summary>
  public override string ToString()
  {
    if(im >0)
      return re.ToString() + "+i" + im.ToString();
    if(im < 0)
      return re.ToString() + "-i" + (-im).ToString();
    return re.ToString();
  }
}// class Complex

//================================================
class ClassSample
{
  static void Main()
  {
    Complex z = new Complex();

    z.re = GetDouble("実部を入力してください : ");
    z.im = GetDouble("虚部を入力してください : ");

    Console.Write("|{0}| = {1}\n", z, z.Abs());
  }

  // 「関数」のところで作った実数入力用関数
  static double GetDouble(string message)
  {
    double x;
    while(true)
    {
      try
      {
        // 入力を促すメッセージを表示して、値を入力してもらう
        Console.Write(message);
        x = double.Parse(Console.ReadLine());
      }
      catch(Exception)
      {
        // 不正な入力が行われた場合の処理
        Console.Write(
          "error : 正しい値が入力されませんでした\n入力しなおしてください\n");
        continue;
      }
      break;
    }
    return x;
  }
}

クラスと構造体

ここまでの説明を見て、 クラス構造体の類似性に気付いた方もいるかと思います。 実際、メンバー変数やメソッドの定義は構造体でもできます。

クラスと構造体の違いを説明するためには、 継承や多態性などのオブジェクト指向の概念や、 値型と参照型というプログラミングの概念の理解が必要になります。 これらの概念の詳細は、 「継承」 、 「多態性」 、 「値型と参照型」 などで説明することにして、 ここでは簡単に概要だけを表にまとめます。

表2: クラスと構造体

クラス構造体
型の分類参照型値型
継承できるできない
多態性使える使えない

迷うようならクラスにしておけばいいと思います。 構造体は、以下の条件がそろっている場合にのみ使います。

  • データのサイズが小さい(目安としては16バイト程度以下)
  • 絶対に継承しないと分かっている
  • 変数への代入がコピーを生むというのが許容できる

クラスの分割定義

Ver. 2.0

C# 1.1 以前は、クラスの定義は全て1つのファイルに収める必要がありました。 まあ、1つのクラスの中身が、複数のファイルに散在するとソースファイルの可読性が低くなるので、 通常は、むしろ、クラス定義を複数のファイルに分割できない方がいいのですが、 時折、クラス定義を分割したい場面があります。

例えば、Visual Studio などの統合開発環境を利用していると分かると思いますが、 ソースファイルにも、 統合開発環境が自動的に生成してくれる部分と、 自分の手で書く部分とがあります。 この、自動生成部分と手書きの部分は、別ファイルに分かれている方が都合が良かったりします。 たとえば、プログラマのミスで開発支援ツールのコード自動生成機能に不具合が生じたり、 ツールのバグでプログラマのコードが破壊されたりといった問題が生じる可能性を極力減らすことにつながります。

そこで、C# 2.0 では、クラス定義時に partial というキーワードを付けることで、 クラス定義を複数に分割することができるようになりました。 (パーシャルクラス) 例えば、(少々無理やりな例ですが)上述の Complex クラスを2つに分割してみましょう。

partial class Complex
{
  public double re;
  public double im;
}

partial class Complex
{
  public double Abs()
  {
    return Math.Sqrt(re * re + im * im);
  }
}

この2分割された Complex クラスは、別々のファイル中にあっても構いません。 ただし、どの分割された部分にも、必ず partial というキーワードをつける必要があります。

class Complex // partial の付いていないクラスに、
{
  public double re;
  public double im;
}

partial class Complex // 別途、partial を追加することはできない。エラーになる。
{
  public double Abs()
  {
    return Math.Sqrt(re * re + im * im);
  }
}

すなわち、元々 partial を想定せずに作ったクラスに、 別ファイルに後付けでコードを加えることは出来ません。

メソッドの実装の分離

Ver. 3.0

C# 3.0 で パーシャルメソッド(partial method)という機能も追加されました。

どういうものかというと、 パーシャルクラス内限定で、 メソッドに partial を付けることでメソッドの宣言と定義を分けれるというものです。

定義の仕方と、制限事項は以下の通り。

  • partial 修飾子を付けてメソッドを宣言する。
  • 必ずパーシャルクラス内になければならない。
  • private でなければならない。
  • 戻り値は void 以外不可。
  • 引数は自由に取れる。ref, this, params も利用可能。ただし、out 引数は不可。
  • クラスメソッド(static)でもインスタンスメソッド(非 static)でも OK。

例えば、まずクラスの部分定義で以下のようなコードを書いたとします。

partial class Program
{
  static void Main(string[] args)
  {
    OnBeginProgram();

    Console.Write("program body\n");

    OnEndProgram();
  }

  static partial void OnBeginProgram();
  static partial void OnEndProgram();
}

この状態でプログラムをコンパイル → 実行すると、「program body」の文字だけが表示されます。

ここで特筆すべきは、 OnBeginProgram、OnEndProgram のメソッド呼び出しはコンパイル後の実行ファイルからはきれいさっぱり消えます。 (メタデータすら残さず、完全に消える。 [Conditional] 属性でも似たようなことができますが、こちらは少なくともメタデータは残ります。)

ここで、以下のような部分定義を追加して、 パーシャルメソッドに実装を与えます。

partial class Program
{
  static partial void OnBeginProgram()
  {
    Console.Write("check pre-condition\n");
  }

  static partial void OnEndProgram()
  {
    Console.Write("check post-condition\n");
  }
}

すると、OnBeginProgram、OnEndProgram が呼ばれるようになります。 実行結果は以下の通り。

check pre-condition
program body
check post-condition

利用場面としては、 宣言側は人手で書いて、 定義側はツールで自動生成というようなものを想定しているようです。

実は、これと似たようなことは仮想メソッドイベントとクラスの継承を使っても可能です。 ただ、多少煩雑な処理が必要ですし、 実行パフォーマンスは悪くなります。 その点、パーシャルメソッドなら、 必要な場合は普通のメソッド呼び出しになるだけですし、 不要な場合は完全削除されるわけで、 パフォーマンスに関しては非常に優れています。

ただ、結構制約も多いですし、利用場面は限られています。 それに実は、妙な副作用もあります。 例えば、以下のコードの実行結果はどうなるでしょう。

partial class Program
{
  static void Main(string[] args)
  {
    int x = 1;
    A(x = 2);
    Console.Write("{0}\n", x);
  }

  static partial void A(int x);
}

これ、A の実装がある場合には 2 に、ない場合には 1 になります。 パーシャルクラスなので、当然、実装は別ファイルにあってもかまいません。 自分以外の誰かがどこか別のところで実装を書くかもしれませんし、 誰も書かないかもしれません。 要するに、自分の知らないところで実行結果が変えられてしまう可能性がある。

まあ、メソッド呼び出しの () 内で代入なんてするなよって話ではあるんですが。 こういう副作用があることも覚えておいてください。 (あるいは、この副作用を積極的に利用したトリッキーなコードも書けるでしょうが・・・。 個人的には非推奨。)

あくまで、人手での記述とツールでの自動生成の混在開発で使うものだと思います。 元々、パーシャルクラス自体がそういう用途のための機能ですし。

匿名型

Ver. 3.0

C# 3.0 では匿名型(anonymous type)を作成できるようになりました。 匿名型の作り方は以下の通りです。

var x = new { FamilyName = "糸色", FirstName="望"};

このようなコードから、自動的に、以下のような型が生成されます。

// ↓この __Anonymous という名前はプログラマが参照できるわけではない。
class __Anonymous1
{
  private string f1;
  private string f2;
  
  public __Anonymous1(string f1, string f2)
  {
    this.f1 = f1;
    this.f2 = f2;
  }

  public string FamilyName
  {
    get { return this.f1}
  };
  public string FirstName
  {
    get { return this.f2}
  };
  
  // あと、Equals, GetHashCode, ToString も実装
}

この機能的はLINQとともに利用することで真価を発揮します。 単体で使う場面はそれほど多くないと思いますが、 例えば、以下のような書き方ができます。

var rectangle = new { Width = 2, Height = 3 };

Console.Write("幅  : {0}\n高さ: {1}\n面積: {2}\n",
  rectangle.Width,
  rectangle.Height,
  rectangle.Width * rectangle.Height);

演習問題

問題 1

データの構造化」 のデータの構造化問題 1で作成した Triangle 構造体をクラスで作り直せ。 (Point 構造体は構造体のままで OK。)

注1:現時点では、 単に struct が class に変わるだけで、特にメリットはありませんが、 今後、 「継承」 」や 「多態性」 を通して、 クラスのメリットを徐々に加えていく予定です。

注2: クラスにした場合、メンバー変数をきちんと初期化してやらないと正しく動作しません。 (構造体でもメンバー変数の初期化はきちんとする方がいいんですが。) 初期化に関しては、次節の 「コンストラクターとデストラクター」 で説明します。

解答

[お問い合わせ](q)