About
Visual C#
統合開発環境ツール:Visual Studio 2008 Professional
データベース:SQL Server 2005 Developer Edition
OS:Windows7 RC

Objective-C
統合開発環境ツール:Xcode3.1.2
OS:Mac OS X v10.5.7 Leopard
  C#   ASP.NET   Link   Contact
         
new修飾子を使ったメソッドの置き換えとオーバーライド
 
  無料のExpress Editionはこちらから  
 
 

開発環境

統合開発環境:
Visual Studio 2008 Professional
使用OS:
Windows7 RC

 
 

  new演算子を使ったメソッドの置き換え

継承を使えば、親クラスが持つフィールドやメソッドを引き継ぐことができます。しかし、派生クラスでフィールドやメソッドを新たに宣言していくと、親クラスが持つメソッドを置き換えたい場合があります。以下のプログラムは、アドレスを管理する「Adress」クラスと、「Adress」クラスを継承した「SubAdress」クラスを実装した例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Adress
{
   protected string name; //名前を保持
   protected string tell; //電話番号を保持
   protected string mail; //メールアドレスを保持
   public void printInfo()
   {
      Console.WriteLine("------------アドレス情報------------");
      name = "Europaris";
      tell = "xxx-xxxx-xxxx";
      mail = "europaris@xxx.xx.xx";
      Console.WriteLine(
      String.Format("名前:{0}\n電話番号:{1}\nメールアドレス:{2}"
      , name, tell, mail));
   }
}
class SubAdress : Adress
{
   string company; //会社名を保持
   string position; //部署名を保持
}

会社関係の連絡先に関しては会社名と部署名を追加したいので、このプログラムでは「Adress」クラスを継承した「SubAdress」クラスを実装し、フィールドに「company」と「position」を追加しています。「Adress」クラスでは、登録されている情報をコンソール画面に出力する為の「printInfo」メソッドが実装されていますが、このメソッドは名前と電話番号、メールアドレスしか返さないよう既に実装されているので、このままでは「company」、「position」を表示できません。

ここで登場するのがnew修飾子によるメソッドの置き換えです。new演算子を使えば、同じ名前を使ったメソッドを実装でき、親クラスのメソッドよりも、派生クラスで宣言しているメソッドが優先されて呼び出されます。もちろん、new修飾子を使用しなくても違う名前で新たにメソッドを実装してしまえば必要のないことですが、メソッドの置き換えで重要なのは呼び出し方法を統一できる点です。同じことをするメソッドなのに、メソッド名が違うとプログラムが複雑化してしまいますし、メソッドを使用する際、継承関係を気にしなければならなくなります。

実装方法は簡単で、クラスの宣言にnew修飾子を追加するだけで使用できます。
1
2
3
4
修飾子 new 戻り値 メソッド名(引数1, 引数2, 引数3...)
{
   //置き換えるメソッドの実装
}

ひとつだけ注意点があります。new修飾子を使用して新しくメソッドを定義する場合、同じメソッド名、戻り値、引数を指定しなければなりません。これらのメソッドを構成する要素はシグネチャと呼ばれ、親クラスのメソッドとシグネチャが異なれば、それは違うメソッドとして認識されてしまいます。

以下のプログラムは、以上のことを踏まえて実行できる形にしたプログラムの例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
   class Program
   {
      static void Main(string[] args)
      {
         Adress adress1 = new Adress();
         SubAdress adress2 = new SubAdress();
         adress1.printInfo();
         adress2.printInfo();
         Console.ReadLine();
      }
   }
   class Adress
   {
      protected string name; //名前を保持
      protected string tell; //電話番号を保持
      protected string mail; //メールアドレスを保持
      public void printInfo()
      {
         Console.WriteLine("------------アドレス情報------------");
         name = "Europaris";
         tell = "xxx-xxxx-xxxx";
         mail = "europaris@xxx.xx.xx";
         Console.WriteLine(
            String.Format("名前:{0}\n電話番号:{1}\nメールアドレス:{2}"
            , name, tell, mail));
      }
   }
   class SubAdress : Adress
   {
      string company; //会社名を保持
      string position; //部署名を保持
      public new void printInfo()
      {
         Console.WriteLine("------------アドレス情報------------");
         name = "Europaris";
         tell = "xxx-xxxx-xxxx";
         mail = "europaris@xxx.xx.xx";
         company = "株式会社xxx";
         position = "開発部";
         Console.WriteLine(
            String.Format("名前:{0}\n電話番号:{1}\nメールアドレス:{2}\n会社名:{3}\n部署名:{4}"
            , name, tell, mail, company, position));
      }
   }
}

