C/C++から、初めてC#を触れたとき、最初に戸惑ったのが const
キーワードです。
ここではC#の const
キーワードではない、C++が持つ const
キーワードの特性を振り返りつつ、それをC#でどのように実現しコードを安全にできるかということです。
C/C++の const
キーワードは非常に強力なツールなので、C/C++で出来るセキュアな使い方が、C#の const
キーワードどこまで実現できるかの挑戦です。
ここで得られるもの
- ちょっぴりセキュアなコーディングテクニック
本日のレシピ
- C++のconstを振り返る
- C#でC++のconstを何処まで再現できるか
C++のconstを振り返ってみよう
C++において const
は値が変更されないことを示すために使用しています。
これだけ聞くとC#も同じでしょ?ってなりそうなので少しC++の const
をみていきましょう。
-
const
変数の宣言と使用const int x = 10; x = 20; // ERROR: const 変数は変更不可
-
const
ポインタの宣言と使用int a = 10; const int* p = &a; *p = 20; // ERROR: constポインタを通じての書き換え不可 a = 20; // 参照元の変数は書き換え可能
-
const
ポインタ自身の変更不可int a = 10; int b = 20; const int* p = &a; p = &b; // ERROR: constポインタ自身は変更不可
-
const
参照の宣言と使用void print(const int& x) { x = 20; // ERROR: const参照を通じての書き換え不可 } int main() { int a = 10; print(a); return 0; }
-
const
メンバ関数の宣言と使用class MyClass { public: void func() { cout << "func() が呼び出されました" << end1; } void func() const { cout << "const func() が呼び出されました" << end1; } }; int main() { MyClass obj1; const MyClass obj2; obj1.func(); // func() has been called obj2.func(); // const func() has been called return 0; }
以上がC/C++における const
の主な役割で、安全に値やクラスを渡す事が可能となっています。
C/C++のconstキーワードをもう少し掘り進めてみる
以下のマネー操作をする Money クラスがあるとき
class Money {
public:
void addMoney(int money) {
_moeny += money;
}
void useMoney(int money) {
_moeny -= money;
}
int money() cosnt {
return _money;
}
private:
int _money;
};
以下のように引数に渡した c を内部で変更することができてしまいます。
int IsEmpty(Money$ money) {
money.useMoney(10);
return money.money() <= 0 ? 1 : 0;
}
これを変更したくない場合C/C++では const
を加えるだけです
Money IsEmpty(const Money$ money) {
money.useMoney(10); // ERROR: constキーワードのついていない関数呼び出しは不可
return money.money() <= 0 ? 1 : 0;
}
もう少しパターンを見てみましょう
先程のマネーを財布が所持していたとき、そのマネーを呼び出し元が変更できないようにしたいときは以下のように戻り値に const
を付けて返せば良いだけです。
class Wallet {
public:
const Money& getMoney() {
return _money;
}
private:
Money _money;
};
returnタイプに const
を付けるだけでPalletクラスはマネーを変更される心配をする必要なく操作させることができます。
const Money$ money = player.getMoney();
money.setMoney(0, 0, 0, 0); // ERROR: constキーワード付き変数のためコンパイルエラー
しかし残念なことにC#ではこういった const
は存在しませんでした。定数定義に対してのみ const
を使用することが可能です。
C#で再現してみる
先程のMoneyとWalletの関係性を参考にC#でWalletの持つMoneyを操作可能なものと、操作不可なものとをインターフェイスを利用して設計してみます。
まずWalletからMoneyの取得だけを取り出した IReadOnlyWallet を作成します
interface IReadOnlyWallet {
int Money { get; }
}
次に IWallet は IReadOnlyWallet を継承したMoneyを設定可能なインターフェイスを定義します
interface IWallet : IReadOnlyWallet {
int Money { set; }
}
最後にそれらの実態クラスを実装します
class Wallet : IWallet {
public Money Money { get; set; }
}
これらを利用すれば
void OutputConsole(IReadOnlyWallet wallet) {
Debug.Log($"Has {wallet.Money}.");
}
というように get にのみアクセス可能になります。
戻り値の場合も同様
class Bank {
private Wallet[] _wallets;
public IReadOnlyWallet FirstWallet {
get => _wallets[0];
}
}
void Func() {
var bank = new Bank()
var wallet = bank.FirstWallet;
wallet.Money = 100; // ERROR: コンパイルエラー
}
と、 IReadOnlyWallet を戻り値にすることで意図しない変更を防ぐことが可能となります。
より良い意見、似たような事してるけど違うアプローチ、違うテクニックなどコメントお待ちしております。