none
C#の継承について教えてください RRS feed

  • 質問

  • C#の継承について教えてください。
    以下のようにクラスが定義してあります。

     //親クラス
     public class Parent
     {
       public string name;
       public Parent(string name)
       {
         this.name = name;
       }
     }
     //子クラス
     public class Child : Parent
     {
       public string address;
       public Parent(string name, string address) : base(name)
       {
         this.address = address;
       }
     }

    そして実行プログラムが以下です。変数addressが空白であればParentクラスを使い、
    空白じゃなければChildクラスを使うようになっています。
    インスタンスがどちらでもいいようにParentクラスの変数を宣言して、
    ParentクラスとChildクラスのインスタンスを入れるようにしています。

     //実行プログラム(前略)
     Parent myClass = null;
     if (address == "")
     {
       myClass = new Parent(name);
     }
     else
     {
       myClass = new Child(name, address);
     }
     string myAddress = myClass.address;//←myClassがChildの場合、addressを参照できない

    myClassにChildクラスのインスタンスが入れられた場合、上記にあるように
    myClass.addressが参照できないのですが、このような使い方は間違っているのでしょうか?

    また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?
    どうかよろしくお願いします。

    2010年9月8日 6:10

回答

  • 処理をどのクラスが実装すべきかはクラスの役割によって違ってきますが、(以下略)のところを Parent や Child に持たせてしまうかもしれません。
    丸投げが難しい場合には、if で分けているような部分だけ Parent や Child にやらせてしまえば良いと思います。
    もしくは Strategy パターンとか。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月8日 6:59
  • typeof と is を正しく使い分けるために例を挙げます。

    Parent myClass1 = new Parent(name);
    myClass1.GetType() == typeof(Parent) //TRUE
    myClass1.GetType() == typeof(Child) //FALSE
    myClass1 is Parent //TRUE
    myClass1 is Child //FALSE
    ------
    Parent myClass2 = new Child(name, address);
    myClass2.GetType() == typeof(Parent) //FALSE  ←インスタンスは Child であり Parent ではない。
    myClass2.GetType() == typeof(Child) //TRUE
    myClass2 is Parent //TRUE  ← Child は Parent にキャスト可能なので TRUE。
    myClass2 is Child //TRUE
    ------
    Child myClass3 = new Child(name, address);
    myClass3.GetType() == typeof(Parent) //FALSE
    myClass3.GetType() == typeof(Child) //TRUE
    myClass3 is Parent //TRUE  ← Child は Parent にキャスト可能なので TRUE。
    myClass3 is Child //TRUE

    ← Child は Parent にキャスト可能なので TRUE。』のところは
    Parent Child を Control TextBox に置き換えると解り易いです。

    TextBox is Control  //TRUE  ←テキストボックスはコントロールです。
    TextBox is TextBox  //TRUE

    のようにどちらも TRUE になります。

    以上、テストすればわかることなので言わずもがなでしょうが念のため。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 7:11
  • ただ、以下のコードのような場合ではnumWing(翼の数)の
    定義をVehicle親クラスにするということと同じになり、
    整合が合わなくなるかもと思ってます。

    「”飛行機”に”翼の数”があるのは良いが、”乗り物”に”翼の数”があることはおかしい」ということでしょうか。
    そうであれば、「”乗り物”と扱うと決めた変数に対して、”翼の数”があることを期待したコードを書くな」ということになるかと思います。

    もし親クラスの変数に子クラスのインスタンスが
    入れられたら、プログラマがそれを意図しているものとして
    自動的に変数が子クラスになるとかであればいいのだと思いますが・・。
    (これって問題ありますでしょうか? varがあるなら、こんなのもと思うのですが・・)

    子クラスの型の変数に代入して扱うべきでしょう。(var もコンパイル時に具体的な型名に置き換えられてます)
    親クラスの型の変数に代入して扱う場合、親クラスにあるものだけを使うべきです。(ダウンキャストする場合を除く)

    ”乗り物”に対して代入した場合、その変数に対する操作はどんな”乗り物”にも使える操作になります。
    その状況で、”飛行機”にしか許されていない、”翼の数”をそのまま参照できたら、どんな”乗り物”でも OK という条件が満たせなくなります。そんな処理は、”乗り物”に対して行うべきではないでしょう。
    また、そのメソッドをコンパイルする時点で、”飛行機”であるか、ほかの”乗り物”となるかは特定できない可能性があります。その場合に、自動的に”飛行機”になることは不可能です。

    さて、「以下略」がほとんど共通だとのことですが、アイデア次第でまとめられるはずです。
    ただ、その具体的なコードが見えない状況では納得できる形のコードを提示できないかもしれません。

    # dynamic キーワードがやりたいことに近いのかもしれませんが、コンパイル時にスペルミスなどもチェックされなくなるので今の段階ではおすすめしません。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 15:11
    モデレータ
  • 子クラスのインスタンスを親クラスの変数に入れて
    同じように使いたいということで質問したのですが
    そういう使い方はできないのだと分かりました。


    できない、と言い切ってしまうのは早計かもしれません。
    Parentクラスのオブジェクトとしてでしたら、両方を区別なく使うことができます。

    (ただし、Parentオブジェクトとして扱うので、Childクラス固有のメンバーであるaddressを使えません。)

    違う種類のオブジェクトを同じように扱うには、前提として両方に共通のメンバーが必要です。
    例えば、次のようになります。

    class Parent 
    {
     ...
     public virtual void Process()
     {
      //Parentを使う処理
     }
    }
    class Child : Parent
    {
     ...
     public override void Process()
     {
      //Childを使う処理
     }
    }
    
    

    そして、オブジェクトの生成処理を別メソッドにします。

    Parent CreateInstance(string name, string address)
    {
     if(address == "")
     {
      return new Parent(name);
     }
     else
     {
      return new Child(name, address);
     }
    } 
    
    

    すると、呼び出し側のコードを単純にできます。

    Parent p = CreateInstance(name, address);
    p.Process(); 
    

    addressについても同じように扱うには、addressを両方のクラスに含める(または、親クラスから継承させる)必要があります。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 15:20
  • なんかOOPの問題で出てきそうな話ですね。「図形」と「描画方法」の関係とかに似たような話です。
    個人的には上記のような状況だと、クラスを使う側が極力キャストしないで済むよう(というか・・・実際のクラスが何であるかを知らないで済むよう)クラス構成を考えます。

    以下は C# の話ではなく、考え方の話になります。あくまで参考までということで、興味なければ流してください。

    TH01さんが

    > 実際の内容次第ですけど、
    > 理想的には、私もすでに挙がっているデザインパターンの採用が一番だと思いました
    > (訂正:1つの選択肢ということで。。。)

    と言われてますが、私はパターンの採用を「一つの選択肢」と思いません。ある意味一番「理想」です。(^^;
    (こういう複雑な問題を、以前から存在する古典的な方法で解決するのがパターンです。とはいえパターン信者じゃありませんが)

    「パターン」というと、とかく難しそうな印象がありまして、一昔前はOOP の基礎を覚えた後にパターンを学習するというのが定石だったようですが、最近ではオブジェクト指向を習うと同時にパターンを修得した方が、初心者でも OOP の理解が深まるという話も出ているようです。詳しくは以下参照

    http://www.amazon.co.jp/dp/4894716844/

    脱線しそうなので元に戻りますが、上の問題の場合、実装中心に考えるのではなく、「乗り物」と「移動方法」の関係という抽象的概念から問題を解決する方法もあったりします。

    上のクラスの例だと、親クラスでタイヤの数を定義してますが、本当にそのメンバは必要なのか。
    親クラスでは「乗り物」を地上を移動すると想定してタイヤ数をメンバに持っているようですが、では「船」の場合どうするか?
    実装中心にものを考えるだけでなく、そのクラスは本当に親クラスと関係あるのか?そのメンバはクラスに本当に必要なのかと自問自答してみると、意外な解が見つかったりします。

     #抽象的な話ですみません。


    ひらぽん http://d.hatena.ne.jp/hilapon/
    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 16:25
    モデレータ

すべての返信

  • string myAddress = myClass.address;//←myClassがChildの場合、addressを参照できない

    Parent クラスに address というメンバーがないので、コンパイル エラーになって然るべきと思うのですが、いかがでしょうか。

    また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?

    実行時にどのクラスを使うか決まることによって、どのような動作をご希望なのでしょうか。
    2010年9月8日 6:37
  • >myClass.addressが参照できないのですが、このような使い方は間違っているのでしょうか?

    myClassの型は「Parent myClass = null;」で定義されているように「Parent」です。

    「Parent」には「address」はないので、参照できません。

     

    また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?

    以下のようにキャストすれば取得することができます。

     

    Parent myClass = null;
    if (address == "")
    {
    	myClass = new Parent(name);
    }
    else
    {
    	myClass = new Child(name, address);
    }
    
    if (myClass is Parent)
    {
    	MessageBox.Show(myClass.name);
    }
    if (myClass is Child)
    {
    	MessageBox.Show(((Child)myClass).address);
    } 
    

     

    ご提示されているコードのように、ParentであろうがChildであろうが、addressの値を取得する必要が

    あるのであれば、Parent自体にaddressをもたせた方がいいと思います。

    質問用に書いたコードと思われますので、上記の意味ではない場合は無視してください。

    Parentにaddressをもたせた場合は、以下のようにコンストラクタを複数定義すればよいと思います。

     

    //親クラス
    public class Parent
    {
    	public string name;
    	public string address;
    	public Parent(string name)
    	{
    		this.name = name;
    	}
    	public Parent(string name, string address)
    	{
    		this.name = name;
    		this.address = address;
    	}
    }
    
    

     


    2010年9月8日 6:44
  • ありがとうございます。

     いさぎよく以下のように書けばいいのかもしれませんが、
    ”以下略”の部分の処理が長く、また重複している処理もあるので、
    ParentとChildを同等に扱うことができるように、プログラムを
    書くことが出来る方法があればと思っています。

    if (address == "")
    {
     Parent p = new Parent(name);
     //(以下略)
    }
    else
    {
     Child c = new Child(name, address);
     //(以下略)
    }
    

    どうかお願いいたします。

    2010年9月8日 6:49
  • > string myAddress = myClass.address;//←myClassがChildの場合、addressを参照できない

    > myClassにChildクラスのインスタンスが入れられた場合、上記にあるように
    > myClass.addressが参照できないのですが、このような使い方は間違っているのでしょうか?

    これを実装したいならクラス Parent にフィールド address を移動するしかないと思いますが、何か出来ない事情があるのでしょうか。

    あと拡張メソッドという手もありますが、このケースの場合は止めといた方がよさそうですね。

    > また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?

    Template Method や Strategy/State パターン等、色々なパターンが思い浮かびますが。
    「デザインパターン」 で検索してみてください。いろいろヒットすると思います。

     


    ひらぽん http://d.hatena.ne.jp/hilapon/
    2010年9月8日 6:54
    モデレータ
  •  
     //実行プログラム(前略)
    
     Parent myClass = null;
    
     if (address == "")
    
     {
    
       myClass = new Parent(name);
    
     }
    
     else
    
     {
    
       myClass = new Child(name, address);
    
     }
    
     string myAddress = myClass.address;//←myClassがChildの場合、addressを参照できない
    
    


     

    変数 myClass の型は Parent です。そして、Parent には address フィールドがありません。このままではコンパイルエラーになります。

    Parent クラスが address フィールドを持つことに問題が無いなら、Parent に address フィールドを追加するといいです。

    address メンバを Child クラスにだけ持たせたいなら、

    string myAddress = "";
    if (myClass is Child)
    {
     myAddress = ((Child)myClass).address;
    }
    

    という風に、キャストするしかないと思います。この場合、myClass の型が Parent クラスだったときの対応が必要ですけど。

     


    なかむら(http://d.hatena.ne.jp/griefworker)
    2010年9月8日 6:57
  • 処理をどのクラスが実装すべきかはクラスの役割によって違ってきますが、(以下略)のところを Parent や Child に持たせてしまうかもしれません。
    丸投げが難しい場合には、if で分けているような部分だけ Parent や Child にやらせてしまえば良いと思います。
    もしくは Strategy パターンとか。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月8日 6:59
  • myClassにどのようなインスタンスが入っていようが、あくまでParent型として扱いますからaddressが参照できないのは無理もないです。といいますか、提示されたコードはコンパイルが通らないですよね。Childクラスのコンストラクタと思われる部分の名前も違っていると思いますし・・・
    実行時に型が決まるのはC# 4.0であればdynamicが使えますが、この場合でもParent型のインスタンスが渡ってくればaddressプロパティが無いので実行時エラーになってしまいます。そもそも、addressプロパティを持たない型のインスタンスが渡ってくる可能性があるのに、常にaddressプロパティを使おうとするのは無理があります。

    インターフェースでaddressプロパティの存在を保障、もしくは基底クラスにaddressプロパティを持てば解決します。実行時にどのような型のインスタンスが渡ってきても、それらに共通の基底クラスにaddressプロパティがあるから、addressプロパティにアクセスできることが保障されるわけです。継承関係の末端である派生クラスでaddressプロパティを定義してしまうと、その型以外の型からaddressプロパティにアクセスできないのは当然の結果です。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年9月8日 7:03
    モデレータ
  • 新規作成ならば、、、

    クラスが親子関係なので
    子クラスのみ使うように設計を見直します。

    メンテナンスで極力既存ソースを変えたくないというケースなら、、、
    実行時の型判定を使った回避策をとるかもしれません。


    if (myClass.GetType() == typeof(Child)) {
    string myAddress = ((Child)myClass).address;//←myClassがChildの場合、addressを参照できない
    }
    2010年9月8日 7:08
  • このスレッドを通して感じたこととして、代表としてここにつけてみます。

    ・myClass.GetType() == typeof(Child) は myClass is Child で書く方が良いかもしれません。
     理由:シンプルなこともありますが、前者は Child の派生クラスをカバーできません。(派生クラスを除外したい場合もあるかもしれませんが)

    ・is 演算子でチェックした後にキャストするのは効率が悪いかもしれません。as 演算子の戻り値を null かどうかで処理を分けましょう。
     理由:二重にキャストしているため。静的コード解析では警告を発するかもしれません。
     http://msdn.microsoft.com/ja-jp/library/ms182271.aspx

    ・(おまけ)必ずキャストに成功する前提であれば、is/as 演算子は避けましょう。キャスト式 (Child) を使うべきです。
     たまに、(myClass as Child).address みたいなコードを見かけますが、これを書くくらいならキャスト式の方がましです。。。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月8日 14:04
    モデレータ
  • typeof と is を正しく使い分けるために例を挙げます。

    Parent myClass1 = new Parent(name);
    myClass1.GetType() == typeof(Parent) //TRUE
    myClass1.GetType() == typeof(Child) //FALSE
    myClass1 is Parent //TRUE
    myClass1 is Child //FALSE
    ------
    Parent myClass2 = new Child(name, address);
    myClass2.GetType() == typeof(Parent) //FALSE  ←インスタンスは Child であり Parent ではない。
    myClass2.GetType() == typeof(Child) //TRUE
    myClass2 is Parent //TRUE  ← Child は Parent にキャスト可能なので TRUE。
    myClass2 is Child //TRUE
    ------
    Child myClass3 = new Child(name, address);
    myClass3.GetType() == typeof(Parent) //FALSE
    myClass3.GetType() == typeof(Child) //TRUE
    myClass3 is Parent //TRUE  ← Child は Parent にキャスト可能なので TRUE。
    myClass3 is Child //TRUE

    ← Child は Parent にキャスト可能なので TRUE。』のところは
    Parent Child を Control TextBox に置き換えると解り易いです。

    TextBox is Control  //TRUE  ←テキストボックスはコントロールです。
    TextBox is TextBox  //TRUE

    のようにどちらも TRUE になります。

    以上、テストすればわかることなので言わずもがなでしょうが念のため。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 7:11
  • anningo さんの返信の意図って、もしかすると Azulean さんの補足(例を挙げるだけ)じゃないかもしれないと思ったので、念のため書かせてもらいます。

    型判定による回避策としては address をメンバに持つことの確認になりますから、
    Parent 型のチェックは不要ですし、
    孫ができることを考慮すれば is にする方が無難だと思いました。

    Parent myClass;
    myClass = new Parent(...);
    myClass.GetType() == typeof(Child) // false
    myClass is Child // false

    myClass = new Child(...);
    myClass.GetType() == typeof(Child) // true
    myClass is Child // true

    myClass = new Grandchild(...); // class Grandchild : Child
    myClass.GetType() == typeof(Child) // false → address は参照されない。
    myClass is Child // true

    それと、私も as を使います。
    var myChildClass = myClass as Child;
    if (myChildClass != null)
        MessageBox.Show(myChildClass.address); // address が参照できる。

    実際の内容次第ですけど、
    理想的には、私もすでに挙がっているデザインパターンの採用が一番だと思いました
    (訂正:1つの選択肢ということで。。。)

    • 編集済み TH01 2010年9月9日 9:37 訂正
    2010年9月9日 9:00
  • > 型判定による回避策としては address をメンバに持つことの確認になりますから、
    > Parent 型のチェックは不要ですし、
    > 孫ができることを考慮すれば is にする方が無難だと思いました。

    スレ主さんの質問は2つあり、ひとつは
    『また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?』です。
    とくに Child かどうかの判断のみではないとおもいます。

    たとえば、今回の例でいうと後続処理に
    「Parent の場合 address を〇〇から取得する。」といった仕様は存在しうるとおもいます。

    その場合
    if (myClass is Parent) {……
    と書いてしまってはバグです。

    > anningo さんの返信の意図って、もしかすると Azulean さんの補足(例を挙げるだけ)じゃないかもしれないと思ったので、念のため書かせてもらいます。

    とくにどなたへの補足というつもりはありませんでした。
    なんとなく、もうちょっと議論が進むと理解が深まる、もしくは別観点から提案がある
    かもなあと思い、てきとうなタイミングで書いてみたというのはちょっとだけあります。

    2010年9月9日 10:13
  • 継承関係は考慮せずに型を特定する場合には is はダメですよということですね。
    「正しく使い分けるため」と書かれているのに、素直な気持ちが欠けてました。。(^^;

    純粋に議論の続きですが、
    「Parent の場合 address を〇〇から取得する。」
    をもし
    「address がない場合は〇〇から取得する。」
    に置き換えることができれば(「Parent の場合」という処理ではだめですけど)、
    if (!(myClass is Child)) {・・・
    のように書く手もありますね。

    2010年9月9日 10:51
  • 継承関係は考慮せずに型を特定する場合には is はダメですよということですね。

    「派生クラスを除外したい場合」ですね。
    ただ、このパターンはどこかで改良の余地がありそうには思います。(必ず「ある」と言い切れませんが、「ある」可能性は高そうです)

    # sealed キーワードというものも頭の中で引っかかりましたが、後でそのキーワードを消されるリスクはあるので万全じゃないですね。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月9日 13:34
    モデレータ
  •  

    多数のご意見ありがとうございます。
    勉強しながら読ませてもらいました。

    子クラスのインスタンスを親クラスの変数に入れて
    同じように使いたいということで質問したのですが
    そういう使い方はできないのだと分かりました。

    address定義を親クラスに入れて、親クラス変数に入れた
    子クラスのインスタンスからでもaddressを参照できるというのは、
    現場的にはOKかなと思いました。

    ただ、以下のコードのような場合ではnumWing(翼の数)の
    定義をVehicle親クラスにするということと同じになり、
    整合が合わなくなるかもと思ってます。

     //親クラス(乗り物)
     public class Vehicle
     {
     public int numTire;//タイヤの数
     public Vehicle(int numTire)
     {
      this.numTire = numTire;
     }
     }
     //子クラス(飛行機)
     public class Airplane : Vehicle
     {
     public int numWing;//翼の数
     public Airplane(int numTire, int numWing) : base(numTire)
     {
      this.numWing = numWing;
     }
     }
    

    もし親クラスの変数に子クラスのインスタンスが
    入れられたら、プログラマがそれを意図しているものとして
    自動的に変数が子クラスになるとかであればいいのだと思いますが・・。
    (これって問題ありますでしょうか? varがあるなら、こんなのもと思うのですが・・)

    で、実際にはtypeOf()で判定して、子クラスのインスタントであれば
    キャストするという方法を試してみようかと思ってます。

    2010年9月9日 14:14
  • ただ、以下のコードのような場合ではnumWing(翼の数)の
    定義をVehicle親クラスにするということと同じになり、
    整合が合わなくなるかもと思ってます。

    「”飛行機”に”翼の数”があるのは良いが、”乗り物”に”翼の数”があることはおかしい」ということでしょうか。
    そうであれば、「”乗り物”と扱うと決めた変数に対して、”翼の数”があることを期待したコードを書くな」ということになるかと思います。

    もし親クラスの変数に子クラスのインスタンスが
    入れられたら、プログラマがそれを意図しているものとして
    自動的に変数が子クラスになるとかであればいいのだと思いますが・・。
    (これって問題ありますでしょうか? varがあるなら、こんなのもと思うのですが・・)

    子クラスの型の変数に代入して扱うべきでしょう。(var もコンパイル時に具体的な型名に置き換えられてます)
    親クラスの型の変数に代入して扱う場合、親クラスにあるものだけを使うべきです。(ダウンキャストする場合を除く)

    ”乗り物”に対して代入した場合、その変数に対する操作はどんな”乗り物”にも使える操作になります。
    その状況で、”飛行機”にしか許されていない、”翼の数”をそのまま参照できたら、どんな”乗り物”でも OK という条件が満たせなくなります。そんな処理は、”乗り物”に対して行うべきではないでしょう。
    また、そのメソッドをコンパイルする時点で、”飛行機”であるか、ほかの”乗り物”となるかは特定できない可能性があります。その場合に、自動的に”飛行機”になることは不可能です。

    さて、「以下略」がほとんど共通だとのことですが、アイデア次第でまとめられるはずです。
    ただ、その具体的なコードが見えない状況では納得できる形のコードを提示できないかもしれません。

    # dynamic キーワードがやりたいことに近いのかもしれませんが、コンパイル時にスペルミスなどもチェックされなくなるので今の段階ではおすすめしません。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 15:11
    モデレータ
  •  いさぎよく以下のように書けばいいのかもしれませんが、
    ”以下略”の部分の処理が長く、また重複している処理もあるので、
    ParentとChildを同等に扱うことができるように、プログラムを
    書くことが出来る方法があればと思っています。

    ちなみに、以下のようには書けないのですよね。

    Parent p;
    if (address == "")
    {
     p = new Parent(name);
     // Parent 独自の処理
    }
    else
    {
     Child c = new Child(name, address);
     // Child 独自の処理
     p = c;
    }
    // Parent/Child 共通の処理(p に対して操作するので Parent にあるメンバに限る)
    // p は if ブロックの外にあるので Parent/Child どちらの場合も通るし、スコープとして有効
    
    

    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月9日 15:17
    モデレータ
  • 子クラスのインスタンスを親クラスの変数に入れて
    同じように使いたいということで質問したのですが
    そういう使い方はできないのだと分かりました。


    できない、と言い切ってしまうのは早計かもしれません。
    Parentクラスのオブジェクトとしてでしたら、両方を区別なく使うことができます。

    (ただし、Parentオブジェクトとして扱うので、Childクラス固有のメンバーであるaddressを使えません。)

    違う種類のオブジェクトを同じように扱うには、前提として両方に共通のメンバーが必要です。
    例えば、次のようになります。

    class Parent 
    {
     ...
     public virtual void Process()
     {
      //Parentを使う処理
     }
    }
    class Child : Parent
    {
     ...
     public override void Process()
     {
      //Childを使う処理
     }
    }
    
    

    そして、オブジェクトの生成処理を別メソッドにします。

    Parent CreateInstance(string name, string address)
    {
     if(address == "")
     {
      return new Parent(name);
     }
     else
     {
      return new Child(name, address);
     }
    } 
    
    

    すると、呼び出し側のコードを単純にできます。

    Parent p = CreateInstance(name, address);
    p.Process(); 
    

    addressについても同じように扱うには、addressを両方のクラスに含める(または、親クラスから継承させる)必要があります。

    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 15:20
  • なんかOOPの問題で出てきそうな話ですね。「図形」と「描画方法」の関係とかに似たような話です。
    個人的には上記のような状況だと、クラスを使う側が極力キャストしないで済むよう(というか・・・実際のクラスが何であるかを知らないで済むよう)クラス構成を考えます。

    以下は C# の話ではなく、考え方の話になります。あくまで参考までということで、興味なければ流してください。

    TH01さんが

    > 実際の内容次第ですけど、
    > 理想的には、私もすでに挙がっているデザインパターンの採用が一番だと思いました
    > (訂正:1つの選択肢ということで。。。)

    と言われてますが、私はパターンの採用を「一つの選択肢」と思いません。ある意味一番「理想」です。(^^;
    (こういう複雑な問題を、以前から存在する古典的な方法で解決するのがパターンです。とはいえパターン信者じゃありませんが)

    「パターン」というと、とかく難しそうな印象がありまして、一昔前はOOP の基礎を覚えた後にパターンを学習するというのが定石だったようですが、最近ではオブジェクト指向を習うと同時にパターンを修得した方が、初心者でも OOP の理解が深まるという話も出ているようです。詳しくは以下参照

    http://www.amazon.co.jp/dp/4894716844/

    脱線しそうなので元に戻りますが、上の問題の場合、実装中心に考えるのではなく、「乗り物」と「移動方法」の関係という抽象的概念から問題を解決する方法もあったりします。

    上のクラスの例だと、親クラスでタイヤの数を定義してますが、本当にそのメンバは必要なのか。
    親クラスでは「乗り物」を地上を移動すると想定してタイヤ数をメンバに持っているようですが、では「船」の場合どうするか?
    実装中心にものを考えるだけでなく、そのクラスは本当に親クラスと関係あるのか?そのメンバはクラスに本当に必要なのかと自問自答してみると、意外な解が見つかったりします。

     #抽象的な話ですみません。


    ひらぽん http://d.hatena.ne.jp/hilapon/
    • 回答としてマーク 山本春海 2010年10月1日 7:46
    2010年9月9日 16:25
    モデレータ
  • ただ、以下のコードのような場合ではnumWing(翼の数)の
    定義をVehicle親クラスにするということと同じになり、
    整合が合わなくなるかもと思ってます。

    クラスとは抽象化されたものですから、numWingをVehicleに入れるのは誤りでしょう。乗り物という抽象化された概念には翼は必然ではないからです。継承は抽象化のレベルにしたがって行われるべきですから、乗り物にはnumWingは定義せず、例えば以下のようになるでしょう。

    乗り物 → 飛行機(numWingフィールド) → プロップ機(プロペラの枚数フィールド)
                            |
                                                   +---→ ジェット機(ジェットエンジン数フィールド)

    プロップ機、ジェット機でもnumWingフィールドを扱えます。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2010年9月10日 1:52
    モデレータ