ref (C# リファレンス)
ref
キーワードは、変数が参照、または別のオブジェクトの別名であることを示します。 これは 5 つの異なるコンテキストで使用されます。
- メソッド シグネチャとメソッドの呼び出しで、参照によってメソッドに引数を渡します。 詳細については、「参照渡しで引数を渡す」を参照してください。
- メソッド シグネチャで、参照渡しで呼び出し元に値を返します。 詳細については、参照戻り値に関するページを参照してください。
- メンバーの本文で、参照戻り値が、呼び出し元によって変更される参照としてローカルに格納されることを示します。 または、ローカル変数から別の値へのアクセスが参照渡しで行われることを示します。 詳細については、「ref ローカル変数」を参照してください。
struct
の宣言で、ref struct
またはreadonly ref struct
を宣言します。 詳細については、「ref struct
」の記事を参照してください。ref struct
宣言で、フィールドが参照であることを宣言します。 フィールドref
に関する記事を参照してください。
参照渡しで引数を渡す
メソッドのパラメーター リストで使用した場合、ref
キーワードは、引数を値ではなく、参照によって渡すことを示します。 ref
キーワードは、仮パラメーターを引数 (変数にする必要があります) の別名にします。 つまり、パラメーターに対するすべての操作は引数に対して行われます。
たとえば、呼び出し元からローカル変数式または配列要素のアクセス式が渡されるとします。 また、呼び出されるメソッドでは、ref パラメーターが参照するオブジェクトを置き換えることができます。 その場合は、メソッドから制御が戻ったとき、呼び出し元のローカル変数または配列要素によって参照されるのは、新しいオブジェクトとなります。
注意
参照渡しで渡すという概念と参照型の概念とを混同しないでください。 2 つの概念は同じではありません。 メソッドのパラメーターは、値型か参照型かどうかに関係なく、ref
によって変更できます。 参照渡しで渡される場合、値型はボックス化されません。
ref
パラメーターを使用するには、メソッド定義と呼び出し元のメソッドの両方が、次の例に示すように ref
キーワードを明示的に使用する必要があります。 (例外として、COM 呼び出しを行う場合は、呼び出し元のメソッドで ref
を省略できます)。
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
ref
または in
パラメーターに渡す引数は、渡す前に初期化する必要があります。 この要件は、引数を渡す前に明示的に初期化する必要がない out パラメーターの場合とは異なります。
クラスのメンバーは、ref
、in
、または out
のみが異なるシグネチャを持つことはできません。 1 つの型の 2 つのメンバー間の唯一の違いが、1 つには ref
パラメーターが存在し、もう 1 つには out
または in
パラメーターが存在することである場合、コンパイラ エラーが発生します。 たとえば、次のコードはコンパイルされません。
class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}
ただし、次の例に示すように、1 つのメソッドに ref
、in
または out
パラメーターがあり、もう 1 つには値渡しされるパラメーターがある場合、メソッドをオーバーロードすることができます。
class RefOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(ref int i) { }
}
非表示やオーバーライドなど、シグネチャの一致が必要な他の状況では、in
、ref
、out
はシグネチャの一部であり、互いに一致しません。
プロパティは変数ではありません。 それらはメソッドであり、ref
パラメーターに渡すことはできません。
次の種類のメソッドには、ref
、in
、out
キーワードを使用することはできません。
- async 修飾子を使用して定義した Async メソッド。
- yield return または
yield break
ステートメントを含む Iterator メソッド。
拡張メソッドには、これらのキーワードの使用に関する制限もあります。
- 拡張メソッドの最初の引数では、
out
キーワードを使用できません。 - 拡張メソッドの最初の引数が構造体ではない場合、または構造体として制約されていないジェネリック型である場合、その引数で
ref
キーワードを使用することはできません。 - 最初の引数が構造体である場合を除き、
in
キーワードは使用できません。 ジェネリック型では、構造体として制約されている場合であっても、in
キーワードを使用することはできません。
参照渡しで引数を渡す:使用例
前の例は、参照によって値型を渡す例でした。 ref
キーワードを使用して、参照渡しで参照型を渡すこともできます。 参照型を参照渡しで渡すと、呼び出されたメソッドは、参照パラメーターが呼び出し元で参照するオブジェクトに置換できます。 オブジェクトの格納場所は、参照パラメーターの値としてメソッドに渡されます。 パラメーターの格納場所の値を変更する場合は (新しいオブジェクトをポイント)、呼び出し元が参照する格納場所を変更することもできます。 次の例では、参照型のインスタンスを ref
パラメーターとして渡します。
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}
public string ItemName { get; set; }
public int ItemID { get; set; }
}
private static void ChangeByReference(ref Product itemRef)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 99999);
// You can change the value of one of the properties of
// itemRef. The change happens to item in Main as well.
itemRef.ItemID = 12345;
}
private static void ModifyProductsByReference()
{
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
// Pass the product instance to ChangeByReference.
ChangeByReference(ref item);
System.Console.WriteLine("Back in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
// This method displays the following output:
// Original values in Main. Name: Fasteners, ID: 54321
// Back in Main. Name: Stapler, ID: 12345
参照型を値渡しまたは参照渡しで渡す方法の詳細については、「参照型パラメーターの引き渡し」を参照してください。
参照戻り値
参照戻り値 (または ref 戻り値) は、メソッドから呼び出し元に参照渡しで返される値です。 つまり、呼び出し元はメソッドによって返される値を変更することができ、その変更は呼び出されたメソッド内のオブジェクトの状態に反映されます。
参照戻り値は ref
キーワードを使用して以下に定義されます。
- メソッド シグネチャ。 たとえば、次のメソッド シグネチャは、
GetCurrentPrice
メソッドが参照渡しで Decimal 値を返すことを示しています。
public ref decimal GetCurrentPrice()
- メソッドの
return
ステートメントで返される変数とreturn
トークンの間。 次に例を示します。
return ref DecimalArray[0];
呼び出し元がオブジェクトの状態を変更するには、ref ローカル変数として明示的に定義した変数に参照戻り値を格納する必要があります。
次に、メソッド シグネチャとメソッド本体の両方を示す、より完全な ref 戻り値の例を示します。
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}
呼び出されるメソッドでは、値が参照渡しで値が返されるように戻り値を ref readonly
として宣言し、返された値を呼び出し元のコードで変更できないようにすることもできます。 呼び出し元のメソッドでは、ローカルの ref readonly 変数に値を格納することで、返された値のコピーを回避できます。
例については、「ref 戻り値と ref ローカル変数の使用例」を参照してください。
ref ローカル変数
ref ローカル変数は、return ref
を使用して返された値を参照するために使用します。 ref ローカル変数は、初期化して ref 戻り値以外の値にすることができません。 言い換えると、初期化の右側は参照にする必要があります。 ref ローカル変数の値に変更を加えると、参照渡しの値を返すメソッドのオブジェクトの状態に反映されます。
次の 2 つの場所で ref
キーワードを使用して、ref ローカルを定義します。
- 変数宣言の前。
- 参照渡しで値を返すメソッドの呼び出しの直前。
たとえば、次のステートメントは、GetEstimatedValue
という名前のメソッドによって返される ref ローカル変数を定義しています。
ref decimal estValue = ref Building.GetEstimatedValue();
同じ方法で、参照渡しの値にアクセスできます。 場合によっては、参照渡しの値へのアクセスによって負荷がかかる可能性があるコピー操作が回避され、パフォーマンスが向上します。 たとえば、次のステートメントは、値の参照に使用される ref ローカル値をどのように定義するかを示しています。
ref VeryLargeStruct reflocal = ref veryLargeStruct;
どちらの例も、ref
キーワードは両方の位置で使用する必要があります。そうしないと、コンパイラ エラー CS8172 "値を使用して参照渡し変数を初期化することはできません" が生成されます。
foreach
ステートメントの反復変数を ref ローカルまたは ref readonly ローカル変数にすることができます。 詳細については、foreach ステートメントに関する記事を参照してください。 ref 代入演算子を使用して、ref ローカルまたは ref readonly ローカル変数を再割り当てできます。
ref readonly ローカル
ref readonly ローカルは、署名に ref readonly
が含まれていて return ref
を使用するメソッドまたはプロパティにより返される値を参照する場合に使用されます。 ref readonly
変数は ref
ローカル変数のプロパティと readonly
変数の組み合わせです。それに割り当てられたストレージのエイリアスであり、変更できません。
ref 戻り値と ref ローカル変数の使用例
次の例は、Title
と Author
という 2 つの String フィールドを持つ Book
クラスを定義しています。 また、Book
オブジェクトのプライベート配列を含む BookCollection
クラスも定義しています。 個々のブック オブジェクトは、GetBookByTitle
メソッドを呼び出すことによって参照渡しで返されます。
public class Book
{
public string Author;
public string Title;
}
public class BookCollection
{
private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
};
private Book nobook = null;
public ref Book GetBookByTitle(string title)
{
for (int ctr = 0; ctr < books.Length; ctr++)
{
if (title == books[ctr].Title)
return ref books[ctr];
}
return ref nobook;
}
public void ListBooks()
{
foreach (var book in books)
{
Console.WriteLine($"{book.Title}, by {book.Author}");
}
Console.WriteLine();
}
}
呼び出し元が GetBookByTitle
によって返される値を ref ローカル変数として格納する場合、呼び出し元が戻り値に加えた変更が BookCollection
オブジェクトに反映されます。次の例を参照してください。
var bc = new BookCollection();
bc.ListBooks();
ref var book = ref bc.GetBookByTitle("Call of the Wild, The");
if (book != null)
book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
// Call of the Wild, The, by Jack London
// Tale of Two Cities, A, by Charles Dickens
//
// Republic, The, by Plato
// Tale of Two Cities, A, by Charles Dickens
ref フィールド
ref struct
型では、ref
フィールドであるフィールドを宣言できます。 ref
フィールドは、参照が参照先のオブジェクトよりも長くならないようにするために、ref struct
型でのみ有効です。 この機能により、System.Span<T> のような型が有効になります。
public readonly ref struct Span<T>
{
internal readonly ref T _reference;
private readonly int _length;
// Omitted for brevity...
}
Span<T>
型は、連続する要素にアクセスする参照を格納します。 参照を使用すると、Span<T>
オブジェクトが参照するストレージのコピーを作成しないようにすることができます。
C# 言語仕様
詳細については、「C# 言語の仕様」を参照してください。 言語仕様は、C# の構文と使用法に関する信頼性のある情報源です。