C#(オブジェクト指向)の基本(1)

今日から始めました、#ゆーじ勉強週間 初日の記事です。

2日目の記事はこちら
3日目の記事はこちら
4日目の記事はこちら

これまで独学でUnityを触ってきたため、基礎知識や最近流行りの技術、よく使われている技術などを知らずに開発しているということを(特にインターンや就活、Slackコミュニティを通して)実感しています。

なので気になること、足りていないことを勉強する時間を一度設けよう、というのが#ゆーじ勉強週間 です。

ツイートしたりSlackで言ったりしてやらざるを得ない状況にしました。
勉強したことはこんな感じでちゃんと記事にして公開する予定です。

今日の勉強テーマ

今日の勉強テーマは、「C#(オブジェクト指向)の基本」です。

Unityで初めてC#(オブジェクト指向言語)を触り始めたため、Unityで使う処理とかはよく知っているのですが、C#の基礎、オブジェクト指向の基礎をそもそもあまり知らずに使ってきました。(反省)
 
ということで、簡単な内容も多く含みますが「C#(オブジェクト指向)の基本」について自分の備忘録としてまとめます。

勉強したことのメモのような感覚なので、上手くまとまってなかったりいろいろ飛ばしたりしてますがご了承ください。

こちらのサイトを参考にしました。
https://ufcpp.net/study/csharp/

オブジェクト指向の原則

オブジェクト指向には原則がある。

  • カプセル化
  • 継承
  • 多態性

ひとつずつ簡単に説明する。

カプセル化

カプセル化とは、オブジェクトの中身(内部実装)は外からは見えないようにして、可能な操作と属性だけを外に公開するということ。

外(クラス利用側)から見た振る舞いと中身(実装側)は分けて考えることが大事で、中身は利用側からは分からないようにする。

例えばPlayerを動かすクラスがあったとして、そのクラスの外からは「歩いて!」「ジャンプして!」という命令だけを送り、実際に歩く処理やジャンプする処理はクラス内で完結する、といったようなこと。

継承

継承とは、再利用性を高めるために、共通部分を引き継ぐこと。

例えばPlayerとEnemyのそれぞれのクラスに「歩く処理」「ジャンプする処理」を記述するのではなく、Characterクラスに「歩く処理」「ジャンプする処理」を記述しておき、PlayerとEnemyはCharacterの処理を引き継ぐようにすること。

多態性

多態性とは、同じメソッド呼び出しに対して、異なるオブジェクトが異なる処理をすること。

ポリモーフィズムとも呼ばれる。

例えばEnemyのDeadというメソッドを実行するとスコアが加算される一方で、PlayerのDeadというメソッドを実行するとゲームオーバーになる、というように同じDeadというメッセージに対してそれぞれのオブジェクトが異なる処理を行うこと。

クラスと構造体の違い

クラスと構造体は似ている部分もありるが、大きく異なる部分が以下の3点。

  • クラスは参照型、構造体は値型
  • クラスは継承できる
  • クラスは多態性がある

オブジェクト初期化子

Player player = new Player{ Id = 1, Name = "name" };

Player player = new Player();
p.Id = 1;
p.Name = "name";

と同じ(ただしId, Nameがpublicなどの場合に限る)

静的コンストラクタ

staticをつけたコンストラクタ。
普通のコンストラクタは新しいインスタンスが生成されるたびに呼び出されるが、
静的コンストラクタはそのクラスの何らかのメンバーに初めてアクセスしたときに、一度だけ呼び出される。

using static

using static [クラスA];

と記述しておくと、クラスAのstaticメソッドや列挙型を クラスA.Method() や クラスA.EnumName ではなく、直接 Method() や EnumName のように呼び出せる。

アクセシビリティ

アクセシビリティの種類には以下のようなものがある。

public どの場所からでもアクセス可能
protected クラス内部および派生クラス内からアクセス可能
internal 同じプロジェクト内からアクセス可能
protected internal protected または internal
private internal protected または internal
private 同じクラスからアクセス可能

アクセサーとプロパティ

アクセサー メンバ変数の取得/変更を行うためのメソッドのこと。
プロパティ クラス外部からはメンバ変数のように扱え、クラス内部ではメソッドのように振舞うもの。


// メンバ変数
private int propertyInt;
// プロパティ
public int PropertyInt
{
set { propertyInt = value; }
get { return propertyInt; }
}

getterとsetterにはそれぞれアクセスレベルを指定可能。

setを記述しないと読み取り専用になる。

上記コードは自動プロパティを利用して以下のように省略できる。
(privateなメンバ変数はコンパイラによって生成されるが、プログラマーが参照できるものではない)


// プロパティ
public int PropertyInt { get; set; }

また、以下のようにsetterを記述しない場合(get-only)はコンストラクタ内だけで値の変更が可能。


// プロパティ
public int PropertyInt { get; }

ただし、プロパティ初期化子を利用することでコンストラクタを書かなくても以下のように初期値を設定できる。


// プロパティ
public int PropertyInt { get; } = 10;

また、get-onlyのプロパティはexpression-bodied形式(ひとつの式)で記述できる。


// プロパティ
public int PropertyInt => 2 + 3;

継承とコンストラクタ

派生クラスのコンストラクタより先に、基底クラスのコンストラクタが呼び出される。

基底クラスのコンストラクタは自動で呼び出されるが、引数を持った基底コンストラクタを呼び出したい時は以下のように明示的に呼び出す。


Player(int id, string name, int hp) : base(id, name)
{
}

基底クラスのメンバーの隠蔽

基底クラスと派生クラスで同名のメソッドが存在する場合、基底クラスのメソッドは隠蔽されてしまう(呼び出されない)

意図せず隠蔽が行われると不都合があるため、意図的に隠蔽する場合は派生クラスのメソッド定義で「new」を付ける。
(newを付けないとコンパイラが警告を出す)


class Derived : Base
{
public new void Method()
{
// 処理
}
}

逆に隠蔽したくない場合は、派生クラスの対象メソッド内で「base.メソッド名」を使って基底クラスのメソッドを呼び出す。


class Derived : Base
{
public new void Method()
{
base.Method();  // 基底クラスのMethod()を実行
// 処理
}
}

is演算子、as演算子

bool match = 変数 is 型名;

でmatchには「変数の型が型名と一致しているか」の判定結果が入る。

型名 res = 変数 as 型名;

でresには「変数を型名に型変換した結果」が入る。

asとキャスト演算子の違いは、asの場合は型変換できなかった場合にnullが返される点。

仮想メソッド

基底クラスと派生クラスに同名のメソッドがある場合、どちらのメソッドが呼び出されるかは静的な型によって決定される。

Character chara = new Player();
chara.Move();

としたとき、呼び出されるのはCharacterクラスのMove()になる。

しかし、これでは不便な場合が多いので、仮想メソッドを使う。

基底クラスのメソッドにvirtualを付け、派生クラスのメソッドにoverrideを付ける。


class Character {
public virtual void Move(){
// 処理
}
}
class Player {
public override void Move(){
// 処理
}
}
class Sample {
void Main(){
Character chara = new Player();
chara.Move(); // PlayerクラスのMove()が呼び出される
}
}

まとめ

今日はC#(オブジェクト指向)の基礎について勉強しました。

知らなかったことがかなり多く、基礎から勉強すべきだったと強く思いました。

ということで、明日も引き続きC#の基礎を勉強したいと思います。

Commentsこの記事についたコメント

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です