メソッド (method)

引数 (arguments)

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

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

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

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

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

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

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

引数が値型で、その値を変更する
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' へ変換することはできません。
    }
}

可変引数 (variable arguments)

public static string Format (string format, params object[] args);
Format(String, Object[]) - String.Format Method (System) | Microsoft Docs

戻り値 (Return Values)

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

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

オーバーロード (Overloading)

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

class MyClass
{
    int    Func(int    a) { return 1; }
    int    Func(double a) { return 1; }
    double Func(double a) { return 1; } // error CS0111: 型 'MyClass' は、'Func' と呼ばれるメンバーを同じパラメーターの型で既に定義しています。
    double Func(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; // 明示的なキャストは不要

次の2つの条件を満たせないならば、明示的なキャストを要求する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; // 明示的なキャストが必要

拡張メソッド (Extension Methods)

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

namespace Name1
{
    // 拡張対象のクラス
    class MyClass1
    {
    }
}

namespace Name2
{
    static class MyClass2
    {
        // 拡張メソッド。拡張対象の型を第1引数に指定する
        public static void Method(this Name1.MyClass1 a, int b)
        {
            Console.WriteLine(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;
}

参考

参考書

MSDN (Microsoft Developer Network) から検索