はじめに
「カタカナを使わずに理解する」C#のクラスとインスタンスについてが思っていたより反響があったので、第二弾です。
今回は解説というより、作り方に対して思うところがあるのでつらつらと書いていきます。
コンストラクタについては
- DI
- カプセル化
- アクセス修飾子
- ゲッター、セッター
と前提知識多めなのでうまく記事がまとまるかわかりませんが、調べるきっかけになればと思うのでわざと分けずに書いてしまいます。
コメントが付いたら分けます。
もちろん、わかる範囲で書けるところは「カタカナを使わずに」書いていきたいと思います。
コンストラクタってなんだよ
先に結論。
インスタンスを作ったときに一番最初走る処理 です。
基本的にこれだけです。
一番解説に使われるのは初期化処理でしょうか。
例えば、先の記事で使ったHumanクラスを使って書いてみると
public class Human
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime Birthday { get; set; }
//コンストラクタ
public Human()
{
//ここに初期化処理を書いたりする
Age = 0;
}
}
ここで、「public string Age; //ここにstringのデフォルト値 0」ってインスタンス作るときに書いてたじゃんと思った人。
ご明察。
初期化(初期値の代入)なら
public class Human
{
public string Name { get; set; } = "匿名";
public int Age { get; set; } = 0;
public DateTime Birthday { get; set; } = DateTime.Now;
}
で、事足ります。
ただ、ここにカプセル化とかいうオブジェクト指向の設計の考え方がやってきます。
設計にうるさい厄介おじさんが出たときに、対処できるようにさらっておきましょう。
カプセル化ってなんだよ
カプセル化とは、データ(変数)と、そのデータを操作する処理(メソッド)をひとまとめにして、外部から直接触れないようにする
Rubyの記事ですが、言ってることは変わりません。
例えば
public class Human
{
public string Name = "おじさん(諸説あり)";
public int Age = 50;
public DateTime Birthday = DateTime.Now.AddYear(-50);
public bool Over20 => Age >= 20;
}
こういうおじさんがいたとしましょう。
50歳については適当です。男は生涯少年です。
で、こんなメソッドがあったとしましょう
public class Program
{
public void SelfIntroduction()
{
var human = new Human();
Console.WriteLine($"私の名前は{human.Name}です。 年齢は{human.Age}です。);
}
}
本来であれば
私の名前はおじさん(諸説あり)です。 年齢は50です。
となるわけです。
ところが、このおじさん。
血迷ったか20歳サバを読みたくなったとします。
public class Program
{
public void SelfIntroduction()
{
var human = new Human();
human.Age -= 20;
Console.WriteLine($"私の名前は{human.Name}です。 年齢は{human.Age}です。);
}
}
とすると当然
私の名前はおじさん(諸説あり)です。 年齢は30です。
となるわけですね。
これを許すまじとするのがカプセル化です。
Humanを改造して読み取り専用クラスにします。
public class Human
{
public string Name { get; private set; } = "おじさん(諸説あり)";
public int Age { get; private set; } = 50;
public DateTime Birthday { get; private set; } = DateTime.Now.AddYear(-50);
}
こうすると読み取り専用になります。
ちなみに{ get; set; }のことを自動実装プロパティって言います。
getがゲッター、値を参照したときに起動するやつです。
setがセッター、値を代入したときに起動するやつです。
うまいこと使うと、setで1足されたことを受け取って、Totalみたいな他プロパティに自動で+1するみたいな処理も作れます。
private setってなんだよ
set入ってるけど値をセットできないってどういうことだよ
ってお思いの方、私もそう思います。
アクセス修飾子ってなんだよ
前述のprivateをアクセス修飾子って言います。
他にpublic,internal,protectedがいます。
| アクセス修飾子 | 範囲 | こういう時に使う |
|---|---|---|
private |
クラス内だけ有効 | 門外不出のプロパティやそのクラスでしか使わないメソッドとか(何回も通るけどクラス化するほどでもないやつとか) |
protected |
基底、派生クラス内 | 使ったことない。EF Coreのマッピング処理とかに使われてる。 |
internal |
同じアセンブリ内 | あんまり使ってない。publicも書きたくないくらいモチベがない時に使う。バリバリのPGだったら処理別にプロジェクトを分けてるらしい。そこではよく使ってるイメージ |
public |
なんでもOK | みんな大好き |
めちゃくちゃ怒られそうですが、正直privateとpublicしか使ってません。
protectedは使う以前に継承とかいう淘汰された技術依存の話ですし、
internalは前述の表のとおり細かくプロジェクトを分けるとかをしない限りは必要ありません。
internalについては同じアセンブリと書きましたが、内包しているdllファイルとかはそのクラスを触れません。exeも然りです。
プロジェクトがどうのこうの言ってるのはそのせいですね。
ゲームとかサードパーティ製のDPSチェッカーとかが数値をとりに行けるのはアクセス修飾子がpublicになっているからですね!
さて、本題に戻りまして。
private setについて、privateは前述のとおりです。
同じクラス内では触れる
ということなので、同じクラスに値を変更するように書いてみましょうか。
Q.どうやって?
A.コンストラクタで。
public class Human
{
public string Name { get; private set; } = "おじさん(諸説あり)";
public int Age { get; private set; } = 50;
public DateTime Birthday { get; private set; } = DateTime.Now.AddYear(-50);
public Human(string name, int age, DateTime birthday)
{
Name = name;
Age = age;
Birthday = birthday;
}
}
こうすることでインスタンスを作るときに引数で流してやれば、引数を初期値とした読み取り専用のインスタンスができるわけです。
値を書き換えるなら転生しろ、ということですね。
public class Program
{
public void SelfIntroduction()
{
var human = new Human("お兄さん(諸説あり)", 30, DateTime.Now.AddYear(-30));
Console.WriteLine($"私の名前は{human.Name}です。 年齢は{human.Age}です。);
}
}
public class Human
{
public string Name { get; private set; }
public int Age { get; private set; }
public DateTime Birthday { get; private set; }
public Human(string name, int age, DateTime birthday)
{
Name = name;
Age = age;
Birthday = birthday;
}
}
これで
私の名前はお兄さん(諸説あり)です。 年齢は30です。
となります。
おわりに
いろいろ脱線しましたが、単にコンストラクタは最初に起動するだけというわけでもないことが分かったでしょうか。
調べていく中でのショートカットになれば幸いです。
まだ書いてませんがここに邪悪なDIというのがやってきます。
次はそれでも書いてみますかね。