none
メソッドをオーバーライドするときの基底クラスのメソッドを呼ぶタイミング RRS feed

  • 質問

  • 或るクラスを継承したクラスを作成し、その基底クラスのメソッドをオーバーライドするときに、
    メソッドによっては基底クラスのメソッドも呼んでやらなければいけないときがあると思いますが、
    その場合、一般に処理のどのタイミングで呼ぶものなのでしょうか?

    例えば、 System.Windows.Forms.TextBox を継承し、Refresh() や OnEnter() などをオーバーライドすると
            Public Overrides Sub Refresh()
                MyBase.Refresh()
            End Sub
            Protected Overrides Sub OnEnter(ByVal e As System.EventArgs)
                MyBase.OnEnter(e)
            End Sub
    なんて表示されると思いますが、これに処理を書き加えるときには MyBase.~() はメソッド中の
    1.必ず先頭に書く
    2.必ず最後に書く
    3.基底クラスでの動作を理解し、ケースバイケース(処理の途中で記述する場合もある?)
    のどれが正しいのでしょうか?

    2006年11月27日 6:41

すべての返信

  • (3) です。

     

    2006年11月27日 8:06
  • レス、ありがとうございます。
     渋木宏明 さんからの引用

    (3) です。

    (3)・・・です・・・か・・・。
    たぶんそういう答えだろうなとは思いながら、でもひょとしたら 1.or 2. かも、と思いながら
    質問してみましたが、最も聞きたくなかった答えでした・・・orz
    オーバーライドするときは逆アセンブルなどで基底クラスのソースを追っかけて、
    中の処理を理解しないと危ないわけですね。
    また、敷居が高くなった。orz
    2006年11月27日 8:27
  • 言うまでもなく(3)ですが、オーバーライド可能な場合はドキュメントに処理概要が書いてあるはずですし、自分が実装する場合も書くべきです。
    それを判断材料にしていつ呼び出すのか、そもそも呼び出すのかを決定します。

    .NET Framework のクラスライブラリは逆アセンブルしてはいけない、となっていたはずです。

    2006年11月27日 8:39
  •  囚人 さんからの引用

    言うまでもなく(3)ですが、オーバーライド可能な場合はドキュメントに処理概要が書いてあるはずですし、自分が実装する場合も書くべきです。
    それを判断材料にしていつ呼び出すのか、そもそも呼び出すのかを決定します。

    え!ドキュメントだけで推測するのですか?
    例えば、TextBox.OnEnter(Control.OnEnter)メソッドの継承時の説明では
    継承時の注意 派生クラスで OnEnter をオーバーライドする場合は、登録されているデリゲートが
    イベントを受け取ることができるように、基本クラスの OnEnter メソッドを呼び出してください。 」
    と1文あるだけです。これだけでは呼び出すタイミングを推測するのはちょっと難しい気がしますが・・・。
    自分なりの解釈で、OnEnterはEnterイベントを発生させ、その先にはデリゲートによってユーザーのプロシジャーが
    実行されることになっているのでたぶんそれよりも先に自分の処理を終わらせたほうが安全なのかな?と
    漠然とそういう風に理解していて、この場合だとMyBase.OnEnter(e)は最後に記述するようにしていますが(合っているのかな?)
    すべてこんな感じで予測しないといけないってことですか?


     囚人 さんからの引用

    .NET Framework のクラスライブラリは逆アセンブルしてはいけない、となっていたはずです。

    こちらもまた、え!そうなんですか?なんですが、
    てっきりこんなもの(http://www.atmarkit.co.jp/fdotnet/tools/dotfuscator/dotfuscator_02.html)
    もあるので.NET Framework のクラスライブラリは見て(参考にして、って言っても私の力ではなかなか
    理解はできませんが)いいものだと思っていました。

    2006年11月27日 9:29
  • そうです。

    OnEnter の場合、登録されているデリゲートを先に呼び出したいとあなたが設計したいなら先に呼び出せばよいし、後に呼び出したいとあなたが設計したいなら後に呼び出せばよいのです。

    ドキュメントで指定されるのは、先に呼ぶのか後に呼ぶかの指定ではなく、あくまで処理概要です。それを指針に先に呼ぶのか後で呼ぶのか途中で呼ぶのかはあなたが決めます。それがオーバーライドです。

    極端な話、OnEnter も登録されたデリゲートを呼び出す必要がないとあなたが思うなら、基本クラスの OnEnter を呼ぶ必要もないでしょう。

    2006年11月27日 12:53
  •  コリン星人 さんからの引用

    オーバーライドするときは逆アセンブルなどで基底クラスのソースを追っかけて、
    中の処理を理解しないと危ないわけですね。

    そこまでする必要があるとは思えません。

    「基底クラスのメソッドをいつ呼び出すべきか」を決定するのは、基底クラスの設計ではありません。
    「オーバーライドメソッドで何をするのか」で決まるはずです。

     

     

     

    2006年11月27日 15:11
  •  囚人 さんからの引用

    ドキュメントで指定されるのは、先に呼ぶのか後に呼ぶかの指定ではなく、あくまで処理概要です。それを指針に先に呼ぶのか後で呼ぶのか途中で呼ぶのかはあなたが決めます。それがオーバーライドです。

     渋木宏明 さんからの引用

    「基底クラスのメソッドをいつ呼び出すべきか」を決定するのは、基底クラスの設計ではありません。
    「オーバーライドメソッドで何をするのか」で決まるはずです。

    これらの頂いたアドバイスから自分なりに解釈したのですが、ドキュメントに特別な条件等が
    書かれていない場合は、派生クラスにとってどちらがよいかということは派生クラスで設計するとして、
    「基底クラスから見て基底クラスのメソッドはいつ呼ばれても(派生クラスの処理への影響は別にしても)基底
    クラスの処理は
    最低限正しく動作する」と割り切って考えてもよいということでしょうか?
    ちょっと話は変わりますが、コンストラクタ内での基底クラスのコンストラクタの呼び出しは必ずコンストラクタの
    最初で行われます。たぶんこれはそうしなければいけない事情があるからだと思いますが、基底クラスの
    メソッドの呼び出しにはそういう制約が無いということは「いつ呼んでもかまわない」と解釈したのですが
    間違っていますか?

    2006年11月28日 0:41
  • オーバーライドメソッド内で、基底クラスのメソッドをどこで呼ぶかは、既に回答が付いていますが、オーバーライドメソッド内で何をやりたいのかによって変わります。一番最初で呼ぶ必要があれば一番最初で呼ぶでしょうし、一番最後で呼ぶ必要があれば一番最後で呼ぶでしょうし、どちらでもよければ、どちらかで呼ぶことになります。オーバーライドメソッド内で基底クラスのメソッドをどこで呼ぶかは、決まっているものではありません。オーバーライドメソッド内で、オーバーライドしていない基底クラスの他のメソッドを呼ぶこともできます(C#では可能ですが、VBでもできるかは確信がありません)

    たぶん、悩まれているのは、基底クラスのメソッドが何をしているのかはっきりわからないけど、とりあえず呼ばなければならないから呼んでいる状態だからでしょうか?
    でも、もし、そのような状態であれば、オーバーライドではなく、イベントなどの他の方法で解決できないでしょうか? 例えば、例に出されているOnEnterですが、Enterイベントで実装できないのでしょうか? この場合ですと、OnEnterが実行されて、Enterイベントが発動されるというわかりやすい流れになります。

    オーバーライドするということは、その基底クラスのメソッドが何をするのかを理解しており、その動きじゃ嫌だから、派生クラスで隠蔽して動作を変えるという流れになっているはずです。ですので、オーバーライドした段階で、基底クラスのメソッドがよくわからず、どの段階で呼んだらいいかというのはあまりないはずだと思うのですが・・・。Enterイベントではなく、OnEnterでされたということであれば、OnEnterの動作を変えたいという理由があると思うのです。

    2006年11月28日 2:28
    モデレータ
  •  trapemiya さんからの引用

    たぶん、悩まれているのは、基底クラスのメソッドが何をしているのかはっきりわからないけど、とりあえず呼ばなければならないから呼んでいる状態だからでしょうか?

    まさにそのような状態です。オーバーライド(したいタイミング)で処理を付加したいけど
    基底クラスのメソッドを呼ぶタイミングによっては、付加した処理が基底クラスに思わぬ
    影響を与えたり、または逆に基底クラスのメソッドの処理が派生クラスに思わぬ影響を
    与えたりしないかが心配なのです。

     trapemiya さんからの引用

    でも、もし、そのような状態であれば、オーバーライドではなく、イベントなどの他の方法で解決できないでしょうか?

    オーバーライドするということは、その基底クラスのメソッドが何をするのかを理解しており、その動きじゃ嫌だから、派生クラスで隠蔽して動作を変えるという流れになっているはずです。ですので、オーバーライドした段階で、基底クラスのメソッドがよくわからず、どの段階で呼んだらいいかというのはあまりないはずだと思うのですが・・・。


    オブジェクト指向の1つの大きな特徴である、クラスの継承を積極的にチャレンジ(勉強)しようとしていましたが、
    かなりハードルが高そうですね。

     trapemiya さんからの引用

    Enterイベントではなく、OnEnterでされたということであれば、OnEnterの動作を変えたいという理由があると思うのです。

    大抵の場合は「動作を変えたいというよりも、そのタイミングで処理を付加した共通部品を作成したい。」という
    場合が大半なのです。

    2006年11月28日 2:54
  • オーバーライド時の基底メソッド呼出し順序の問題は『Essential.NET』でも論じられているので,一度読んでみるといいかもしれません.

    順序や網羅性が重要な,フローの再利用や拡張については,Windows Workflow Foundation (WF) である程度綺麗にカバーできるのではないかと思います.
    一方で,.NET のメソッドオーバーライドやイベントの使用を前提としたクラスライブラリは,拡張時の順序や網羅性をそこまで強く制御できないという印象があります.

    2006年11月28日 3:41

  • 「基底クラスから見て基底クラスのメソッドはいつ呼ばれても(派生クラスの処理への影響は別にしても)基底
    クラスの処理は最低限正しく動作する」と割り切って考えてもよいということでしょうか?

    基底クラスのメソッドがいつ呼ばれても正しく動くかどうかは派生クラスの設計者の責任です。基底クラスの設計者のやれる事はせいぜいドキュメントに明記して推奨させる事ぐらいでしょうね。
    私個人としては、そういう事を強制できる事が組み込まれている言語があっても良いと思います(私が知らないだけで既に存在するかもしれませんが)。


    ちょっと話は変わりますが、コンストラクタ内での基底クラスのコンストラクタの呼び出しは必ずコンストラクタの
    最初で行われます。

    コンストラクタの呼び出し順は明確に決まっていて、最も祖のクラスのコンストラクタから順に呼ばれていきます。つまり、.NET ではどのようなクラスであれ Object クラスから順に初期化されていくという事です。
    そういう理由から、基底クラスのコンストラクタを明示的に指定したいときは、派生クラスのコンストラクタの最初に呼び出さなければなりません。

    話は逸れますが、コンストラクタはそういう実装になっているため、コンストラクタ内で仮想メソッドを呼び出すと意図しない動きになる可能性がある、という事も知っておいて損はないと思います。
    http://msdn2.microsoft.com/ja-jp/library/ms182331(vs.80).aspx

    C++ ではその辺は常識だったのですが、.NET では意図通りに動いているように見えるから厄介です。派生クラスのコンストラクタが呼ばれる前(いろいろ初期化される前)に派生クラスの仮想メソッドが呼ばれます。
    http://blogs.wankuma.com/shuujin/archive/2006/04/18/22541.aspx

    2006年11月28日 3:45
  •  NyaRuRu さんからの引用

    オーバーライド時の基底メソッド呼出し順序の問題は『Essential.NET』でも論じられているので,一度読んでみるといいかもしれません.

    サブタイトルが「共通言語ランタイムの本質」・・・う~ん、何やら難しそうな本ですが、よ、読んでみたい、と思います。

     NyaRuRu さんからの引用

    順序や網羅性が重要な,フローの再利用や拡張については,Windows Workflow Foundation (WF) である程度綺麗にカバーできるのではないかと思います.

    .NET3.0に期待、というところですか。私の力で現時点でそれをやることは無理だと思いますので、もう少し一般に情報が出てきたらやってみようと思います。

     囚人 さんからの引用

    コンストラクタの呼び出し順は明確に決まっていて、最も祖のクラスのコンストラクタから順に呼ばれていきます。つまり、.NET ではどのようなクラスであれ Object クラスから順に初期化されていくという事です。

    流れとしては、基底クラスのインスタンス化->派生クラスのインスタンス化->基底クラスのコンストラクタの実行->派生クラスのコンストラクタの実行、となるということですね。

    2006年11月28日 5:00

  • 流れとしては、基底クラスのインスタンス化->派生クラスのインスタンス化->基底クラスのコンストラクタの実行->派生クラスのコンストラクタの実行、となるということですね。

    いえ、「コンストラクタの実行 = インスタンス化」と考えていれば大筋は間違いではないでしょう。

    2006年11月28日 6:33
  •  囚人 さんからの引用

    いえ、「コンストラクタの実行 = インスタンス化」と考えていれば大筋は間違いではないでしょう。

    わかりました。
    2006年11月28日 7:38