プログラミングと開発方法論と情報セキュリティ(主に初心者向け、言語はC#)、その他、ゲーム、思想等。

開発方法論萌え

C# 応用

C#応用編第3回 – 関数のクラス間移動と this 参照(this ポインタ)

投稿日:


前回は、初めてクラスについて学びました。

今回は、静的メソッド(クラスメソッド)とインスタンスメソッドの違いについて学びます。

前提

本記事は、次の2つの記事の続編となります。

まだ読んでいない方はまずは先にこちらをお読みください。

C#応用編第1回 – クラスの前提事項

C#応用編第2回 – いよいよクラス登場!

Add() 関数の新たな問題点

前回、Add() 関数を改良し、このようなプログラムを作成しました。


using System;

class Program
{
  static void Main(string[] args)
  {
    IntList hoge = new IntList();
    hoge.array = new int[0];
    Console.WriteLine("好きな数だけ数値を入力してください。");
    while (true)
    {
      string str = Console.ReadLine();
      if (str == "")
        break;

      Add(hoge, int.Parse(str));
    }

    int sum = 0;
    for (int i = 0; i < hoge.length; i++)
    {
      sum += hoge.array[i];
    }

    Console.WriteLine("合計は {0} になりました。", sum);
  }

  static void Add(IntList intList, int a)
  {
    if (intList.length < intList.array.Length)
    {
      // 物理的な要素数が足りている場合は新規の要素を追加するだけで良い。
      intList.array[intList.length] = a;
      intList.length++;
    }
    else
    {
      // 物理的な要素数が足りない場合は配列を拡張する。

      int[] newArray;
      if (intList.array.Length == 0)
      {
        // 配列の要素が 0 の場合は要素数が 1 の配列を作成する。
        newArray = new int[1];
      }
      else
      {
        // 既にある配列の倍の要素を持つ配列を作成する。
        newArray = new int[intList.array.Length * 2];
      }

      // 既にある要素を複製する。
      for (int i = 0; i < intList.array.Length; i++)
      {
        newArray[i] = intList.array[i];
      }

      // 新規の要素を追加する。
      newArray[intList.length] = a;
      intList.array = newArray;
      intList.length++;
    }
  }
}

class IntList
{
  public int[] array;
  public int length;
}

 

しかし、新たな問題点が浮上しました。

ここでは、その問題点の1つを解決していきます。

問題点とは、Add() 関数の中で array や length にアクセスする際に intList. を前に付けなければならなくなったのです。気にならない人もいるでしょうが、これは結構面倒です。

これを解決するには次のような流れを踏みます。

  1. Add() 関数を Program クラスから IntList クラスに移動する。
  2. Add() 関数を静的関数(クラス関数)からインスタンス関数に変更する。
  3. Main() 関数から Add() 関数を呼び出すときは IntList クラスの変数である hoge を経由して呼び出すようにする。
  4. Add() 関数で使用している intList. を this. に変更する。
  5. Add() 関数から引数 intList を取り除き、Main() 関数側でも intList を引き渡すのをやめる。
  6. Add() 関数で使用している this を取り除く。

以上で、非常にすっきりとしたプログラムになります。言い換えればオブジェクト指向らしいプログラムになります。

非常にややこしく感じるかもしれませんが、慣れてしまえば瞬殺でできるようになります。

それでは見ていきましょう。

手順1

Add() 関数を Program クラスから IntList クラスに移動する。

前提として、C#には関数はいずれかのクラスに所属していなければならないというルールがあります。

現在 Add() クラスは、Program クラスに所属していますが、これを IntList クラスに所属させます。

現在のプログラムは次のような形になっています。


using System;

class Program
{
  static void Main(string[] args)
  {
    // 省略

      Add(hoge, int.Parse(str));

    // 省略
  }

  static void Add(IntList intList, int a)
  {
    // 省略
  }
}

class IntList
{
  public int[] array;
  public int length;
}

 

このプログラムをこう変更します。


using System;

class Program
{
  static void Main(string[] args)
  {
    // 省略

      Add(hoge, int.Parse(str));

    // 省略
  }
}

class IntList
{
  public int[] array;
  public int length;

  static void Add(IntList intList, int a)
  {
    // 省略
  }
}

 

単純に、カット&ペーストすれば良いです。

これで Add() 関数を Program クラスから IntList クラスに移動できました。

しかし、このプログラムはエラーとなります。

というのは、原則として関数を呼び出すには、関数が所属しているクラス名と.(ドット)を先頭に付けなければならないからです。

これには例外があります。呼び出し元の関数と、呼び出し先の関数が同じクラスに所属しているときは、クラス名と.(ドット)を省略できるのです。

今までは Main() 関数と Add() 関数が同じ Program クラスに所属していたため、例外に当てはまっていました。

Add() 関数を移動したことで、この例外に当てはまらなくなったので、原則通りクラス名と.(ドット)を付ける必要があります。

Main() 関数を次のように変更します。


  static void Main(string[] args)
  {
    // 省略

      IntList.Add(hoge, int.Parse(str));

    // 省略
  }

 

この変更を加えてもまだエラーです。

実は、クラスに所属している関数を呼び出せるのは同じクラスに所属している関数だけだというのが原則です。

Main() 関数は Program クラスに所属し、Add() 関数は IntList に所属しましたから、所属するクラスが異なっています。

これを解決するには、Add() 関数に public アクセス指定子を付けて、異なるクラスの関数からのアクセスを明示的に許可してあげる必要があります。

この変更を加えるとこうなります。


  public static void Add(IntList intList, int a)
  {
    // 省略
  }

 

以上で、プログラムはこれまでとまったく同じように動作します。

手順2

Add() 関数を静的関数(クラス関数)からインスタンス関数に変更する。


  public static void Add(IntList intList, int a)
  {
    // 省略
  }

 

Add() 関数の先頭に static が付いています。

static が付いた関数のことを静的関数、クラス関数、静的メソッド、またはクラスメソッドと言います。

思い出していただきたいのは、クラスは設計図であって、同じクラスはひとつのプログラムに複数存在せず、唯一無二だと言うことです。

これに対して、インスタンスは設計図を基に作られた実体ですから、必要に応じていくつでも作り出すことができます。

静的関数は、インスタンスではなくクラスに所属しています。即ち、静的関数もプログラム上、唯一無二だと言うことです。

では、static を関数から外してしまうとどうなるでしょうか?

関数から static を取り除くと、その関数はインスタンス関数、またはインスタンスメソッドと呼ばれるようになります。

インスタンス関数は、クラスではなくインスタンスに所属します。

これは、インスタンスを作ったら作っただけ、インスタンスごとに独立したインスタンス関数ができ上がるということです。(厳密には違うのですが、今の段階ではそう考えてください。)

では、Add() から static を取り除いて、静的関数からインスタンス関数に変更してみましょう。

残念ながらこれだけではエラーになります。

そこで次の手順が必要です。

手順3

もう一度 Main() 関数を見てみましょう。


using System;

class Program
{
  static void Main(string[] args)
  {
    // 省略

      IntList.Add(hoge, int.Parse(str));

    // 省略
  }
}

赤字の部分に注目してください。

Add() 関数は IntList クラスを通して呼び出されています。

しかし Add() 関数はインスタンス関数になりました。

既に述べた通り、インスタンス関数はクラスではなくインスタンスに所属します。

そして、インスタンス関数は、インスタンスを作ったら作っただけ、インスタンスごとに独立したものが出来上がります。

ということは、インスタンス関数を呼び出す際には、その関数がどのインスタンスに所属しているかを特定しなければならないのです。

ここでは、変数 hoge の指す先にある IntList のインスタンスを操作しますから、hoge を特定してあげる必要があります。

以上から、次のようなコードになります。(赤字の部分に注目。)


using System;

class Program
{
  static void Main(string[] args)
  {
    IntList hoge = new IntList();
    hoge.array = new int[0];
    Console.WriteLine("好きな数だけ数値を入力してください。");
    while (true)
    {
      string str = Console.ReadLine();
      if (str == "")
        break;

      hoge.Add(hoge, int.Parse(str));
    }

    int sum = 0;
    for (int i = 0; i < hoge.length; i++)
    {
      sum += hoge.array[i];
    }

    Console.WriteLine("合計は {0} になりました。", sum);
  }
}

 

これで、プログラムはこれまでとまったく同じように動作するようになります。

手順4

Add() 関数で使用している intList. を this. に変更する。

静的関数とインスタンス関数の大きな違いとして this 参照(this ポインタ)が使えるかどうかと言うのがあります。

this 参照は性的関数では使うことができず、インスタンス関数でのみ使用できます。

この this 参照には何が入っているかと言えば、インスタンス関数を呼び出す際に特定したインスタンスのアドレス(座標)が入ります。

言い替えるとこう言うことです。

Main() 関数では、hoge.Add(...); として Add() 関数を呼び出していますが、.(ドット)の左側にある hoge が this になります。

(静的関数の中ではインスタンスが特定されていないので this が使えません。)

さらに次の部分に着目してください。

hoge.Add(hoge, int.Parse(str));

hoge を Add() 関数の引数に渡しています。この hoge は Add() 関数の仮引数 intList に代入されます。

ということは、this と intList には全く同じものが入っていることになります。

Add() 関数の中で使われている全ての intList を this に置き換えても同じように動くことになります。

置き換えるとこうなります。


using System;

class Program
{
  static void Main(string[] args)
  {
    // 省略
  }
}

class IntList
{
  public int[] array;
  public int length;

  public void Add(IntList intList, int a)
  {
    if (this.length < this.array.Length)
    {
      // 物理的な要素数が足りている場合は新規の要素を追加するだけで良い。
      this.array[this.length] = a;
      this.length++;
    }
    else
    {
      // 物理的な要素数が足りない場合は配列を拡張する。

      int[] newArray;
      if (this.array.Length == 0)
      {
        // 配列の要素が 0 の場合は要素数が 1 の配列を作成する。
        newArray = new int[1];
      }
      else
      {
        // 既にある配列の倍の要素を持つ配列を作成する。
        newArray = new int[this.array.Length * 2];
      }

      // 既にある要素を複製する。
      for (int i = 0; i < this.array.Length; i++)
      {
        newArray[i] = this.array[i];
      }

      // 新規の要素を追加する。
      newArray[this.length] = a;
      this.array = newArray;
      this.length++;
  }
}

 

この変更を加えても、プログラムはこれまでとまったく同じように動作します。

手順5

Add() 関数から引数 intList を取り除き、Main() 関数側でも intList を引き渡すのをやめる。

以上の操作で、Add() 関数の仮引数 intList は、使用されなくなりました。

なので、この引数を取り除いてしまいましょう。

取り除くとこうなります。


using System;

class Program
{
  static void Main(string[] args)
  {
    // 省略

      hoge.Add(hoge, int.Parse(str));

    // 省略
  }
}

class IntList
{
  public int[] array;
  public int length;

  public static void Add(IntList intList, int a)
  {
    // 省略
  }
}

 

手順6

Add() 関数で使用している this を取り除く。

実は、同じクラスにある変数や、関数にアクセスするのに使用する this. は省略することができるのです。

そこで、Add() 関数で使用している this. を全部取り除いてしまいましょう。

取り除くとこうなります。


using System;

class Program
{
  static void Main(string[] args)
  {
    IntList hoge = new IntList();
    hoge.array = new int[0];
    Console.WriteLine("好きな数だけ数値を入力してください。");
    while (true)
    {
      string str = Console.ReadLine();
      if (str == "")
        break;

      hoge.Add(int.Parse(str));
    }

    int sum = 0;
    for (int i = 0; i < hoge.length; i++)
    {
      sum += hoge.array[i];
    }

    Console.WriteLine("合計は {0} になりました。", sum);
  }
}

class IntList
{
  public int[] array;
  public int length;

  public void Add(int a)
  {
    if (length < array.Length)
    {
      // 物理的な要素数が足りている場合は新規の要素を追加するだけで良い。
      array[length] = a;
      length++;
    }
    else
    {
      // 物理的な要素数が足りない場合は配列を拡張する。

      int[] newArray;
      if (array.Length == 0)
      {
        // 配列の要素が 0 の場合は要素数が 1 の配列を作成する。
        newArray = new int[1];
      }
      else
      {
        // 既にある配列の倍の要素を持つ配列を作成する。
        newArray = new int[array.Length * 2];
      }

      // 既にある要素を複製する。
      for (int i = 0; i < array.Length; i++)
      {
        newArray[i] = array[i];
      }

      // 新規の要素を追加する。
      newArray[length] = a;
      array = newArray;
      length++;
    }
  }
}

 

完成!

以上で、オブジェクト指向らしい非常にすっきりしたプログラムになります。

変更前は Add() 関数の中で array や length にアクセスする際に intList. を前に付けなければならなかったのが綺麗に消滅しました。

更に、この変更には副次的なメリットがあります。

Main() 関数中の Add() 関数の呼び出しが次の通り変更されています。

変更前)

IntList.Add(hoge, int.Parse(str));

変更後)

hoge.Add(int.Parse(str));

変更前は Add() 関数が2つありました。hoge の差す先にあるインスタンスを操作するために Add() 関数を呼んでいるのですから、引数2つのうち、主役は hoge(IntList の仮引数 intList)です。しかし、変更前の書き方からは「hoge が主役だ!」と言う意図が読み取りづらいのです。

対して変更後は、hoge の差す先にあるインスタンスを操作している(「hoge が主役だ!」)と言う意図が読み取り易くなっています。

まだまだメリットがあります。

Add() 関数が IntList クラスに移動したことにより、IntList クラスを Add() もろとも別のプロジェクトに移動することが出来るようになりました。

IntList クラスを共通ライブラリ(.dll)に移動して、複数の実行ファイル(.exe)から使い回せるということです。

今回は以上です。

次回以降残された問題について解説します。


管理人が学習に利用した書籍

管理人が学習に利用した書籍を紹介させていただきます。

私がお勧めしたいのは独習C#第3版です。独習C#新版と言うのもありますが、第3版とは異なる著者が書いた本です。

Amazon さん

楽天さん

 

-C#, 応用

Copyright© 開発方法論萌え , 2020 All Rights Reserved Powered by STINGER.