オブジェクト
最終更新:
atachi
クラスのインスタンス作成
クラスはnew演算子を使ってインスタンス化する。
MyClass inst = new MyClass();
MyClass inst2 = new MyClass(1); // 引数指定
Point p = new Point{ X=0, Y=0}; // オブジェクト初期化子
オブジェクト初期化子でクラスをインスタンス化する方法では、デフォルトコンストラクタが呼び出される。
デフォルトコンストラクタが実装されていないクラスではオブジェクト初期化子によるインスタンス化はできない。
ポリモーフィズムなインスタンスの変数
基底クラスの変数は派生クラスのインスタンスを格納できる。
class Parent {
}
class Child : Parent {
}
void main() {
Parent p = new Child(); // Parent型の変数にChildクラスのインスタンスを代入することができる。
}
クラスを分割で定義できるパーシャルクラス
これまではクラスの定義は必ず「class クラス名 { クラス定義 }」という形でコーディングしなければならなかったが、partialキーワードを使用するとパーシャルクラスと呼ばれる分割定義可能なクラスを定義できます。
パーシャルクラスは次のように同一ファイルに複数のクラス定義文を記述することもできますし、複数のファイルをまたいでクラス定義文を記述することもできます。
partial class MyTest {
public int num;
}
partial class MyTest {
public int num2;
}
// ↓ 以下のクラスを定義したことと同じ
class MyTest {
public int num;
public int num2;
}
元々パーシャルクラスでないクラスにpartialキーワードをつけてクラスの定義を行うことはできない。
パーシャルクラスの利点は、ツールやマクロなどによって生成されたコードを使用する場合にあります。
通常これらのコードは不用意に編集すべきではないため別ファイルとして参照し、プログラマがコーディングを行うファイルと分けたいような場合に有用です。
ポリモーフィズム
静的な型情報と動的な型情報を取得
基底クラスの型は派生クラスのインスタンスを格納できる。(アップキャスト)
class Parent {
}
class Child:Parent {
}
// Parent型の変数にChildのインスタンスを設定できる
Parent p = new Child(); // アップキャスト
上記のコード内にある変数pについて、静的な型情報を取得したい場合にはtypeofキーワードを使用し、動的な型情報を取得したい場合は「p.GetType()」を使用します。
静的な型情報というのは、この場合Parentクラスに関する情報を指します。
動的な型情報は、この場合Childクラスの情報を指します。
アップキャストとダウンキャスト
基底クラスの変数に派生クラスのインスタンスを代入することをアップキャストと呼ぶ。
アップキャストでは次のように、キャスト先を明示しない。
class Parent {
}
class Child:Parent {
}
Parent p = new Child(); // アップキャスト
その逆を行うことをダウンキャストと呼ぶ。
上記のコード内にある変数pの実態はChildクラスなので、次のようにChild型変数にダウンキャストできる。
Parent p = new Child();
Child c = p as Child; // ダウンキャスト
Child c2 = (Child)p; // C/C++時代の伝統的なダウンキャストの記述
asによる安全なダウンキャスト
asキーワードを使用してダウンキャストを行うと、ダウンキャストに失敗した場合にnullを返すため、事前に安全なキャストであるかチェックする手間が省略できます。
C#では
専らasによるダウンキャストを使うべき
です。
Child c = p as Child;
if(c == null ) {
// キャスト失敗時の処理
}
C/C++やJavaで採用されている伝統的なキャストもC#ではサポートされていますが、使うべきではありません。
伝統的なキャストでは次のようなデメリットがあります。
- 一時変数を宣言する必要
- キャスト例外の処理が必要
Base b = new Parent();
Parent p;
try{
p = (Parent)b;
if( p != null ) {
// アップキャスト成功時の処理
}else{
// キャスト失敗時の処理
}
}catch( ... ) {
// キャスト失敗の例外処理
}
キャスト例外を捕捉するためパフォーマンスにも影響してしまう。
isによる変数の型チェック
isキーワードを使用すると変数に格納されたインスタンスの型を検証できる。
型が一致する場合は正を返し、一致しない場合には負を返す。
if( p is Child ) {
Child c = (Child)p; // pの実態はChildであることがわかっているので、安全にダウンキャスト可能。
}
ポリモーフィズムなメソッドの呼び出し
アップキャストによって基底クラスの変数からメソッドを呼び出す際に派生クラスで上書きされたメソッドである場合、メソッドの上書き方法によって呼び出される処理が異なります。
// hello()がnewによる上書きの場合
Parent p = new Child();
p.hello(); // Parent.hello()が実行
Child c = p;
c.hello(); // Child.hello()が実行(!)
// hello()がオーバーライドによる上書きの場合
Parent p = new Child();
p.hello(); // Child.hello()が実行(C++やJavaと同じ)
Child c = p;
c.hello(); // Child.hello()が実行
C++やJavaではメソッドの上書きにはオーバーライドしかないため後者のような処理になる。
C#では変数には静的な型と動的な型の2種類の解釈があり、原則的に静的な型として評価します。
newによる上書きは静的なクラス情報を書き換えるため、静的な型として解釈した場合にParent.hello()を呼び出してしまう。
インターフェース
メソッドの形だけを宣言する点では抽象メソッドと似ていますが、インターフェースと抽象メソッドを定義した抽象クラスとは決定的に異なる点がある。
- インターフェースは、すべてのメソッドがpublic
- インターフェースは、複数インプリメントが可能
interface IHandler {
void run();
}
オブジェクト指向をしっかりと理解していなければクラスの継承とインターフェースの違いについて理解しづらい。
たとえば次のような実態を扱うためのクラスを用意する。
abstract public Person {
protected string name;
protected int age;
public Person(string name,int age) {
this.name = name;
this.age = age;
}
abstract public void say();
}
public Sato : Person {
public Sato() : Person("佐藤",18) {
}
public void say() {
System.Write("ぼくの名前は" + name + "です");
}
}
public Tanaka : Person {
public Sato() : Person("田中",21) {
}
public void say() {
System.Write("私の名前は" + name + "です");
}
}
public SpeakingRobot : Person { // Personはおかしい。
public SpeakingRobot() : Person("SR-1型",1) {
}
public void say() {
System.Write("ワタシノナマエハ" + name + "デス");
}
}
SatoやTanakaはPersonであるため、Personを基底クラスに持つということはオブジェクト指向てきには正しいですが、 SpeakingRobotはPersonであるというには無理があり、オブジェクト指向的には間違っていると解釈できる。
正しくSpeakingRobotを実装するならば次のようになる。
// ロボットに年齢はないので、Personの時にあったageは削除した。
public SpeakingRobot {
protected string name;
public SpeakingRobot(){
this.name = "SR-1型";
}
public void say() {
System.Write("ワタシノナマエハ" + name + "デス");
}
}
ただし、SpeakingRobotはしゃべるロボットなのでsay()は実装した。
これらのクラスをインスタンス化して使用する際に「しゃべるオブジェクト」だけを集めてしゃべらせたい場合、SatoとTanakaはPersonを継承しているためPerson型の変数を使ってポリモーフィズムにsay()を呼び出せる。
Person[] persons = new Person[] { new Sato(), new Tanaka()};
foreach(Person p in persons) {
p.say();
}
// SpeakingRobotもしゃべることができるが、Person型の変数に代入することは出来ない。
そこでしゃべる(say()を実装した)機能を実装したインスタンスをまとめたい場合にインターフェースを利用できる。
interface ISay{
void say();
}
abstract public Person : ISay{
protected string name;
protected int age;
public Person(string name,int age) {
this.name = name;
this.age = age;
}
}
public Sato : Person {
public Sato() : Person("佐藤",18) {
}
public void say() {
System.Write("ぼくの名前は" + name + "です");
}
}
public Tanaka : Person {
public Sato() : Person("田中",21) {
}
public void say() {
System.Write("私の名前は" + name + "です");
}
}
public SpeakingRobot : ISay {
public SpeakingRobot() : Person("SR-1型",1) {
}
public void say() {
System.Write("ワタシノナマエハ" + name + "デス");
}
}
void main() {
ISay[] sayObjects = new ISay[] { new Sato(), new Tanaka(), new SpeakingRoot()};
foreach(ISay say : sayObjects) {
say.say();
}
}