.NET開発者のためのブログメディア
C#のref・out・inについてわかりやすく解説!ref・out・inの違いと制約

- SE
- C#のref・out・inは何が違うのですか。
- PM
- 呼び出す側での初期化やメソッドでの値の代入の点で違いがあります。
目次
C#のref・out・inとは?
C#にはrefやoutやinというキーワードがありますが、初心者の方はよく知らない人も多いでしょう。この記事はref・out・inについて分かりやすく解説するので是非ご覧ください。
C#でメソッドに値を渡して、その値を操作して結果を取得する場合、以下のように行うのが普通です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
using System;
class Program
{
public static void Main()
{
int sum = AddValue(3, 4);
Console.WriteLine(sum);
}
public static int AddValue(int a, int b)
{
return a + b;
}
}
|
refでフィールドをメソッドに渡すことができる
上の結果は7になります。これを「値渡し」と言います。基本的にこのやり方でプログラミングを行っていて問題はありません。ただ、フィールドをメソッドに渡してそちらで値を操作したい時もあるかもしれません。その場合はどうしたらよいのでしょうか。
それを実現するのがrefです。以下のC#サンプルのように使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
using System;
class Program
{
public static void Main()
{
int sum = 0;
AddValue(3, 4, ref sum);
Console.WriteLine(sum);
}
public static void AddValue(int a, int b, ref int c)
{
c = a + b;
}
}
|
refで参照渡しが可能になる
上のC#サンプルを実行すると結果は7になります。前のサンプルと変わった所は、AddValueメソッドに3つ目のパラメータの「ref int c」が追加され、戻り値をreturnで返さずそのcにaとbを加算した結果を代入するようになっています。
AddValueを呼び出す側では、加算した結果を入れる変数のsumをrefキーワードを付けてAddValueに渡しています。これでAddValue側でsumに値が代入されます。この渡し方を「参照渡し」と言います。
フィールドを初期化せずに参照渡しするには?
上のC#サンプルではまずsumを0で初期化していますが、その後にAddValueで値が更新されるので無意味に感じます。しかしsumの宣言を「int sum;」とすると、「ref sum」の箇所で未割り当てのローカル変数が使用されましたというエラーになります。
初期化しないで参照渡しをする方法はないのでしょうか?それを可能にするのが、以下のC#サンプルで使用しているoutです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
using System;
class Program
{
public static void Main()
{
int sum;
AddValue(3, 4, out sum);
Console.WriteLine(sum);
}
public static void AddValue(int a, int b, out int c)
{
c = a + b;
}
}
|
outで初期化をせず参照渡しができる
上のC#サンプルは今までのサンプルのinをoutに変更して、sumの初期化を無くしただけです。これで今までと同じように動作します。outを使えばrefと違って渡すフィールドの初期化が不要になるということですね。
ただしoutにも制約があります。渡す側の初期化が不要になる代わりに、渡されたメソッドでの初期化が必須になります。以下のようにAddValueでcに値の代入をしていない場合エラーになります。refでは渡す側で初期化していればAddValueが空でもエラーにはなりません。
1
2
3
4
|
public static void AddValue(int a, int b, out int c)
{
}
|
3つめのinの役割は?
C#にはrefとout以外にinと言うキーワードもあります。これはC# 7.2から追加されましたが、最新環境ではそのまま利用できます。以下がinを利用したC#サンプルです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
using System;
class Program
{
public static void Main()
{
int sum = 3 + 4;
DispValue(in sum);
}
public static void DispValue(in int a)
{
Console.WriteLine(a);
}
}
|
inを使えば読み取り専用にできる
上のC#サンプルではsumにinを付与してDispValueメソッドに渡し、DispValueではそれを表示しています。inを付与すると渡されたメソッドの先で値を変更できなくなります。例えば以下のようにするとエラーになります。
1
2
3
4
5
6
|
public static void DispValue(in int a)
{
a++;
Console.WriteLine(a);
}
|
inを付与すると読み取り専用になるということです。フィールドの値を渡したメソッド内で変更されたくない時に利用すれば、間違いを防ぐことができますね。
ref・out・inの違いのまとめ
ref・out・inについてまとめると以下のようになります。この3つを正しく使い分けることで、間違って値が更新されることが無いようにしましょう。
ref
呼び出す側での初期化=必須
メソッドでの値の代入=してもしなくても良い
out
呼び出す側での初期化=禁止
メソッドでの値の代入=必須
in
呼び出す側での初期化=必須
メソッドでの値の代入=禁止
ref・in・outの制約
ref・in・outには制約があります。非同期のasyncのメソッドと、yield returnを使うメソッドにはref・in・outは使えません。理由はその2つのメソッドは呼び出す時点で結果が不定であるからです。例えば以下のC#サンプルはエラーになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
using System;
using System.Collections.Generic;
class Program
{
public static void Main()
{
int value = 3;
foreach (var v in GetValues(in value))
{
Console.WriteLine(v);
}
}
public static IEnumerable<int> GetValues(in int a)
{
yield return 1 + a;
yield return 2 + a;
yield return 3 + a;
}
}
|
yield returnは行き来が発生するため使用不可
上のC#サンプルのyield returnは通常のreturnと違いそこでメソッドが終わらずに、値を返しながら最後まで進みます。
呼び出し側とメソッドでの行き来が発生するため、最初の呼び出しで結果が確定しないという理由でref・out・inを使用できません。なお上のC#サンプルの2か所のinを除去すると正しく動作し、以下のような結果になります。
4
5
6
ref・out・inの効果的な利用法
ここまで読んできてref・out・inにあまり使い道が無いように感じた人もいるかもしれません。しかしref・out・inは構造体(struct)を使う時に効果を発揮するのです。以下のC#サンプルをご覧ください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
using System;
struct TestStruct
{
public string str1;
public string str2;
public string str3;
}
class Program
{
public static void Main()
{
TestStruct ts = new TestStruct();
ts.str1 = "あいうえお";
ts.str2 = "かきくけこ";
ts.str3 = "さしすせそ";
DispTS(in ts);
}
public static void DispTS(in TestStruct ts)
{
Console.WriteLine(ts.str1);
Console.WriteLine(ts.str2);
Console.WriteLine(ts.str3);
}
}
|
ref・out・inを使えば構造体の処理で高速化できる
上のC#サンプルは構造体をinで渡しています。C#ではクラスを渡す場合は参照渡し、構造体を渡す時は値渡しになります。しかしもし構造体のサイズが大きいと、メソッドに値を渡す時に値渡しのため中身全てを渡すことになって、非常にパフォーマンスが低下します。
しかしref・out・inを使えば構造体の参照渡しができます。渡すのはアドレスだけになるためパフォーマンスが良くなるのです。なお上のサンプルの結果は以下になります。
あいうえお
かきくけこ
さしすせそ
- SE
- C#のref・out・inのそれぞれの違いや制約についてよく分かりました。
- PM
- C#のref・out・inはあるケースでは必須の機能ですので、それぞれの違いをしっかり理解して、活用してください。
C#のref・out・inを活用しよう
C#のref・out・inについて活用しましたがご理解頂けましたでしょうか。構造体を利用する時にパフォーマンス改善のために必要になるので、是非活用してください。
Search キーワード検索
Popular 人気の記事
-
【VB.NET入門】DataGridViewの使い方まとめ
公開: 更新:
reccomended おすすめ記事
-
【.NETが統合】.NET 5の概要と今後のリリース予定
公開: 更新:
Categories 連載一覧
Tags タグ一覧
Jobs 新着案件
-
開発エンジニア/東京都品川区/【WEB面談可】/在宅ワーク
月給29万~30万円東京都品川区(大崎駅) -
遠隔テストサービス機能改修/JavaScript/東京都港区/【WEB面談可】/テレワーク
月給45万~60万円東京都港区(六本木駅) -
病院内システムの不具合対応、保守/東京都豊島区/【WEB面談可】/テレワーク
月給30万~30万円東京都豊島区(池袋駅) -
開発/JavaScript/東京都豊島区/【WEB面談可】/テレワーク
月給50万~50万円東京都豊島区(大塚駅) -
債権債務システム追加開発/東京都文京区/【WEB面談可】/在宅勤務
月給62万~67万円東京都文京区(後楽園駅)