変数の渡し方 ref out in

# はじめに

C#における、参照まわりの機能が充実してきたので、
変数の渡し方について自分なりにまとめてみました。

## C#の機能

変数の渡し方について、関連しそうな機能をピックアップしてみます。

- C#1.0
    - `ref` 引数 『参照引数』
    - `out` 引数 『出力参照引数』
- C#4.0
    - オプション引数
- C#7.0
    - `ref` 変数
    - `ref` 戻り値
    - `out` 変数宣言
- C#7.2
    - `in` 引数 『入力参照引数』
    - `ref readonly` 変数
    - `ref readonly` 戻り値
    - `readonly` 構造体
- C#7.3
    - `ref`ローカル変数の再割り当て

:::note info
当記事執筆以降に追加された機能をいくつか挙げておきます。
- C#11
    - `ref` 構造体の `ref` フィールド
    - `scoped` 修飾子
- C#12
    - `ref readonly` 引数

一部の機能について、記事内にコメントを入れています。 
:::


## 引数の型

引数に指定する、`ref`,`out`はC#の初期からある機能なので今更ですが、C#7.2より`in`が指定できるようになりました。

||評価|値の変更|参照の変更|備考|
|:--|:--|:--|:--|:--|
|(なし)|値渡し|可|－|
|`ref`|参照渡し|可|可|
|`out`|参照渡し|可／必須|可|未初期化の変数を渡せる|
|`in`|参照渡し|不可|可|

```csharp
void DoSomething(in int x1, ref int x2, out int x3) {
    // x1 = 1; // 書けない
    x2 = 2; // 書ける（参照先=呼び出し元の値が書き換わる）
    x3 = 3; // 書かなくてはならない（参照先=呼び出し元の値が書き換わる）
}

void DoSomethingIn(in int x1, in int x2) {
    x1 = ref x2; // 参照先=呼び出し元には影響しないので書ける
}

void DoSomethingOut(out int x1, out int x2) {
    x1 = 1;
    x2 = 2;
    x1 = ref x2;
    x1 = 3;
    // 呼び出し元には、x1=1, x2=3 が出力(out)される
}
// in引数はオプション引数にできる
void DoSomethingIn(in int x1 = default) {
}
```

`in`は、主に大きな構造体を扱う場合の「コピー」を避けるために使われます。
「読み取り専用」の引数だと思うと、少し奇妙な動作をするので注意が必要。以下の理由などから、単に**引数を読み取り専用にしたいだけの目的では使用しない**ほうが良いと思います。

- `in` の有無でシグネチャが変わる (影響大!)
- `ref` をつけた参照の再代入は可能

:::note info
C#11では、参照引数および参照変数に `scoped` 修飾できるようになりました。
参照のスコープがこのメソッド内に限られることを指定します。
:::

:::note info
C#12では、`ref readonly` 修飾できるようになりました。
`in` と似た挙動をしますが、一時変数や式の受け取りを拒否します。(呼び出し元に参照があることを強制します。)
:::

## 戻り値の型

C#7.0で `ref`が、C#7.2で `ref readonly`が、それぞれ戻り値の型に指定できるようになりました。


| |評価|備考|
|:--|:--|:--|
|(なし)|値返し| |
|`ref`|参照返し|参照先の値を変更できる|
|`ref readonly`|参照返し|参照先の値を変更できない|

```csharp
ref int DoReturnRef(in int x1, ref int x2, out int x3) {
    x3 = 1;

    int a = 1;
    ref int b = ref a;
    // return ref b; // これは返せない

    //if (x1 > x2 && x1 > x3) {
    //    return ref x1; // 読み取り専用の参照(in)を読み取り可能な参照(ref)では返せない
    //}
    if (x2 > x3) {
        return ref x2;
    }
    return ref x3;
}
ref readonly int DoReturnRefReadonly(in int x1, ref int x2, out int x3) {
    x3 = 1;

    int a = 1;
    ref readonly int b = ref a;
    // return ref b; // これは返せない

    if (x1 > x2 && x1 > x3) {
        return ref x1;
    }
    if (x2 > x3) {
        return ref x2;
    }
    return ref x3;
}

void Do() {
    int a = 1, b = 2;
    DoReturnRef(in a, ref b, out int c) = 100;
}

```

スコープを辿って問題が無いかどうかはコンパイラがチェックしてくれますので、メモリを破壊するような参照にはなりません。
ただ、`ref`する階層が深くなるとロジックを追いづらくなるので、そこは要注意。
気を付けて使えば、ロジックをよりスマートに書くこともできると思います。

:::note info
C#12では、引数を `in` の代わりに　`ref readonly` で修飾することで、`ref readonly` 戻り値が参照であることを保証できます。
:::

## 変数の型

C#7.0 より、変数の型に`ref`を、 C#7.2 より、変数の型に`ref readonly`を付けることができます。
`ref`,`ref readonly`に指定された変数は、「参照」として扱われます。

||評価|値の変更|参照の変更|備考|
|:--|:--|:--|:--|:--|
|(なし)|値|可|－|
|`readonly`|値|不可|－|ローカル変数には指定不可|
|`ref`|参照|可|可|
|`ref readonly`|参照|不可|可|

```csharp
int a = 1, b = 2;

ref readonly int x = ref a;
x = ref b;
// x = 100; // これはNG

ref int y = ref a;
y = ref b;
y = b;

// readonly int z = a; // これもNG
```

ローカル変数では、`ref readonly` とは書けるけれども、`readonly`とは書けない不思議な状態となっています。

## どんな時に使うか

1. 大きな構造体に対して、 `ref readonly` `in` を使用する
    - 併用：対象の構造体は `readonly`構造体とする。
    - 効果：構造体全体のコピーが発生しないため、高速化できる。
2. `ref`戻り値のプロパティやインデクサを持たせたクラスを使用する
    - 効果：複雑な構造、条件のオブジェクトの書き換えを簡単にできる、かも。

上記二つは、設計思想的には割と対極で、

1. 『構造体をきちんとカプセル化してimmutableにする』
2. 『カプセル化を緩くして外部からの中身の書き換えをしやすくする』

なのですが、どちらも『結果として処理の負荷を軽減する』ための使い方だと思います。

## まとめ

C#7.xの機能は使いどころが難しいものが多いですが、きちんと理解しておくことで必要な場面で選択肢として思い浮かべられるようにしたいところです。