in/out/ref 引数を理解しよう!

[C#] in/out/ref 引数を理解しよう!

※ 当サイトは広告を含みます。

ここではC#のin/out/ref引数について学びます。
これはメソッド(関数)で利用する特殊な引数になります。

値型と参照型の理解

この仕組みを理解するためには値型参照型の動作をしっかりと把握する必要があります。
特に両者の値渡し時の挙動を理解してないと、全く意味が分からないと思います。

管理人

という事で、復習が必要な人はこちらをどうぞ。

in引数

inを付けた引数は読み取り専用であることを意味します。つまり書き換え不可能です。
要はメソッドの引数にinが付いてる場合、メソッド内で値が変更されないことが保証されます。

正確には値の変更を試みるとビルドエラーとなり、そもそも実行できなくなります。
以下はサンプルコードです。単に対象となる引数の手前にinを付けます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      Method(10);
    }

    // inが付いた引数は読み取り専用
    public static void Method(in int x)
    {
      // 値を変更しようとするとビルドエラー
      x = 100;

      Console.WriteLine(x);
    }
  }
}
管理人

当たり前ですが、参照型にinを付けても参照値が読み取り専用になるだけです。

りさ

つまり参照先の値は書き換えできるんですよね?

管理人

そうだよ。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      var a = new int[] { 0, 1, 2 };

      Console.WriteLine(a[0]);
      Console.WriteLine(a[1]);
      Console.WriteLine(a[2]);

      Method(a);

      Console.WriteLine(a[0]);
      Console.WriteLine(a[1]);
      Console.WriteLine(a[2]);
    }

    public static void Method(in int[] a)
    {
      // 参照値は書き換えできない
      //a = null;

      a[0] = 100; // 参照先は書き換えできる
      a[1] = 200; //   〃
      a[2] = 300; //   〃
    }
  }
}

out引数

単語的にはinの反対ですが意味はかなり違います。
なんとoutを付けた引数は出力、つまりメソッドからの戻り値に近い扱いを受けます。

りさ

どういうこと?

管理人

他に表現する方法がなかった。後でサンプルコードを見て覚えよう。

これは別に戻り値が増えてる訳ではないです。そもそもメソッドって引数は無限なのに戻り値は最大1個です。
戻り値をクラス化する等、複数の値を返す方法は色々ありますが、outはその種類の最も簡単なやつです。

管理人

実際、「複数の値を返したいなぁ」って思うことありませんか? その願い簡単に叶いますよ。

以下がサンプルコードです。inと同じように引数の手前にoutを付けます。
なお、inと異なり呼び出し時もoutの記述が必要となります。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int x;

      // 呼び出し時もoutの記述が必要
      Method(out x);

      Console.WriteLine(x);
    }

    public static void Method(out int x)
    {
      // ここで値を変えると元の変数にも影響する
      x = 100;
    }
  }
}

イメージは参照型です。先程の例だと値型を渡してるのに、元の変数にまで影響が及んでます。
これは本来の値渡しではありえない挙動ですよね。ぱっとみ出力が増えてるとも言えます。

この仕組みですが、いくつか制約があります。

  1. outで渡せるのは変数のみ
  2. outで受け取った引数は必ず代入が必要

1は受け取りたいので当たり前、2は受け取ったメソッド側の話です。
この時、以下のようなout引数に代入をしない記述はビルドエラーになります。


public static void Method(out int x)
{
  // メソッドを抜けるまでにout引数に代入しない
}
管理人

値を出力するための記述なので未初期化は駄目ってこと。

同時に変数宣言

制約の1つに変数で渡す必要があることを伝えましたが、逆に言えば渡す変数を必ず作る必要があります。
そんな時に簡易的に記述する方法があり、以下のように記述するとout引数の利用と同時に変数が宣言できます。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // 同時に変数を作る
      Method(out var x);

      Console.WriteLine(x);
    }

    public static void Method(out int x)
    {
      x = 100;
    }
  }
}

呼び出す時に型名を記述するだけです。ここは明示的な型を書いてもいいけど基本的にはvarを使います。


Method(out 型名 変数名);

これ見た目から分かりづらいですが、変数のスコープは最初の記述と同じになります。
つまり、見た目はfor文とかの変数宣言に似てますが、以下のような記述は同名のビルドエラーになります。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // 同時に変数を作る
      Method(out var x);

      // 同スコープなので無理
      int x;

      Console.WriteLine(x);
    }

    public static void Method(out int x)
    {
      x = 100;
    }
  }
}

ref引数

refとはreference(リファレンス)、つまり参照って意味です。なんとrefが付いた引数は参照型になります。
より正確には参照渡しと呼ばれる機能でメソッドに引数が渡ります。なので参照型になっちゃったわけじゃないです。

以下がサンプルコードです。in/outと同じように引数の手前にrefを付けます。
また、outと同様に呼び出し時もrefの記述が必要となります。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      int x = 0;

      // 呼び出し時もrefの記述が必要
      Method(ref x);

      Console.WriteLine(x);
    }

    public static void Method(ref int x)
    {
      // ここで値を変えると元の変数にも影響する
      x = 100;
    }
  }
}

ぱっとみ結果はoutと変わりませんが、こちらは値型の引数をメソッド内で加工する時とかに使います。
このref引数にも制約があり、out同様に満たせない場合はビルドエラーになります。

  1. 変数しか渡せない
  2. 渡す変数は初期化が必須

1はそうじゃないと成り立たないから、2は未初期化の引数を渡すのはNGってことです。


namespace Sample
{
  internal class Program
  {
    static void Main(string[] args)
    {
      // 未初期化は駄目
      int x;

      // 呼び出し時もrefの記述が必要
      Method(ref x);

      Console.WriteLine(x);
    }

    public static void Method(ref int x)
    {
      // ここで値を変えると元の変数にも影響する
      x = 100;
    }
  }
}

TipsC++には参照渡しと呼ばれる機能があって凄く似てます。それをC#的に実装した機能がこれです(たぶん)。

値渡しと参照渡しの理解

先程のout引数やref引数は参照渡しになります。そしてC#には値型と参照型があります。
つまり、2 x 2全4パターンの渡し方が存在するわけですが、その中にどう考えても不要なものがあります。

例えば参照型のref引数です。結論として意味を成しません。
これは仕様的には作れるんですが、そうすることにメリットがないです。

管理人

値型と参照型の挙動が理解できてれば何となく分かるはず。

あとがき

個人的にC#で1番大切なのは値型参照型の理解だと思ってます。
この話も値型参照型が理解できてる人なら、すんなり学べたのではないでしょうか?

◆ C#に関する学習コンテンツ

この記事は参考になりましたか?

関連記事

コメント

この記事へのコメントはありません。