ここではC#のオーバーライドについて学びます。
オーバーライド(override)
オーバーライドとは基底クラスで定義したメソッド(プロパティも可能)を派生クラスで書き換えることを言います。
その名前の通りで上書きです。メソッドの機能を上書きして、違う機能に差し替えるような感じです。
オーバーライドする側(派生クラス)の記述は難しくなく、書き換える時にoverrideってキーワードを付けます。
抽象クラスの学習で少し触りましたが、以下みたいなのが簡単なサンプルコードになります。
namespace Sample
{
public abstract class Base
{
public abstract void Attack();
}
public class Derived : Base
{
public override void Attack()
{
System.Console.WriteLine("殴る");
}
}
internal class Program
{
static void Main(string[] args)
{
var d = new Derived();
d.Attack();
}
}
}
このオーバーライドですが、何でも自由に書き換えできる訳ではありません。
絶対条件として、書き換え元と書き換え先でアクセス修飾子や引数、戻り値など、基本的に全てが同じである必要があります。
これは当然なんですが、そうしないと呼び出しが不可能になるからです。この辺はポリモーフィズムの話を思い出してください。
ここではC#の抽象クラスについて学びます。これはクラスの継承に関する話の延長になります。理解するためには継承の仕組みの理解が必須なので、忘れてしまった人は以下で復習しましょう。抽象クラス(abstract class)抽象クラスとは、抽象的で不完全なクラスを意味します。全く分からないよ。僕も同じ気持ちです。よくある日本語に翻訳しないほうが良くねって感じの言葉だね。簡単に言ってしまうとインタンス化できないクラスです。別の表現をするならインタンス化する...
つまり、基底クラスにも派生クラスにも同じ形(引数とか戻り値)のメソッドやプロパティが誕生します。
そしてオーバーライドされたメソッドがポリモーフィズムの仕組みに従って呼び出されます。
派生クラスを継承した派生クラスで、オーバーライド済みメソッドをオーバーライドすることも可能です。
オーバーライドを許可する記述
この表現が妥当か微妙なんですが、オーバーライドされる側(基底クラス)にも特殊な記述が必要です。
そのキーワードは2つあり、1つは学習済みのabstractキーワード、もう1つはvirtualキーワードです。
abstractキーワード
これは既に学習済みなので問題ないでしょう。抽象クラスで利用する記述です。
abstractを記述したメソッド(プロパティ)は実装がないため、派生クラスで必然的にオーバーライドする必要があります。
public abstract class Base
{
// 抽象クラスだから実装がない
public abstract void Attack();
}
virtualキーワード
これは実装済みメソッドのオーバーライドを許可する場合に使います。
例を見たほうが早いので、サンプルコードで動作を確認してみましょう。
namespace Sample
{
public class Base
{
// オーバーライドを許可する
public virtual void Attack()
{
System.Console.WriteLine("殴る");
}
}
public class Derived : Base
{
// オーバーライドする
public override void Attack()
{
System.Console.WriteLine("強く殴る");
}
}
internal class Program
{
static void Main(string[] args)
{
var b = new Base();
var d = new Derived();
b.Attack();
d.Attack();
}
}
}
このように基底クラスが抽象クラスとは限らないため、実装が存在する場合があります。
その時に派生クラスでオーバーライドを許可する場合の表現がvirtualキーワードです。
また、virtualが付いたオーバーライド可能なメソッドを仮想メソッドと呼びます。
virtualがない場合はオーバーライドしちゃ駄目なんですか?
駄目というか普通にはできない。この後に話す方法でオーバーライドする必要があるよ。
newキーワード
基底クラスでabstractやvirtualが付いてないメソッドをオーバーライドするとビルドエラーになります。
つまり、こういう記述は不可能です。以下みたいなコードはビルドエラーになるので認められません。
public class Base
{
// virtualを付けない
public void Attack()
{
}
}
public class Derived : Base
{
// overrideする
public override void Attack()
{
}
}
では、これはどうか。
public class Base
{
// virtualを付けない
public void Attack()
{
}
}
public class Derived : Base
{
// overrideを書かない
public void Attack()
{
}
}
実はこれはビルドが通ります。ただし、エラーの代わりに警告が出ます。
この警告に対する考え方は人それぞれですが、エラーと違ってプログラムは実行できます。
実行できるなら警告は無視していい?
警告による。内容を読んで理解できるなら無視してもいいかなって感じ。
最近はコードの記述が冗長だよ的な警告(もしくはメッセージ)も出るので何とも言えませんが、一般的に警告は無視しないほうが安全です。
と言うか、無視していいなら警告なんて出ないと思いませんか? それが表示されるのは読めってことです。
本題に戻り、オーバーライド時に警告を出さない正しい記述は以下となります。
namespace Sample
{
public class Base
{
// virtualを付けない
public void Attack()
{
System.Console.WriteLine("殴る");
}
}
public class Derived : Base
{
// overrideの代わりにnewを付ける
public new void Attack()
{
System.Console.WriteLine("強く殴る");
}
}
internal class Program
{
static void Main(string[] args)
{
var b = new Base();
var d = new Derived();
b.Attack();
d.Attack();
}
}
}
こちらオーバーライドと同時に説明されることが多いのですが、実際にはオーバーライドではありません。
newと記述した通り、基底クラスのメソッドを隠して、同名のメソッドを派生クラスに新規作成してます。
つまり、先程までのオーバーライドはメソッドの上書きですが、これはメソッドの新規追加です。
さらに注意もあって、ポリモーフィズムが使えなくなります。
確認のため、以下のサンプルコードを実行してみましょう。
namespace Sample
{
public class Base
{
public void Attack()
{
System.Console.WriteLine("殴る");
}
}
public class Derived : Base
{
public new void Attack()
{
System.Console.WriteLine("強く殴る");
}
}
internal class Program
{
static void Main(string[] args)
{
// 基底クラスの型で派生クラスをインスタンス化する
Base d = new Derived();
d.Attack();
}
}
}
ポリモーフィズムが機能すれば、実インスタンスの型で処理されるために派生クラスのメソッドが呼ばれます。
がっ、結果は見ての通りで基底クラスが呼ばれました。これはnewした場合は参照してる型で呼ばれるからです。
正直、自分で基底クラスから設計した場合にnewが必要になったら、それは設計が悪いです。
だって、この機能ってポリモーフィズムが機能しない代わりに得られるものがないもん。
毎度、どこで使うの感が強いのですが、自分が基底クラスを改造できない場合は普通に使います。
ぶっちゃけ、その場合はメソッド名を変えるのも選択肢ですが、なんか同じ名前にしたい場合があります。
baseキーワード
派生クラス内でメソッドを呼ぶと、自分自身(this参照)が所有するメソッドが呼ばれます。
仮にオーバーライドしてる場合、自分自身=オーバーライド済みメソッドとなり、書き換えた後のメソッドが呼ばれます。
その中で基底クラスのメソッドを呼びたい場合があるのですが、これはbaseキーワードを使うことで解決できます。
これをサンプルコードで確認してみましょう。baseで参照してる部分がポイント、thisと同じ感覚です。
namespace Sample
{
public class Base
{
public virtual void Attack()
{
System.Console.WriteLine("殴る");
}
}
public class Derived : Base
{
public override void Attack()
{
// 基底クラスのメソッドを呼ぶ
base.Attack();
}
}
internal class Program
{
static void Main(string[] args)
{
var d = new Derived();
d.Attack();
}
}
}
初期化系のメソッドをオーバーライドした場合に、base参照で基底クラスの処理を行いつつ、後に自分の処理も行うってパターンがあります。
具体的には以下のようなコードになり、このように記述すると派生クラスに無駄な基底クラスの処理が入らず、見た目が美しいコードになります。
namespace Sample
{
public class Base
{
public virtual void Initialization()
{
System.Console.WriteLine("基底クラスの初期化");
}
}
public class Derived : Base
{
public override void Initialization()
{
base.Initialization();
System.Console.WriteLine("派生クラスの初期化");
}
}
internal class Program
{
static void Main(string[] args)
{
var d = new Derived();
d.Initialization();
}
}
}
あとがき
全部virtual付けたら万能じゃん。天才!って思った人。そんなことしたら席ごと爆破されますよ。
後、オーバーライドで全然関係の無い機能にも改造できますが、単なる迷惑コードなので駄目です。
◆ C#に関する学習コンテンツ
この記事は参考になりましたか?
コメント