C#を使うようにはなったものの、具体的な機能については未習熟なので(というかまだ使うべき状況が出てきていないので)
を読んで、まずは最低ラインを学んでいく。
10. オブジェクト指向②
10.1 静的メンバ
インスタンスを生成しなくても利用できるフィールド/メソッドを静的メンバという。これに対してインスタンスを生成しないと呼び出せないメソッドをインスタンスメソッドという。静的メンバはstatic修飾子を付ければよい。
静的メソッドの呼び出しには「クラス名.メソッド名(引数)」とする。static修飾子が付いたフィールドを静的フィールドという。ただし同一クラス内であればクラス名を省略できる。フィールドおよびメソッドへのアクセス制限を指定するために用いるのがアクセス修飾子である。静的メンバからインスタンスメンバに直接アクセスできない。
Mainメソッドはプログラム実行時に呼び出される処理が記述される特殊なメソッドである。
10.2 名前空間
1つのプログラム中に同名のクラスを共存可能にするようにクラスが所属するより抽象的な“クラス”(に近いもの)を名前空間という。
同一名称のクラスを呼び出す際には
名前空間.クラス名
の形で呼びたいクラスを宣言する。
10.2.1 usingディレクティブ
もっとも毎回名前空間付きでクラスを呼び出すのは煩雑であるため、usingディレクティブを用いて使用する名前空間名を予め宣言すれば、名前空間を省略してクラスを呼び出すことが出来る。
10.2.2 デフォルトで存在する名前空間
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;
10.3 スコープ
{}で囲まれた範囲のことをスコープという。
スコープ内で定義した変数をローカル変数と言い、ローカル変数は定義した時点から所属するスコープの終了まで存在する。したがって同一スコープに所属してなければ、同一名のローカル変数が同一プログラム内で共存し得る。
10.3.1 例題
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExampleProblem501 { class Program { static double Add(double a, double b) { return a + b; } static double Sub(double a, double b) { return a - b; } static double Mul(double a, double b) { return a * b; } static double Div(double a, double b) { return a / b; } static void Main(string[] args) { Console.Write("a="); double a = double.Parse(Console.ReadLine()); Console.Write("b="); double b = double.Parse(Console.ReadLine()); Console.WriteLine("a+b=", Add(a, b)); Console.WriteLine("a-b=", Sub(a, b)); Console.WriteLine("a*b=", Mul(a, b)); Console.WriteLine("a/b=", Div(a, b)); } } }
10.4 継承
基本となるクラスの性質を受け継いで独自の拡張をするために継承(インヘリタンス)という機能が存在する。
あるクラスがあるときに、それを継承して新たなクラス(サブクラス)を定義する場合の宣言方法は
class サブクラス名 : ベースクラス名
{
}
である。
C#ではサブクラスはベースクラスを1つしか持てない。これを単一継承という。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ExampleProblem502 { class Calculator { protected int num1; protected int num2; public int Num1 { set { num1 = value; } get { return num1; } } public int Num2 { set { num2 = value; } get { return num2; } } public void add() { Console.WriteLine("{0} + {1} = {2}", num1, num2, num1 + num2); } public void sub() { Console.WriteLine("{0} - {1} = {2}", num1, num2, num1 - num2); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sample502 { // Calculatorクラスを継承したExCalculatorクラス class ExCulculator : Calculator { public void mul() { Console.WriteLine("{0} * {1} = {2}", num1, num2, num1 * num2); } public void div() { Console.WriteLine("{0} / {1} = {2}", num1, num2, num1 / num2); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sample502 { static void Main(string[] args) { Calculator c1 = new Calculator(); c1.Num1 = 10; c1.Num2 = 3; c1.add(); c1.sub(); ExCalculator c2 = new ExCalculator(); c2.Num1 = 10; c2.Num2 = 3; c2.add(); c2.sub(); c2.mul(); c2.div(); } }
上記の例では、Calculatorクラスをprotected修飾子で定義したことで、外部からのアクセスが不可能な一方でサブクラスからのアクセスは可能になっている。
10.5 継承とコンストラクタ・デストラクタ
継承関係にあるクラス間のコンストラクタ・デストラクタを考えると、サブクラスのコンストラクタを呼び出すと、必ずベースクラスのコンストラクタが呼び出される。特に指定が無ければ引数なしのコンストラクタが呼び出される。
10.6 オーバーライドとポリモーフィズム
ベースクラスのメソッドを活用しつつもサブクラスで異なる処理をするように変えたいとする。
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sample505 { class Program { static void Main(string[] args) { // Parentクラスのインスタンス生成 Parent p = new Parent(); Child c = new Child(); // p.Foo(); c.Foo(); } } } // Parent.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sample505 { class Parent { public virtual void Foo() { Console.WriteLine("親クラスのFoo()メソッド"); } } } // Child.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sample505 { class Child : Parent { public override void Foo() { base.Foo();){ Console.WriteLine("子クラスのFoo()メソッド"); } } } }
まず親クラスにおいてvirtual修飾子を付けている。また子クラスにおいてはoverride修飾子を付けている。このように、サブクラスで名前、引数/戻り値の型が全く同じメソッドを定義すると、ベースクラスの同名メソッドと異なる動作をする。これをオーバーライドという。
10.8 Objectクラス
C#のすべてのクラスはObjectクラスを暗黙のうちに継承している。
メソッド |
動作 |
|
Equals | (object) | 引数内のオブジェクトと等しいかどうかを調べる。 |
ToString() | オブジェクトを表す文字列を返す。 | |
GetType() | オブジェクトのタイプを返す。 |
10.9 抽象クラストインターフェース
実際の開発現場には類似したクラスが同一名のメンバを持つことがしばしばある。またNameプロパティがいずれのクラスにも共通で中身も変わらないこともある。このように部分的に類似する処理が存在し同一名の異なる処理が存在するときに友好なのが抽象クラスである。
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractClass { class Program { static void Main(string[] args) { Crow c = new Crow(); Sparrow s = new Sparrow(); Console.Write(c.Name + " : "); c.Sing(); Console.Write(s.Name + " : "); s.Sing(); } } } // Crow.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractClass { class Crow : Bird { public Crow() : base("カラス") { } public override void Sing() { Console.WriteLine("カーカー"); } } } // Sparrow.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractClass { class Sparrow : Bird { public Sparrow() : base("すずめ") { } public override void Sing() { Console.WriteLine("チュンチュン"); } } } // Bird.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractClass { abstract class Bird { private String name; public Bird(string name) { this.name = name; } public String Name { get { return name; } } public abstract void Sing(); } }
定義の前にabstract修飾子を与えられたBirdクラスが抽象クラスである。
abstract class (クラス名){
...
}
抽象クラスはインスタンスを生成できないクラスである。抽象クラスの目的は「継承してサブクラスを作る」という設計を強制することである。
10.9.1 抽象メソッド
Bird.cs内で定義されたSing()では処理が実装されておらず、astract修飾子が付いている。このようなメソッドを抽象メソッドという。
abstract 戻り値 メソッド名(引数);
このメソッドをサブクラスで実装する場合、override修飾子を付ける必要がある。
override 戻り値 メソッド名(引数)
{
実装
}
10.9.2 ベースクラスにおけるコンストラクタの呼び出し
Birdクラスでの
abstract class Bird
{
...
public Bird(String name)
{
this.name = name;
}
}
は
class Crow : Bird
{
...
public Crow(String name): base("カラス")
...
}class Sparrow : Bird
{
...
public Sparrow(String name): base("すずめ")
...
}
10.10 抽象プロパティ
以下の例においてVectorクラスは抽象クラスVectorBaseを実装している。抽象プロパティにもabstract修飾子が付いており、中身にはsetおよびgetのみが記述されている。
//VectorBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AbstractProperty
{
abstract class VectorBase
{
public abstract double X
{
set;
get;
}
public abstract double Y
{
set;
get;
}
}
}
// Vector.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AbstractProperty
{
class Vector : VectorBase
{
private double x = 0.0;
private double y = 0.0;
public override double X
{
set { x = value; }
get { return x; }
}
public override double Y
{
set { y = value; }
get { return y; }
}
}
}
// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AbstractProperty
{
class Program
{
static void Main(string[] args)
{
Vector v = new Vector();
v.X = 0.1;
v.Y = 0.2;
Console.WriteLine("v = ({0},{1})", v.X, v.Y);
}
}
}
11. インターフェース
抽象クラスを更に発展させたのがインターフェースである。
// Program.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Interface { class Program { static void Main(string[] args) { CellPhone cp = new CellPhone("hoge@email.com", "090-1234-5678"); // cp.Call("011-123-4567"); cp.SendMail("fuga@email.com"); // IPhone phone = (IPhone)cp; phone.Call("011-987-6543"); // // // IEmail mail = (IEmail)cp; mail.SendMail("bar@email.com"); // //mail.Call("011-222-3333"); } } } // CellPhone.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Interface { class CellPhone : IPhone, IEmail { private string mailAddress; private string number; public CellPhone(string mailAddress, string number) { this.mailAddress = mailAddress; this.number = number; } public void SendMail(string address) { Console.WriteLine(address + "に" + this.mailAddress + "からメールを出します。"); } public void Call(string number) { Console.WriteLine(number + "に" + this.number + "から電話を掛けます。"); } } } // IPhone.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Interface { interface IPhone { void Call(string number); } } // IEmail.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Interface { interface IEmail { void SendMail(string address); } }
インターフェースは実装が無いメソッドの集合体のようなものである。
interface インターフェース名
{
...
}
インターフェースにはメソッドの実装やフィールドを持つことは出来ない。インターフェースの実装は他のクラスの役割である。インターフェースのメソッドはすべてpublicであることは前提であり、他の修飾子を付ける必要はない。インターフェースは命名するとき、"I"を頭に付けるのが普通である。
インターフェースを実装するときは軽症と同様にクラス名の後ろに「:」を書いたうえで「,」で区切って書いていく
class クラス名 : インターフェース名, インターフェース名, …
{
....
}
インターフェースは複数実装できる。
インターフェースを用いることで、各インターフェースからなる架空のクラスとして振る舞うことが出来る(そのため、キャストを行なっている箇所がある)。あるクラスをベースクラスやインターフェースの型に変更して代入するときはキャストを用いる。キャストすると、元は同じクラスのインスタンスであったとしても、キャストした型のメンバしか利用できなくなる。
- インターフェースは1つのクラスに様々な機能を実装しなくてはならない場合に必須の機能である。
- インターフェースを用いて強制的にクラスの機能を制限することで余計なメンバへのアクセスを制限できる。
インターフェースはメソッドの重複があっても問題無く、また複数のクラスを実装できる点が継承と根本的に異なる点である。