実行結果
1
2
3
4
5
6
7
8
9
10
------------アドレス情報------------
名前:Europaris
電話番号:xxx-xxxx-xxxx
メールアドレス:europaris@xxx.xx.xx
------------アドレス情報------------
名前:Europaris
電話番号:xxx-xxxx-xxxx
メールアドレス:europaris@xxx.xx.xx
会社名:株式会社xxx
部署名:開発部

この実行結果を見てみると、「Adress」クラスの「printInfo」メソッドと「SubAdress」クラスの「printInfo」メソッドでは、シグネチャは同じでも番うメソッドが呼ばれていることがわかります。サンプルコードの39行目でnew修飾子を使用し、親クラスである「Adress」クラスのメソッドを置き換えているためです。

12行目では「Adress」クラスのインスタンスを、13行目では「SubAdress」クラスのインスタンスを作成し、14行目、15行目でそれぞれのインスタンスが持つ「printInfo」メソッドを呼び出しています。

  アップキャストとダウンキャスト

しかし、呼び出し方法は同じでも、型が違えば何かと不自由です。例えば、先程のインスタンスをメソッドの戻り値としたい場合に「Adress」型、「SubAdress」型の両方でアドレスを管理していては、戻り値として指定できる型は1種類なので、どちらかのインスタンスは取得できないということになります。幸いなことに、継承関係があるクラスのインスタンス同士はお互いの型を変換する機能が提供されています。この一連の作業は型変換と呼ばれ、アップキャストとダウンキャストの二種類があります。

派生クラスの型を親クラスの型に変換するアップキャストは無条件に行えます。ただし、派生クラスで追加したメンバは一切反映されないので注意が必要です。以下にアップキャストの使用方法を示します。
1
2
3
4
5
6
//「SubClass」型のインスタンスを作成し、親クラスである「RootClass」型の変数にアップキャストする方法
SubClass sub = new SubClass();
RootClass root = sub;

//インスタンス生成時にアップキャストしてしまう方法
RootClass root = new SubClass();

前者の方法では、アップキャストされている変数「root」と、アップキャストされていない変数「sub」の両方を保持することになります。ただし、その参照が指し示す先は同じインスタンスです。始めからアップキャストが必要であることがわかっている場合、後者のように宣言時の段階でアップキャストしても構いません。

以下にアップキャストを実際に使用した簡単なプログラム例を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
   class Program
   {
      static void Main(string[] args)
      {
      RootClass root = new RootClass();
      SubClass sub = new SubClass();
      RootClass cast = sub; //ここでアップキャスト
      root.show();
      sub.show();
      cast.show();
      Console.ReadLine();
   }
}
   class RootClass
   {
      public void show()
      {
         Console.WriteLine("親クラスのメソッドです");
      }
}
   class SubClass : RootClass //RootClassの派生クラスを宣言
   {
      public new void show()
      {
         Console.WriteLine("派生クラスのメソッドです");
      }
   }
}

実行結果
1
2
3
親クラスのメソッドです
派生クラスのメソッドです
親クラスのメソッドです

このプログラムでは、12行目で「RootClass」型の変数「root」の宣言とインスタンス化を行い、13行目で「SubClass」型の変数「sub」の宣言とインスタンス化をしています。次の行で「RootClass」型の変数「cast」を宣言し、SubClass型のインスタンス「sub」をRootClass型にアップキャストしています。また、32行目では「RootClass」にある「show」メソッドをnew修飾子を使用して置き換えています。

実行結果の2行目を見てみると、「RootClass」の「show」メソッドが置き換えられているのがわかります。注目すべきは3行目で、置き換えられたはずの「show」メソッドが置き換えられていません。これは、アップキャストした場合、派生クラスで追加されたメンバは全て無視されてしまうためです。new修飾子で置き換えたメソッドも例外ではありません。

アップキャストされたを元に戻す場合はダウンキャストを使用します。
1
2
3
//アップキャストしたインスタンス「upCast」を明示的にダウンキャストする
RootClass upCast = new SubClass();
SubClass downCast = (SubClass)upCast;

ダウンキャストする場合、アップキャストと構文が少し違います。3行目を見ると「(SubClass)」という記述がありますが、ダウンキャストでは、もともとそのインスタンスがどの型で生成されたものかを明示的に示さなければなりません。また、ダウンキャストはアップキャストされたものしかダウンキャストできません。そのため、「RootClass」型として生成されたインスタンスは「SubClass」型にダウンキャストできない点に注意してください。

以下のプログラムは、ダウンキャストを実際に使用したプログラムの例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
   class Program
   {
      static void Main(string[] args)
      {
         RootClass upCast = new SubClass(); //アップキャスト
         SubClass downCast = (SubClass)upCast; //ダウンキャスト
         upCast.show();
         downCast.show();
         Console.ReadLine();
      }
}
   class RootClass
   {
      public void show()
      {
         Console.WriteLine("親クラスのメソッドです");
      }
}
   class SubClass : RootClass
   {
      public new void show()
      {
         Console.WriteLine("派生クラスのメソッドです");
      }
   }
}

