メソッドの引数は既定では値渡し (Passing by Value) で、refまたはoutキーワードを指定することで参照渡し (Passing by Reference) となります。
参照型も既定で値渡しですが、それは参照しているアドレスの変更がメソッド外へ影響しないことを意味し、参照型で参照されているデータの変更は呼び出し元へ作用します。
名称が似ていますが、変数の値をコピーして渡す値渡しと、変数の値を直接格納する値型は、異なる概念です。一方で参照を渡す参照渡しと、参照を格納する参照型は近いです。
refとoutの違いは、値の割り当てを強制するタイミングにあります。
パラメータ修飾子 | 引数 (イン) | 戻り値 (アウト) |
---|---|---|
なし (値渡し) | コピー | |
ref | コピー | コピー |
out | コピー |
C# 7.2以降で使用可能なin修飾子を指定すると、refやout修飾子と同様に参照渡しとなります。ref修飾子と同様にメソッドに渡す前に値を割り当てなければなりませんが、メソッド内ではその値を変更できません。in パラメーター修飾子 - C# リファレンス | Microsoft Learn
static void Val(int a, ref int b, out int c) { a++; b++; // c++; // error CS0269: 未割り当ての out パラメーター 'c' が使用されました。 c = 5; } static void Main() { int i1 = 10; int i2 = 10; int i3 = 10; Val(i1, ref i2, out i3); Console.Write(i1); // 10 (値型はパラメータ修飾子がないと、呼び出し元へは作用しない) Console.Write(i2); // 11 Console.Write(i3); // 5 }
class MyClass { public int num; public MyClass(int i) { num = i; } } static void Ref1(MyClass a, ref MyClass b, out MyClass c) { a.num++; b.num++; // c.num++; // error CS0269: 未割り当ての out パラメーター 'c' が使用されました。 c = new MyClass(5); } static void Main() { MyClass c1 = new MyClass(10); MyClass c2 = new MyClass(10); MyClass c3 = new MyClass(10); Ref1(c1, ref c2, out c3); Console.Write(c1.num); // 11 (参照型はパラメータ修飾子がなくても、参照しているデータの変更は呼び出し元へ作用する) Console.Write(c2.num); // 11 Console.Write(c3.num); // 5 }
static void Ref2(object a, ref object b, out object c) { a = null; b = null; c = null; } static void Main() { object o1 = new object(); object o2 = new object(); object o3 = new object(); Ref2(o1, ref o2, out o3); bool r1 = (o1 == null); // false (パラメータ修飾子がないと、参照しているアドレスの変更は呼び出し元へ作用しない) bool r2 = (o2 == null); // true bool r3 = (o3 == null); // true }
引数のクラスの派生クラスとなるインスタンスは、refやoutを付加して参照渡しできません。c# - Why doesn't 'ref' and 'out' support polymorphism? - Stack Overflow
class BaseClass {} class SubClass : BaseClass {} class Program { static void M1( BaseClass a) {} static void M2(ref BaseClass a) {} static void M3(out BaseClass a) {a = null;} static void Main(string[] args) { BaseClass baseClass = new BaseClass(); M1(baseClass); M2(ref baseClass); M3(out baseClass); SubClass subClass = new SubClass(); M1(subClass); M2(ref subClass); // error CS1503: 引数 1: は 'ref SAMPLE.SubClass' から 'ref SAMPLE.BaseClass' へ変換することはできません。 M3(out subClass); // error CS1503: 引数 1: は 'out SAMPLE.SubClass' から 'out SAMPLE.BaseClass' へ変換することはできません。 } }
refまたはoutで受け取った引数はラムダ式などの内部では使用できないため、一時変数を介して渡します。Compiler Error CS1628 | Microsoft Learn
void Method(ref int value)
{
int tmp = value;
Action action = () =>
{
//value++; // error CS1628: ref または out パラメーター 'value' は、匿名メソッド、ラムダ式、またはクエリ式の内部では使用できません。
tmp++;
};
action();
value = tmp;
}
プロパティとインデクサは、refやoutで渡せません。Compiler Error CS0206 | Microsoft Learn
非同期メソッドには、refやoutを指定できません。CS1988 - 参照パラメーター修飾子に関連付けられているエラーと警告 - C# reference | Microsoft Learn
可変引数とする引数は、paramsキーワードを付加した1次元の配列とします。
void Method(params int[] list) { }
Method(1, 2, 3);
Method(new int[] { 1, 2, 3 }); // 配列でも渡せる
たとえばString.Format()では、次のように用いられています。
public static string Format (string format, params object[] args);Format(String, Object[]) - String.Format Method (System) | Microsoft Learn
paramsは最後の引数でなければならず、これに違反すると「params パラメーターは、仮パラメーター リストの最後のパラメーターでなければなりません。」としてCS0231でエラーとなります。
引数の定義に既定値 (default value) を指定しておくことで、呼び出し時にその引数を省略できるようになります。これはC++のデフォルト引数に相当します。省略可能な引数 - 名前付き引数と省略可能な引数 - C# | Microsoft Learn
C# 4.0以降で使用可能です。名前付き引数と省略可能な引数 (C# プログラミング ガイド) | Microsoft Learn
void Method(int a, int b = 1, int c = 2) { }
Method(0); Method(0, 5);
既定値が指定されていない引数は省略できません。
Method(); // error CS7036: 'a' の必要なパラメーター 'Method(int, int, int)' に対応する特定の引数がありません
末尾から順に省略する必要があります。先行する引数を省略するときは、名前付き引数を用います。
Method(0, ,10); // error CS0839: 引数がありません Method(0, c: 10); // ok
対応する引数を、順番ではなく名前で指定できます。名前付き引数 - 名前付き引数と省略可能な引数 - C# | Microsoft Learn
C# 4.0以降で使用可能です。名前付き引数と省略可能な引数 (C# プログラミング ガイド) | Microsoft Learn
void Method(int a, int b, int c) { }
Method(a: 0, b: 1, c: 2);
Method(b: 0, c: 1, a: 2); // 記述する順番は問題とならない
名前で指定するならば、すべての引数に指定するようにします。一部の名前を省略したとき、順番が正しくないとエラーとなります。
Method(0, b: 1, c: 2); // 順番が正しければ、一部の名前を省略しても問題ない Method(a: 0, b: 1, 2); Method(b: 0, c: 1, 2); // error CS8323: 名前付き引数 'b' の場所が正しくありません。後ろに名前なし引数があります Method(0, c: 1, a: 2); // error CS1744: 名前付き引数 'a' は、場所引数が既に指定されているパラメーターを指定します
戻り値の型がvoid以外のとき、returnで値を返せます。また戻り値は引数と異なり、つねに値渡し (変数のコピー) で返されます。
C# 7.0以降は参照戻り値 (ref 戻り値) とすることで、参照で返せます。ref 戻り値と ref ローカル変数 (C# ガイド) | Microsoft Learn
ref MyClass Method()
{
// ...
return ref myClass;
}
戻り値はつねに1つですが、クラスや構造体でまとめることで複数の値を返せます。そのとき以下のクラスなどが有用です。
名前が同じだが、引数や戻り値が異なるメソッドを同一クラス内に複数定義できます。
class MyClass
{
int Method(int a) { return 1; }
int Method(double a) { return 1; }
double Method(double a) { return 1; } // error CS0111: 型 'MyClass' は、'Method' と呼ばれるメンバーを同じパラメーターの型で既に定義しています。
double Method(float a) { return 1; }
}
クラスや構造体の型変換 (キャスト) の方法を定義できます。
暗黙的な型変換を許容します。それは明示的なキャストは不要であることを示します。
class MyClass
{
private double val;
public static implicit operator double(MyClass myClass)
{
return myClass.val;
}
}
MyClass myClass = new MyClass();
double num = myClass; // 明示的なキャストは不要
次のいずれかの条件に合致するならば、明示的なキャストを要求するexplicitを用います。
明示的な型変換を強制します。それは明示的なキャストが必要であることを示します。
class MyClass
{
private double val;
public static explicit operator double(MyClass myClass)
{
return myClass.val;
}
}
MyClass myClass = new MyClass();
double num = (double)myClass; // 明示的なキャストが必要
静的にすべきメソッドは、コード分析のCA1822: Mark members as staticで検出できます。
既存の型を変更することなく、メソッドを追加できます。
namespace Name1 { // 拡張対象のクラス class MyClass1 { } } namespace Name2 { static class MyClass2 // 拡張メソッドを定義するクラスは、静的でなければならない { // 拡張メソッド。拡張対象の型を第1引数に指定する public static void Method(this Name1.MyClass1 a, int b) { Console.Write(b); } } }
これを用いる側では、次のようにします。
using Name2; // 拡張メソッドが含まれる名前空間を指定する namespace Sample { class Program { static void Main(string[] args) { Name1.MyClass1 myClass1 = new Name1.MyClass1(); // MyClass1でMethod()が定義されているかのように呼び出せる myClass1.Method(10); // 実際には静的メソッドのため、次のようにも呼び出せる MyClass2.Method(myClass1, 20); } } }
この方法で拡張できるのはメソッドのみで、プロパティやイベントはクラスを継承した上で追加します。
コードから実行位置のメソッド名を取得するには、次のような方法があります。c# - How to get the name of the current method from code - Stack Overflow
GetCurrentMethod()は現在実行しているメソッドの情報を返すため、それからメソッド名を取得できます。
string name = System.Reflection.MethodBase.GetCurrentMethod().Name;
このとき同時にクラス名も取得できます。
System.Reflection.MethodBase m = System.Reflection.MethodBase.GetCurrentMethod(); string methodName = m.Name; // メソッド名 string className = m.ReflectedType.Name; // クラス名
CallerMemberName属性では呼び出し元のメンバの名前を取得できるため、この属性を付けたメソッドを呼び出すことで、呼び出し元の名前がわかります。
string GetMethodName([System.Runtime.CompilerServices.CallerMemberName] string memberName = "") { return memberName; }
Type.GetMethod()で任意のメソッドの情報を取得し、それを呼び出せます。