トップ回答者
C#の継承について教えてください

質問
-
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が参照できないのですが、このような使い方は間違っているのでしょうか?また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?
どうかよろしくお願いします。
回答
-
typeof と is を正しく使い分けるために例を挙げます。
Parent myClass1 = new Parent(name);myClass1.GetType() == typeof(Parent) //TRUEmyClass1.GetType() == typeof(Child) //FALSEmyClass1 is Parent //TRUEmyClass1 is Child //FALSE------Parent myClass2 = new Child(name, address);myClass2.GetType() == typeof(Parent) //FALSE ←インスタンスは Child であり Parent ではない。myClass2.GetType() == typeof(Child) //TRUEmyClass2 is Parent //TRUE ← Child は Parent にキャスト可能なので TRUE。myClass2 is Child //TRUE------Child myClass3 = new Child(name, address);myClass3.GetType() == typeof(Parent) //FALSEmyClass3.GetType() == typeof(Child) //TRUEmyClass3 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
-
ただ、以下のコードのような場合ではnumWing(翼の数)の
定義をVehicle親クラスにするということと同じになり、
整合が合わなくなるかもと思ってます。「”飛行機”に”翼の数”があるのは良いが、”乗り物”に”翼の数”があることはおかしい」ということでしょうか。
そうであれば、「”乗り物”と扱うと決めた変数に対して、”翼の数”があることを期待したコードを書くな」ということになるかと思います。もし親クラスの変数に子クラスのインスタンスが
入れられたら、プログラマがそれを意図しているものとして
自動的に変数が子クラスになるとかであればいいのだと思いますが・・。
(これって問題ありますでしょうか? varがあるなら、こんなのもと思うのですが・・)子クラスの型の変数に代入して扱うべきでしょう。(var もコンパイル時に具体的な型名に置き換えられてます)
親クラスの型の変数に代入して扱う場合、親クラスにあるものだけを使うべきです。(ダウンキャストする場合を除く)”乗り物”に対して代入した場合、その変数に対する操作はどんな”乗り物”にも使える操作になります。
その状況で、”飛行機”にしか許されていない、”翼の数”をそのまま参照できたら、どんな”乗り物”でも OK という条件が満たせなくなります。そんな処理は、”乗り物”に対して行うべきではないでしょう。
また、そのメソッドをコンパイルする時点で、”飛行機”であるか、ほかの”乗り物”となるかは特定できない可能性があります。その場合に、自動的に”飛行機”になることは不可能です。さて、「以下略」がほとんど共通だとのことですが、アイデア次第でまとめられるはずです。
ただ、その具体的なコードが見えない状況では納得できる形のコードを提示できないかもしれません。# dynamic キーワードがやりたいことに近いのかもしれませんが、コンパイル時にスペルミスなどもチェックされなくなるので今の段階ではおすすめしません。
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。- 回答としてマーク 山本春海 2010年10月1日 7:46
-
子クラスのインスタンスを親クラスの変数に入れて
同じように使いたいということで質問したのですが
そういう使い方はできないのだと分かりました。
できない、と言い切ってしまうのは早計かもしれません。
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
-
なんかOOPの問題で出てきそうな話ですね。「図形」と「描画方法」の関係とかに似たような話です。
個人的には上記のような状況だと、クラスを使う側が極力キャストしないで済むよう(というか・・・実際のクラスが何であるかを知らないで済むよう)クラス構成を考えます。以下は C# の話ではなく、考え方の話になります。あくまで参考までということで、興味なければ流してください。
TH01さんが
> 実際の内容次第ですけど、
> 理想的には、私もすでに挙がっているデザインパターンの採用が一番だと思いました。
> (訂正:1つの選択肢ということで。。。)と言われてますが、私はパターンの採用を「一つの選択肢」と思いません。ある意味一番「理想」です。(^^;
(こういう複雑な問題を、以前から存在する古典的な方法で解決するのがパターンです。とはいえパターン信者じゃありませんが)「パターン」というと、とかく難しそうな印象がありまして、一昔前はOOP の基礎を覚えた後にパターンを学習するというのが定石だったようですが、最近ではオブジェクト指向を習うと同時にパターンを修得した方が、初心者でも OOP の理解が深まるという話も出ているようです。詳しくは以下参照
http://www.amazon.co.jp/dp/4894716844/
脱線しそうなので元に戻りますが、上の問題の場合、実装中心に考えるのではなく、「乗り物」と「移動方法」の関係という抽象的概念から問題を解決する方法もあったりします。
上のクラスの例だと、親クラスでタイヤの数を定義してますが、本当にそのメンバは必要なのか。
親クラスでは「乗り物」を地上を移動すると想定してタイヤ数をメンバに持っているようですが、では「船」の場合どうするか?
実装中心にものを考えるだけでなく、そのクラスは本当に親クラスと関係あるのか?そのメンバはクラスに本当に必要なのかと自問自答してみると、意外な解が見つかったりします。#抽象的な話ですみません。
ひらぽん http://d.hatena.ne.jp/hilapon/- 回答としてマーク 山本春海 2010年10月1日 7:46
すべての返信
-
>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; } }
-
> string myAddress = myClass.address;//←myClassがChildの場合、addressを参照できない
> myClassにChildクラスのインスタンスが入れられた場合、上記にあるように
> myClass.addressが参照できないのですが、このような使い方は間違っているのでしょうか?これを実装したいならクラス Parent にフィールド address を移動するしかないと思いますが、何か出来ない事情があるのでしょうか。
あと拡張メソッドという手もありますが、このケースの場合は止めといた方がよさそうですね。
> また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?
Template Method や Strategy/State パターン等、色々なパターンが思い浮かびますが。
「デザインパターン」 で検索してみてください。いろいろヒットすると思います。
ひらぽん http://d.hatena.ne.jp/hilapon/ -
//実行プログラム(前略) 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) -
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/ -
このスレッドを通して感じたこととして、代表としてここにつけてみます。
・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 みたいなコードを見かけますが、これを書くくらいならキャスト式の方がましです。。。
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。 -
typeof と is を正しく使い分けるために例を挙げます。
Parent myClass1 = new Parent(name);myClass1.GetType() == typeof(Parent) //TRUEmyClass1.GetType() == typeof(Child) //FALSEmyClass1 is Parent //TRUEmyClass1 is Child //FALSE------Parent myClass2 = new Child(name, address);myClass2.GetType() == typeof(Parent) //FALSE ←インスタンスは Child であり Parent ではない。myClass2.GetType() == typeof(Child) //TRUEmyClass2 is Parent //TRUE ← Child は Parent にキャスト可能なので TRUE。myClass2 is Child //TRUE------Child myClass3 = new Child(name, address);myClass3.GetType() == typeof(Parent) //FALSEmyClass3.GetType() == typeof(Child) //TRUEmyClass3 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
-
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 訂正
-
> 型判定による回避策としては address をメンバに持つことの確認になりますから、> Parent 型のチェックは不要ですし、> 孫ができることを考慮すれば is にする方が無難だと思いました。
スレ主さんの質問は2つあり、ひとつは『また実行時にどのクラスを使うか決まるような場合、他のよい方法がありますでしょうか?』です。とくに Child かどうかの判断のみではないとおもいます。
たとえば、今回の例でいうと後続処理に「Parent の場合 address を〇〇から取得する。」といった仕様は存在しうるとおもいます。
その場合if (myClass is Parent) {……と書いてしまってはバグです。
> anningo さんの返信の意図って、もしかすると Azulean さんの補足(例を挙げるだけ)じゃないかもしれないと思ったので、念のため書かせてもらいます。
とくにどなたへの補足というつもりはありませんでした。なんとなく、もうちょっと議論が進むと理解が深まる、もしくは別観点から提案があるかもなあと思い、てきとうなタイミングで書いてみたというのはちょっとだけあります。
-
継承関係は考慮せずに型を特定する場合には is はダメですよということですね。
「派生クラスを除外したい場合」ですね。
ただ、このパターンはどこかで改良の余地がありそうには思います。(必ず「ある」と言い切れませんが、「ある」可能性は高そうです)# sealed キーワードというものも頭の中で引っかかりましたが、後でそのキーワードを消されるリスクはあるので万全じゃないですね。
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。 -
多数のご意見ありがとうございます。
勉強しながら読ませてもらいました。子クラスのインスタンスを親クラスの変数に入れて
同じように使いたいということで質問したのですが
そういう使い方はできないのだと分かりました。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()で判定して、子クラスのインスタントであれば
キャストするという方法を試してみようかと思ってます。 -
ただ、以下のコードのような場合ではnumWing(翼の数)の
定義をVehicle親クラスにするということと同じになり、
整合が合わなくなるかもと思ってます。「”飛行機”に”翼の数”があるのは良いが、”乗り物”に”翼の数”があることはおかしい」ということでしょうか。
そうであれば、「”乗り物”と扱うと決めた変数に対して、”翼の数”があることを期待したコードを書くな」ということになるかと思います。もし親クラスの変数に子クラスのインスタンスが
入れられたら、プログラマがそれを意図しているものとして
自動的に変数が子クラスになるとかであればいいのだと思いますが・・。
(これって問題ありますでしょうか? varがあるなら、こんなのもと思うのですが・・)子クラスの型の変数に代入して扱うべきでしょう。(var もコンパイル時に具体的な型名に置き換えられてます)
親クラスの型の変数に代入して扱う場合、親クラスにあるものだけを使うべきです。(ダウンキャストする場合を除く)”乗り物”に対して代入した場合、その変数に対する操作はどんな”乗り物”にも使える操作になります。
その状況で、”飛行機”にしか許されていない、”翼の数”をそのまま参照できたら、どんな”乗り物”でも OK という条件が満たせなくなります。そんな処理は、”乗り物”に対して行うべきではないでしょう。
また、そのメソッドをコンパイルする時点で、”飛行機”であるか、ほかの”乗り物”となるかは特定できない可能性があります。その場合に、自動的に”飛行機”になることは不可能です。さて、「以下略」がほとんど共通だとのことですが、アイデア次第でまとめられるはずです。
ただ、その具体的なコードが見えない状況では納得できる形のコードを提示できないかもしれません。# dynamic キーワードがやりたいことに近いのかもしれませんが、コンパイル時にスペルミスなどもチェックされなくなるので今の段階ではおすすめしません。
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。- 回答としてマーク 山本春海 2010年10月1日 7:46
-
いさぎよく以下のように書けばいいのかもしれませんが、
”以下略”の部分の処理が長く、また重複している処理もあるので、
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 どちらの場合も通るし、スコープとして有効
質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。 -
子クラスのインスタンスを親クラスの変数に入れて
同じように使いたいということで質問したのですが
そういう使い方はできないのだと分かりました。
できない、と言い切ってしまうのは早計かもしれません。
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
-
なんかOOPの問題で出てきそうな話ですね。「図形」と「描画方法」の関係とかに似たような話です。
個人的には上記のような状況だと、クラスを使う側が極力キャストしないで済むよう(というか・・・実際のクラスが何であるかを知らないで済むよう)クラス構成を考えます。以下は C# の話ではなく、考え方の話になります。あくまで参考までということで、興味なければ流してください。
TH01さんが
> 実際の内容次第ですけど、
> 理想的には、私もすでに挙がっているデザインパターンの採用が一番だと思いました。
> (訂正:1つの選択肢ということで。。。)と言われてますが、私はパターンの採用を「一つの選択肢」と思いません。ある意味一番「理想」です。(^^;
(こういう複雑な問題を、以前から存在する古典的な方法で解決するのがパターンです。とはいえパターン信者じゃありませんが)「パターン」というと、とかく難しそうな印象がありまして、一昔前はOOP の基礎を覚えた後にパターンを学習するというのが定石だったようですが、最近ではオブジェクト指向を習うと同時にパターンを修得した方が、初心者でも OOP の理解が深まるという話も出ているようです。詳しくは以下参照
http://www.amazon.co.jp/dp/4894716844/
脱線しそうなので元に戻りますが、上の問題の場合、実装中心に考えるのではなく、「乗り物」と「移動方法」の関係という抽象的概念から問題を解決する方法もあったりします。
上のクラスの例だと、親クラスでタイヤの数を定義してますが、本当にそのメンバは必要なのか。
親クラスでは「乗り物」を地上を移動すると想定してタイヤ数をメンバに持っているようですが、では「船」の場合どうするか?
実装中心にものを考えるだけでなく、そのクラスは本当に親クラスと関係あるのか?そのメンバはクラスに本当に必要なのかと自問自答してみると、意外な解が見つかったりします。#抽象的な話ですみません。
ひらぽん http://d.hatena.ne.jp/hilapon/- 回答としてマーク 山本春海 2010年10月1日 7:46
-
ただ、以下のコードのような場合ではnumWing(翼の数)の
定義をVehicle親クラスにするということと同じになり、
整合が合わなくなるかもと思ってます。クラスとは抽象化されたものですから、numWingをVehicleに入れるのは誤りでしょう。乗り物という抽象化された概念には翼は必然ではないからです。継承は抽象化のレベルにしたがって行われるべきですから、乗り物にはnumWingは定義せず、例えば以下のようになるでしょう。
乗り物 → 飛行機(numWingフィールド) → プロップ機(プロペラの枚数フィールド)
|
+---→ ジェット機(ジェットエンジン数フィールド)プロップ機、ジェット機でもnumWingフィールドを扱えます。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年9月10日 1:57 追記