メソッド (method)

引数 (arguments)

パラメータ修飾子 (parameter modifier)

メソッドの引数は既定では値渡し (Passing by Value) で、refまたはoutキーワードを指定することで参照渡し (Passing by Reference) となります。

参照型も既定で値渡しですが、それは参照しているアドレスの変更がメソッド外へ影響しないことを意味し、参照型で参照されているデータの変更は呼び出し元へ作用します。

名称が似ていますが、変数の値をコピーして渡す値渡しと、変数の値を直接格納する値型は、異なる概念です。一方で参照を渡す参照渡しと、参照を格納する参照型は近いです。

refとoutの違いは、値の割り当てを強制するタイミングにあります

既定の動作
パラメータ修飾子 引数 (イン) 戻り値 (アウト)
なし (値渡し) コピー  
ref コピー コピー
out   コピー
in修飾子

C# 7.2以降で使用可能なin修飾子を指定すると、refやout修飾子と同様に参照渡しとなります。ref修飾子と同様にメソッドに渡す前に値を割り当てなければなりませんが、メソッド内ではその値を変更できません。in パラメーター修飾子 - C# リファレンス | Microsoft Learn

引数の型による作用の違い

引数が値型で、その値を変更する
public 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
}
引数が参照型で、それに格納されているデータを変更する
public class MyClass
{
    public int num;
    public MyClass(int i)
    {
        num = i;
    }
}

public 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
}
引数が参照型で、そのアドレスを変更する
public 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で受け取った引数は、ラムダ式などの内部で使用できない

refまたはoutで受け取った引数はラムダ式などの内部では使用できないため、一時変数を介して渡します。Compiler Error CS1628 | Microsoft Learn

static void Func(ref int value)
{
    int tmp = value;
    Action action = () =>
    {
        //value++; // error CS1628: ref または out パラメーター 'value' は、匿名メソッド、ラムダ式、またはクエリ式の内部では使用できません。
        tmp++;
    };

    action();
    value = tmp;
}
プロパティとインデクサは、refやoutで渡せない

プロパティインデクサは、refやoutで渡せません。Compiler Error CS0206 | Microsoft Learn

可変引数 (variable arguments)

可変引数とする引数は、paramsキーワードを付加した1次元の配列とします。

public 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

戻り値 (return values)

戻り値の型がvoid以外のとき、returnで値を返せます。また戻り値は引数と異なり、つねに値渡し (変数のコピー) で返されます。

C# 7.0以降は参照戻り値 (ref 戻り値) とすることで、参照で返せます。ref 戻り値と ref ローカル変数 (C# ガイド) | Microsoft Learn

public ref MyClass Method()
{
    // ...
    return ref myClass;
}

複数の値を返す

戻り値はつねに1つですが、クラスや構造体でまとめることで複数の値を返せます。そのとき以下のクラスなどが有用です。

構文:複数のオブジェクトを一時的に1つにまとめるには?[C#/VB、.NET Framework 4.7以降]:.NET TIPS - @IT 山本康彦 (2018/01/17)

オーバーロード (overloading)

名前が同じだが、引数や戻り値が異なるメソッドを同一クラス内に複数定義できます。

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; }
}

演算子のオーバーロード (operator overloading)

等値演算子 (equality operator) ==

オブジェクトの等値を確認するメソッドの実装方法

変換演算子 (conversion operator)

クラスや構造体の型変換 (キャスト) の方法を定義できます。

implicit 変換演算子

暗黙的な型変換を許容します。それは明示的なキャストは不要であることを示します。

class MyClass
{
    private double val;

    public static implicit operator double(MyClass myClass)
    {
        return myClass.val;
    }
}

MyClass myClass = new MyClass();
double num = myClass; // 明示的なキャストは不要

次のいずれかの条件に合致するならば、明示的なキャストを要求するexplicitを用います。

  • 情報が失われる。たとえば小さい整数型から大きい整数型への変換など
  • 例外を投げる
explicit 変換演算子

明示的な型変換を強制します。それは明示的なキャストが必要であることを示します。

class MyClass
{
    private double val;

    public static explicit operator double(MyClass myClass)
    {
        return myClass.val;
    }
}

MyClass myClass = new MyClass();
double num = (double)myClass; // 明示的なキャストが必要

静的メソッド (static methods)

静的にすべきメソッドは、コード分析CA1822: Mark members as staticで検出できます。

拡張メソッド (extension methods)

既存の型を変更することなく、メソッドを追加できます。

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
{
    public 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

MethodBase.GetCurrentMethod() メソッド

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; // クラス名

CallerMemberNameAttribute 属性

CallerMemberName属性では呼び出し元のメンバの名前を取得できるため、この属性を付けたメソッドを呼び出すことで、呼び出し元の名前がわかります。

public string GetMethodName([System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
    return memberName;
}

メソッド情報の取得

Type.GetMethod()で任意のメソッドの情報を取得し、それを呼び出せます。

参考

参考書

Microsoft Learnから検索