C#: 高度な手法

1. メソッドのオーバーロード

メソッドのオーバーロードにより、パラメータリストが異なれば、同じクラス内で同じ名前のメソッドを複数定義することができます。パラメータリストが異なるということは、パラメータの数、型、または順序が異なることを意味します。戻り値の型は、オーバーロードの解決には関与しません。

▶ サンプル

CSHARP
using System;

class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public double Add(double a, double b)
    {
        return a + b;
    }

    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        Console.WriteLine(calc.Add(1, 2));
        Console.WriteLine(calc.Add(1.5, 2.5));
        Console.WriteLine(calc.Add(1, 2, 3));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
3
4
6
💡 ヒント: 戻り値の型が異なるだけではオーバーロードとはみなされず、コンパイラはエラーを報告します。


2. オプションパラメータと名前付きパラメータ

オプションのパラメータは、メソッド宣言内のパラメータにデフォルト値を割り当てます。これらのパラメータは、メソッドを呼び出す際に省略することができます。名前付きパラメータは、位置順に従う必要なく、パラメータ名を指定することで値を渡します。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Print(string msg, int count = 1, string prefix = "")
    {
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine($"{prefix}{msg}");
        }
    }

    static void Main()
    {
        Print("Hello");
        Print("Hi", 3);
        Print(msg: "World", prefix: "> ");
        Print(prefix: ">> ", count: 2, msg: "Test");
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Hello
Hi
Hi
Hi
> World
>> Test
>> Test
⚠️ 注: オプションのパラメータは、すべての必須パラメータの後に指定する必要があります。


3. ref パラメータ修飾子

キーワード ref は、パラメータを参照渡しで渡します。つまり、メソッド内でパラメータに変更が加わると、呼び出し元の変数にもその変更が反映されます。この変数は呼び出し前に初期化しておく必要があり、また、呼び出し箇所でもキーワード ref を使用する必要があります。

▶ サンプル

CSHARP
using System;

class Program
{
    static void Swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

    static void Main()
    {
        int x = 10;
        int y = 20;
        Console.WriteLine($"Before: x={x}, y={y}");
        Swap(ref x, ref y);
        Console.WriteLine($"After: x={x}, y={y}");
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Before: x=10, y=20
After: x=20, y=10

4. out パラメータ修飾子

out キーワードも参照渡しですが、メソッドは out パラメータに値を代入する必要があり、呼び出し前に変数を初期化する必要はありません。これは、メソッドが複数の値を返す必要がある場合に一般的に使用されます。

▶ サンプル

CSHARP
using System;

class Program
{
    static bool TryParseInt(string s, out int result)
    {
        result = 0;
        foreach (char c in s)
        {
            if (c < '0' || c > '9')
                return false;
            result = result * 10 + (c - '0');
        }
        return true;
    }

    static void Main()
    {
        if (TryParseInt("123", out int number))
        {
            Console.WriteLine($"Parsed: {number}");
        }

        if (!TryParseInt("abc", out int fail))
        {
            Console.WriteLine("Parse failed");
        }
    }
}
▶ 試してみよう
TEXT 📖 参照専用
Parsed: 123
Parse failed
📌 重要なポイント: out パラメータは、メソッドが戻る前に代入する必要があります。C# 7 以降では、呼び出し箇所で直接変数を宣言できるようになりました。


5. in パラメータ修飾子

in キーワードは、パラメータを参照渡し(読み取り専用)で渡します。つまり、メソッドは in パラメータの値を変更することはできません。これは、コピーによるオーバーヘッドを回避しつつ、意図しない変更を防ぐために、大きな構造体を渡す際に役立ちます。

▶ サンプル

CSHARP
using System;

struct BigData
{
    public long A, B, C, D, E, F, G, H;
}

class Program
{
    static void Process(in BigData data)
    {
        Console.WriteLine($"A={data.A}, B={data.B}");
    }

    static void Main()
    {
        BigData d = new BigData { A = 100, B = 200 };
        Process(in d);
        Process(d);
    }
}
▶ 試してみよう
TEXT 📖 参照専用
A=100, B=200
A=100, B=200

ℹ️ 注: 呼び出し箇所では in キーワードを省略できます。コンパイラが自動的に参照渡しを行います。


6. params 変数引数

params キーワードを使用すると、メソッドは可変個の引数を受け取ることができます。コンパイラはこれらの引数を配列にまとめます。メソッドに params パラメータを指定できるのは 1 つだけであり、かつパラメータリストの最後にある必要があります。

▶ サンプル

CSHARP
using System;

class Program
{
    static int Sum(params int[] numbers)
    {
        int total = 0;
        foreach (int n in numbers)
        {
            total += n;
        }
        return total;
    }

    static void Main()
    {
        Console.WriteLine(Sum(1, 2, 3));
        Console.WriteLine(Sum(10, 20, 30, 40, 50));
        Console.WriteLine(Sum());
    }
}
▶ 試してみよう
TEXT 📖 参照専用
6
150
0
⚠️ 注: params は1次元配列でのみ使用でき、メソッドが持つ params パラメータは最大1つまでです。


7. 再帰の原理と代表的な例

再帰とは、メソッドが自分自身を呼び出すプログラミングの手法です。すべての再帰には終了条件(基本ケース)が必要です。そうでないと、スタックオーバーフローが発生してしまいます。

(1) 階乗

CSHARP
using System;

class Program
{
    static long Factorial(int n)
    {
        if (n <= 1)
            return 1;
        return n * Factorial(n - 1);
    }

    static void Main()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"{i}! = {Factorial(i)}");
        }
    }
}
TEXT 📖 参照専用
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

