トップ回答者
基底クラスと派生クラス

質問
-
C#を勉強中です。派生クラスのインスタンスを作成した後の取り扱いについてご教示ください。
クラスは参照型であるため、別のクラスに代入した後代入元のクラスの内容を変更するとそれが代入先のクラスに反映されます(Test1)。このことを継承された派生クラスにも応用したいのですが、なかなか上手く行きません。
イメーとしては…
・ 現在東京に住む30歳の山田太郎さん(基底クラス:Person)は歌手(派生クラス:Siger)であると同時に学生(派生クラス:Student)でもある。
・ 山田太郎さんは31歳になったとき北海道に移住したので、基底クラスの情報を変更するとそれが同時に歌手及び学生の派生クラスの年齢と住所にも自動的に反映されるようにしたい。
Test2でこのような関係を記述してみたのですが、派生クラスのコンストラクタで渡した各要素は参照型と見做されていないようでうまく行きません。どうすれば良いかアドバイスいただけると幸いです。
ネットで探しているのですが、派生クラスのインスタンスを作成するまでの情報は沢山ありますが、その後の保守のやり方について説明した資料はなかなか見つかりません。ご存じであれば併せて教えていただけると助かります。
以上、初歩的な質問ですが、よろしくお願いします。
////////////////////////////////////////////////////////////////////////////////////////////////
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public Person(Person p)
{
this.Name = p.Name;
this.Age = p.Age;
this.Address = p.Address;
}
public Person(string name,int age,string adrs)
{
this.Name = name;
this.Age = age;
this.Address = adrs;
}
public object Clone()
{
return this.MemberwiseClone();
}
}
public class Singer : Person
{
private string _nickname;
public string NickName
{
get { return _nickname; }
set { _nickname = value; }
}
public Singer(Person p) : base(p)
{
}
}
public class Student : Person
{
private string _school;
public string Scool
{
get { return _school; }
set { _school = value; }
}
public Student(string name,int age,string adrs) : base(name,age,adrs)
{
}
}
///////////////////////////////////////////////////////////////////////////////////////
private void Test1()
{
Person taro = new Person("山田太郎", 30, "東京都");
Person taro1 = taro;
taro.Address = "北海道";
taro.Age = 31;
Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ",
taro.Name, taro.Age, taro.Address);
Debug.WriteLine("Copied Person/name= {0} age={1} adrs={2} ",
taro1.Name, taro1.Age, taro1.Address);
}
private void Test2()
{
Person taro = new Person("山田太郎", 30, "東京都");
Singer singertaro = new Singer(taro);
Student studenttaro = new Student(taro.Name,taro.Age,taro.Address);
taro.Address = "大阪";
taro.Age = 32;
Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ",
taro.Name, taro.Age, taro.Address);
Debug.WriteLine("Singer Taro/name= {0} age={1} adrs={2} ",
singertaro.Name, singertaro.Age, singertaro.Address);
Debug.WriteLine("Student Taro/name= {0} age={1} adrs={2} ",
studenttaro.Name, studenttaro.Age, studenttaro.Address);
}
回答
-
Test2でこのような関係を記述してみたのですが、派生クラスのコンストラクタで渡した各要素は参照型と見做されていないようでうまく行きません。どうすれば良いかアドバイスいただけると幸いです。
Person型のコンストラクタで元になるPersion型からName,Age,Addressプロパティの値をコピーしていますね。
ですが、以降は元プロパティを変更しても何も処理されないために派生クラス側のプロパティに反映されません。
Name,Age,Addressプロパティを持つクラスが参照型であっても、プロパティが値型なので値を変更は反映されないということです。あえてやるなら、Name型,Age型,Address型のクラスを用意してやると値型でなく参照型になるので反映させることはできます。
using System; using System.Diagnostics; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Debug.WriteLine("*** Test1 ***"); Test1(); Debug.WriteLine("*** Test2 ***"); Test2(); } private static void Test1() { Person taro = new Person("山田太郎", 30, "東京都"); Person taro1 = taro; taro.Address.Value = "北海道"; taro.Age.Value = 31; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Copied Person/name= {0} age={1} adrs={2} ", taro1.Name, taro1.Age, taro1.Address); } private static void Test2() { Person taro = new Person("山田太郎", 30, "東京都"); Singer singertaro = new Singer(taro); singertaro.NickName = "タロー"; Student studenttaro = new Student(taro); studenttaro.Shcool = "何処大学"; taro.Address.Value = "大阪"; taro.Age.Value = 32; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Singer Taro/name= {0} age={1} adrs={2} ", singertaro.Person.Name, singertaro.Person.Age, singertaro.Person.Address); Debug.WriteLine("Student Taro/name= {0} age={1} adrs={2} ", studenttaro.Person.Name, studenttaro.Person.Age, studenttaro.Person.Address); } } class Name { public string Value { get; set; } public override string ToString() { return Value.ToString(); } } class Age { public int Value { get; set; } public override string ToString() { return Value.ToString(); } } class Address { public string Value { get; set; } public override string ToString() { return Value.ToString(); } } class Person : ICloneable { public Name Name { get; set; } public Age Age { get; set; } public Address Address { get; set; } public Person() { Name = new Name(); Age = new Age(); Address = new Address(); } public Person(string name, int age, string adrs) : this() { this.Name.Value = name; this.Age.Value = age; this.Address.Value = adrs; } public object Clone() { return (Person)this.MemberwiseClone(); } } class Job : ICloneable { public Job(Person person) { this.Person = person; } public Person Person { get; private set; } public object Clone() { return this.MemberwiseClone(); } } class Singer : Job { public Singer(Person person) : base(person) { } public string NickName { get; set; } } class Student : Job { public Student(Person person) : base(person) { } public string Shcool { get; set; } } }
Person型からSinger型やStudent型を派生させているのは問題ではないのですが、SingerかつStudentを同時に表すPerson型というのは継承では作ることはできません。
Person型に複数の仕事を所有できるようにした方が作りとしては楽だと思います。
using System; using System.Diagnostics; using System.Collections.Generic; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Debug.WriteLine("*** Test1 ***"); Test1(); Debug.WriteLine("*** Test2 ***"); Test2(); } private static void Test1() { Person taro = new Person("山田太郎", 30, "東京都"); Person taro1 = taro; taro.Address = "北海道"; taro.Age = 31; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Copied Person/name= {0} age={1} adrs={2} ", taro1.Name, taro1.Age, taro1.Address); } private static void Test2() { Person taro = new Person("山田太郎", 30, "東京都"); Singer singertaro = new Singer(taro); singertaro.NickName = "タロー"; Student studenttaro = new Student(taro); studenttaro.Shcool = "何処大学"; taro.Jobs.Add(singertaro); taro.Jobs.Add(studenttaro); taro.Address = "大阪"; taro.Age = 32; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Singer Taro/name= {0} age={1} adrs={2} ", singertaro.Person.Name, singertaro.Person.Age, singertaro.Person.Address); Debug.WriteLine("Student Taro/name= {0} age={1} adrs={2} ", studenttaro.Person.Name, studenttaro.Person.Age, studenttaro.Person.Address); } } class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public Person() { this.Jobs = new List<Job>(); } public Person(string name, int age, string adrs) { this.Jobs = new List<Job>(); this.Name = name; this.Age = age; this.Address = adrs; } public object Clone() { Person p = (Person)this.MemberwiseClone(); p.Jobs = new List<Job>(); foreach (Job j in this.Jobs) { p.Jobs.Add((Job)j.Clone()); } return p; } public List<Job> Jobs { get; set; } public T FindJob<T>() where T : Job { foreach (Job j in this.Jobs) { if (j is T) { return (T)j; } } return null; } } class Job : ICloneable { public Job(Person person) { this.Person = person; } public Person Person { get; private set; } public object Clone() { return this.MemberwiseClone(); } } class Singer : Job { public Singer(Person person) : base(person) { } public string NickName { get; set; } } class Student : Job { public Student(Person person) : base(person) { } public string Shcool { get; set; } } }
#インターフェースという機能を使うとSingerインターフェース、Studentインターフェースを用意することでPersonに複数状態を持たせることができますが、種類が増えると大変になるのであまりお勧めできる方法ではありません。
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2018年8月7日 11:10 usingの漏れ追加
- 回答としてマーク Taizo Yamamoto 2018年8月7日 22:31
-
SingerやStudentのインスタンスに引数としてPersonのインスタンス(もしくはそのプロパティ)を渡すのであれば、派生クラスにするのではなく、IPersonといったインターフェースを定義してPersonインスタンスを渡してしまえば良いでのはないでしょうか?
そもそも基底クラス、派生クラスはクラスの定義の仕組みであって、インスタンスもしくはそのメンバーを共有するための仕組みではありません。基底クラス、派生クラスの関係にあっても、それぞれのインスタンスは全くの別物であり、そこにつながりはありません。1つのインスタンスであれば、基底クラス、派生クラスの関係にあればキャストできますが、型が変わるだけであくまで1つのインスタンスです。
くどいようですが、基底クラス、派生クラスはクラスの定義の関係を表したもので、それぞれのインスタンスの基底、派生を表すものではありません。というより、インスタンス同士に基底、派生の関係の結びつきはありません。
クラスとインスタンスをごっちゃに考えられているような気がします。
★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 編集済み trapemiyaModerator 2018年8月8日 4:42 一部修正
- 回答としてマーク Taizo Yamamoto 2018年8月8日 10:10
すべての返信
-
Test2でこのような関係を記述してみたのですが、派生クラスのコンストラクタで渡した各要素は参照型と見做されていないようでうまく行きません。どうすれば良いかアドバイスいただけると幸いです。
Person型のコンストラクタで元になるPersion型からName,Age,Addressプロパティの値をコピーしていますね。
ですが、以降は元プロパティを変更しても何も処理されないために派生クラス側のプロパティに反映されません。
Name,Age,Addressプロパティを持つクラスが参照型であっても、プロパティが値型なので値を変更は反映されないということです。あえてやるなら、Name型,Age型,Address型のクラスを用意してやると値型でなく参照型になるので反映させることはできます。
using System; using System.Diagnostics; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Debug.WriteLine("*** Test1 ***"); Test1(); Debug.WriteLine("*** Test2 ***"); Test2(); } private static void Test1() { Person taro = new Person("山田太郎", 30, "東京都"); Person taro1 = taro; taro.Address.Value = "北海道"; taro.Age.Value = 31; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Copied Person/name= {0} age={1} adrs={2} ", taro1.Name, taro1.Age, taro1.Address); } private static void Test2() { Person taro = new Person("山田太郎", 30, "東京都"); Singer singertaro = new Singer(taro); singertaro.NickName = "タロー"; Student studenttaro = new Student(taro); studenttaro.Shcool = "何処大学"; taro.Address.Value = "大阪"; taro.Age.Value = 32; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Singer Taro/name= {0} age={1} adrs={2} ", singertaro.Person.Name, singertaro.Person.Age, singertaro.Person.Address); Debug.WriteLine("Student Taro/name= {0} age={1} adrs={2} ", studenttaro.Person.Name, studenttaro.Person.Age, studenttaro.Person.Address); } } class Name { public string Value { get; set; } public override string ToString() { return Value.ToString(); } } class Age { public int Value { get; set; } public override string ToString() { return Value.ToString(); } } class Address { public string Value { get; set; } public override string ToString() { return Value.ToString(); } } class Person : ICloneable { public Name Name { get; set; } public Age Age { get; set; } public Address Address { get; set; } public Person() { Name = new Name(); Age = new Age(); Address = new Address(); } public Person(string name, int age, string adrs) : this() { this.Name.Value = name; this.Age.Value = age; this.Address.Value = adrs; } public object Clone() { return (Person)this.MemberwiseClone(); } } class Job : ICloneable { public Job(Person person) { this.Person = person; } public Person Person { get; private set; } public object Clone() { return this.MemberwiseClone(); } } class Singer : Job { public Singer(Person person) : base(person) { } public string NickName { get; set; } } class Student : Job { public Student(Person person) : base(person) { } public string Shcool { get; set; } } }
Person型からSinger型やStudent型を派生させているのは問題ではないのですが、SingerかつStudentを同時に表すPerson型というのは継承では作ることはできません。
Person型に複数の仕事を所有できるようにした方が作りとしては楽だと思います。
using System; using System.Diagnostics; using System.Collections.Generic; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Debug.WriteLine("*** Test1 ***"); Test1(); Debug.WriteLine("*** Test2 ***"); Test2(); } private static void Test1() { Person taro = new Person("山田太郎", 30, "東京都"); Person taro1 = taro; taro.Address = "北海道"; taro.Age = 31; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Copied Person/name= {0} age={1} adrs={2} ", taro1.Name, taro1.Age, taro1.Address); } private static void Test2() { Person taro = new Person("山田太郎", 30, "東京都"); Singer singertaro = new Singer(taro); singertaro.NickName = "タロー"; Student studenttaro = new Student(taro); studenttaro.Shcool = "何処大学"; taro.Jobs.Add(singertaro); taro.Jobs.Add(studenttaro); taro.Address = "大阪"; taro.Age = 32; Debug.WriteLine("Base Person/name= {0} age={1} adrs={2} ", taro.Name, taro.Age, taro.Address); Debug.WriteLine("Singer Taro/name= {0} age={1} adrs={2} ", singertaro.Person.Name, singertaro.Person.Age, singertaro.Person.Address); Debug.WriteLine("Student Taro/name= {0} age={1} adrs={2} ", studenttaro.Person.Name, studenttaro.Person.Age, studenttaro.Person.Address); } } class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } public string Address { get; set; } public Person() { this.Jobs = new List<Job>(); } public Person(string name, int age, string adrs) { this.Jobs = new List<Job>(); this.Name = name; this.Age = age; this.Address = adrs; } public object Clone() { Person p = (Person)this.MemberwiseClone(); p.Jobs = new List<Job>(); foreach (Job j in this.Jobs) { p.Jobs.Add((Job)j.Clone()); } return p; } public List<Job> Jobs { get; set; } public T FindJob<T>() where T : Job { foreach (Job j in this.Jobs) { if (j is T) { return (T)j; } } return null; } } class Job : ICloneable { public Job(Person person) { this.Person = person; } public Person Person { get; private set; } public object Clone() { return this.MemberwiseClone(); } } class Singer : Job { public Singer(Person person) : base(person) { } public string NickName { get; set; } } class Student : Job { public Student(Person person) : base(person) { } public string Shcool { get; set; } } }
#インターフェースという機能を使うとSingerインターフェース、Studentインターフェースを用意することでPersonに複数状態を持たせることができますが、種類が増えると大変になるのであまりお勧めできる方法ではありません。
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 編集済み gekkaMVP 2018年8月7日 11:10 usingの漏れ追加
- 回答としてマーク Taizo Yamamoto 2018年8月7日 22:31
-
早速のご回答、有難うございました。
頂いた2つの解決策の動作はそれぞれ確認できました。
内容についてはまだ消化不良なので、これからじっくり勉強いたします。しかし、こんな発想は今の私のC#のレベルでは到底浮かんできません。もっともっと勉強が必要ですね。
それにしても、実際のアプリケーションでは、私が例題として挙げたようなケースは極く当たり前のようにあると思います。実際にはもっともっと多くの項目が対象になる筈であり、それらのすべてにこのように対処するのは大変な労力が必要になるのではないでしょうか。素人考えかもしれませんが、C#の言語レベルでもっと簡単にできる方法が提供されると大変助かると思うのですが無理でしょうか(例えば、派生クラスを作るときにコンストラクタで基底クラスの内容をまるごと参照型で渡せるようにするとか...)。
いずれにしても、かなり長い間悩んでいた問題の解答が見つかり大変すっきりしました。本当に有難うございました。
-
SingerやStudentのインスタンスに引数としてPersonのインスタンス(もしくはそのプロパティ)を渡すのであれば、派生クラスにするのではなく、IPersonといったインターフェースを定義してPersonインスタンスを渡してしまえば良いでのはないでしょうか?
そもそも基底クラス、派生クラスはクラスの定義の仕組みであって、インスタンスもしくはそのメンバーを共有するための仕組みではありません。基底クラス、派生クラスの関係にあっても、それぞれのインスタンスは全くの別物であり、そこにつながりはありません。1つのインスタンスであれば、基底クラス、派生クラスの関係にあればキャストできますが、型が変わるだけであくまで1つのインスタンスです。
くどいようですが、基底クラス、派生クラスはクラスの定義の関係を表したもので、それぞれのインスタンスの基底、派生を表すものではありません。というより、インスタンス同士に基底、派生の関係の結びつきはありません。
クラスとインスタンスをごっちゃに考えられているような気がします。
★良い回答には質問者は回答済みマークを、閲覧者は投票を!
- 編集済み trapemiyaModerator 2018年8月8日 4:42 一部修正
- 回答としてマーク Taizo Yamamoto 2018年8月8日 10:10