0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C/C++でいうところのconstをC#で。

Posted at

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 をみていきましょう。

  1. const 変数の宣言と使用
    const int x = 10;
    x = 20; // ERROR: const 変数は変更不可
    
  2. const ポインタの宣言と使用
    int a = 10;
    const int* p = &a;
    *p = 20;  // ERROR: constポインタを通じての書き換え不可
    a = 20;   // 参照元の変数は書き換え可能
    
  3. const ポインタ自身の変更不可
    int a = 10;
    int b = 20;
    const int* p = &a;
    p = &b; // ERROR: constポインタ自身は変更不可
    
  4. const 参照の宣言と使用
    void print(const int& x) {
      x = 20;  // ERROR: const参照を通じての書き換え不可
    }
    int main() {
      int a = 10;
      print(a);
      return 0;
    }
    
  5. 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; }
}

次に IWalletIReadOnlyWallet を継承した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 を戻り値にすることで意図しない変更を防ぐことが可能となります。

より良い意見、似たような事してるけど違うアプローチ、違うテクニックなどコメントお待ちしております。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?