(2) フィボナッチ数列

CSHARP
using System;

class Program
{
    static int Fibonacci(int n)
    {
        if (n <= 0) return 0;
        if (n == 1) return 1;
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }

    static void Main()
    {
        for (int i = 0; i <= 10; i++)
        {
            Console.Write($"{Fibonacci(i)} ");
        }
        Console.WriteLine();
    }
}
TEXT 📖 参照専用
0 1 1 2 3 5 8 13 21 34 55
🔥 パフォーマンスに関する注意: フィボナッチ数列に対する単純な再帰は、多くの冗長な計算を伴い、時間計算量は O(2^n) となります。本番環境では、反復処理やメモ化を用いて最適化してください。


8. ローカル関数

C# 7 ではローカル関数が導入され、メソッド内で他のメソッドを定義できるようになりました。ローカル関数は、それを含むメソッド内でのみアクセス可能であり、特定のメソッド内でのみ使用されるヘルパーロジックを抽出するのに役立ちます。

▶ サンプル

CSHARP
using System;

class Program
{
    static int[] GetEvenSquares(int[] numbers)
    {
        bool IsEven(int n) => n % 2 == 0;
        int Square(int n) => n * n;

        System.Collections.Generic.List```<int>``` result = new System.Collections.Generic.List```<int>```();
        foreach (int n in numbers)
        {
            if (IsEven(n))
            {
                result.Add(Square(n));
            }
        }
        return result.ToArray();
    }

    static void Main()
    {
        int[] data = { 1, 2, 3, 4, 5, 6, 7, 8 };
        int[] result = GetEvenSquares(data);
        Console.WriteLine(string.Join(", ", result));
    }
}
▶ 試してみよう
TEXT 📖 参照専用
4, 16, 36, 64
💡 ヒント: ローカル関数は、その関数が定義されているメソッドのローカル変数をキャプチャすることができ、ラムダ式よりもオーバーヘッドが低くなります。


9. 変数のスコープ

変数のスコープは、その変数がどこからアクセスできるかを決定します。C# には、主に 3 つのスコープレベルがあります。ブロックスコープ(if/for/while ブロック内)、メソッドスコープ(メソッド内で定義されたローカル変数)、およびクラススコープ(クラス内で定義されたフィールド)です。

▶ サンプル

CSHARP
using System;

class ScopeDemo
{
    int classField = 100;

    void MethodA()
    {
        int localVar = 10;
        Console.WriteLine($"classField={classField}, localVar={localVar}");

        if (true)
        {
            int blockVar = 20;
            Console.WriteLine($"blockVar={blockVar}");
        }
    }

    void MethodB()
    {
        Console.WriteLine($"classField={classField}");
    }
}

class Program
{
    static void Main()
    {
        ScopeDemo demo = new ScopeDemo();
        demo.MethodA();
        demo.MethodB();
    }
}
▶ 試してみよう
TEXT 📖 参照専用
classField=100, localVar=10
blockVar=20
classField=100

ルール: 同じメソッド内で、同じ名前のローカル変数を宣言することはできません。内側のコードブロックからは外側の変数にアクセスできますが、外側のブロックからは内側のブロックの変数にアクセスすることはできません。

❓ よくある質問

Q メソッドのオーバーロードは、戻り値の型のみに基づいて行うことはできますか?
A いいえ、コンパイラは呼び出し箇所での引数に基づいてメソッドをマッチングさせます。戻り値の型はシグネチャの一部ではありません。
Q ref と out の根本的な違いは何ですか?
A ref は呼び出し前に初期化が必要ですが、out はメソッド内で代入を行う必要があります。ref は変更可能な既存の値を渡すのに対し、out は通常、追加の結果を返すために使用されます。
Q params の引数は null になることがありますか?
A はい、params は本質的に配列です。null を渡しても自動的に空の配列が作成されるわけではないため、メソッド内では null チェックを行う必要があります。
Q 再帰は反復処理よりも常に遅いのでしょうか?
A 必ずしもそうとは限りませんが、再帰にはスタックの深さの制限や呼び出しのオーバーヘッドがあります。深い再帰を行うと、StackOverflowException が発生する可能性があります。
Q ローカル関数とラムダ式の違いは何ですか?
A ローカル関数は、デリゲートのオーバーヘッドがなく、ジェネリックや ref/out をサポートする本格的なメソッドです。一方、ラムダ式は、余分なメモリ割り当てを伴うデリゲートや式ツリーを生成します。

📖 まとめ

📝 練習問題

  1. 2つのint、2つのdouble、および3つのintを受け取り、それぞれ積を返す、Multiplyのオーバーロードされたメソッドを3つ記述してください。
  2. メソッド void Log(string msg, string level = "INFO", bool timestamp = true) を作成し、名前付きパラメータの順序を変えて、少なくとも3回呼び出してください。
  3. 引数として渡された整数を1増やすvoid Increment(ref int value)メソッドを作成し、Main内でその変数が実際に変更されていることを確認してください。
  4. b が 0 の場合は false を返し、それ以外の場合は out を通じて除算の結果を返す bool TryDivide(int a, int b, out double result) メソッドを作成してください。
  5. すべての引数の平均を計算し、空の配列の場合も適切に処理する double Average(params double[] values) メソッドを作成してください。
  6. 1+2+3+…+n を計算する再帰的なメソッドを実装し、その結果を反復処理による実装の結果と比較してください。
  7. 数値が素数であるかどうかを判定するために内部でローカル関数を使用し、1から50までのすべての素数を返すメソッドを作成してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%