実行結果
1
2
親クラスのメソッドです
派生クラスのメソッドです

12行目では、「SubClass」型のインスタンスを生成し、「RootClass」型の変数「upCast」に代入しています。次の行では、変数「upCast」に代入されているインスタンスをダウンキャストして「SubClass」型の変数「downCast」に代入しました。

実行結果を見てみると、アップキャストされている時は親クラスのメソッドが呼び出されるのに対し、ダウンキャストされたときは派生クラスで実装したメンバがちゃんと反映されているのがわかります。

さて、ここでひとつの問題が残ります。メソッドの戻り値を統一するためにアップキャストし、戻り値として得たインスタンスを元の型にダウンキャストしたいとします。しかし、そのインスタンスがアップキャストされているかどうかがわからなければダウンキャストのしようがありません。無理やりダウンキャストしようとすれば、アップキャストされたインスタンスしかダウンキャストは行えないので、プログラム実行中にエラーが発生してしまいます。

そこでC#では、そのインスタンスがダウンキャストできるかどうかを調べるためのis演算子が用意されています。is演算子は以下のように使用します。
1
変数名 is 型

変数名にはダウンキャストしたい変数を、型には実際にダウンキャストしたい型を指定します。is演算子を使用した場合、戻り値としてbool型の値が返されるので、以下のようにifステートメントと組み合わせることで変換できるかどうかを厳密にチェックできます。
1
2
3
4
if (upCast is SubClass)
{
   SubClass downCast = (SubClass)upCast;
}

この例では、変数「upCast」が「SubClass型」に変換できる場合、if関数に「true」が返され、3行目でダウンキャストを行います。ダウンキャストを行う場合、プログラムの安全性を保つためにもこのようなチェックは必ず行うよう癖をつけておきましょう。

  メソッドのオーバーライド

new修飾子を使用して親クラスのメソッドを派生クラスで置き換えた場合、インスタンスをアップキャストすると置き換えたメソッドは実行されませんでした。C#では、より徹底的にメソッドを置き換えたいときのためにメソッドのオーバーライド機能が用意されています。

メソッドのオーバーライドでは、置き換えられる可能性のあるメソッドにvirtual修飾子を指定する必要があります。
1
2
3
4
修飾子 virtual 戻り値 メソッド名(引数1, 引数2, 引数3...)
{
   //メソッドの実装
}

このように、virtual修飾子が指定され、置き換えられることを予め想定して実装されているメソッドを仮想メソッドと呼びます。メソッドを置き換える派生クラス側では、virtualの代わりにoverride修飾子を指定します。
1
2
3
4
修飾子 virtual 戻り値 メソッド名(引数1, 引数2, 引数3...)
{
   //メソッドの実装
}

new修飾子を指定したメソッドと同じように、置き換える側と置き換えられる側のシグネチャは同じでなければなりません。以下のプログラムは、親クラスで実装されている仮想メソッドをオーバーライドした例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
   class Program
   {
      static void Main(string[] args)
      {
         RootClass root = new RootClass();
         SubClass sub = new SubClass();
         RootClass cast = sub;
         root.show();
         sub.show();
         cast.show();
         Console.ReadLine();
      }
   }
   class RootClass
   {
      public virtual void show()
      {
         Console.WriteLine("親クラスのメソッドです");
      }
   }
   class SubClass : RootClass
   {
      public override void show()
      {
         Console.WriteLine("派生クラスのメソッドです");
      }
   }
}

実行結果
1
2
3
親クラスのメソッドです
派生クラスのメソッドです
派生クラスのメソッドです

このプログラムでは、12行目で「RootClass」型のインスタンスを、13行目で「RootClass」の派生クラスである「SubClass」型のインスタンスを作成しています。14行目では、「SubClass」型のインスタンスである「sub」をアップキャストし、変数「cast」に代入しています。

実行結果の3行目では、new修飾子では置き換えることのできなかったアップキャスト時のメソッドを、メソッドのオーバーライドにより強制的に置き換えられていることがわかります。

また、baseキーワードを使用することで、派生クラスから親クラスのメンバにアクセスすることもできます。
1
2
base.メンバ名; //構文
base.show(); //「SubClass」から親クラスの「show」メソッドにアクセスする例

メソッドのオーバーライドはとても強力な機能ですが、親クラスにおいて仮想メソッドとして実装されていることが前提条件となります。基本的にアップキャストを意識してメソッドを置き換えることは少ないので、ほとんどの場合はnew修飾子を使用してメソッドを置き換えます。
   
Search:

Count:
inserted by FC2 system