C#: 高度な手法
1. メソッドのオーバーロード
メソッドのオーバーロードにより、パラメータリストが異なれば、同じクラス内で同じ名前のメソッドを複数定義することができます。パラメータリストが異なるということは、パラメータの数、型、または順序が異なることを意味します。戻り値の型は、オーバーロードの解決には関与しません。
▶ サンプル
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));
}
}
3
4
6
2. オプションパラメータと名前付きパラメータ
オプションのパラメータは、メソッド宣言内のパラメータにデフォルト値を割り当てます。これらのパラメータは、メソッドを呼び出す際に省略することができます。名前付きパラメータは、位置順に従う必要なく、パラメータ名を指定することで値を渡します。
▶ サンプル
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");
}
}
Hello
Hi
Hi
Hi
> World
>> Test
>> Test
3. ref パラメータ修飾子
キーワード ref は、パラメータを参照渡しで渡します。つまり、メソッド内でパラメータに変更が加わると、呼び出し元の変数にもその変更が反映されます。この変数は呼び出し前に初期化しておく必要があり、また、呼び出し箇所でもキーワード ref を使用する必要があります。
▶ サンプル
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}");
}
}
Before: x=10, y=20
After: x=20, y=10
4. out パラメータ修飾子
out キーワードも参照渡しですが、メソッドは out パラメータに値を代入する必要があり、呼び出し前に変数を初期化する必要はありません。これは、メソッドが複数の値を返す必要がある場合に一般的に使用されます。
▶ サンプル
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");
}
}
}
Parsed: 123
Parse failed
out パラメータは、メソッドが戻る前に代入する必要があります。C# 7 以降では、呼び出し箇所で直接変数を宣言できるようになりました。
5. in パラメータ修飾子
in キーワードは、パラメータを参照渡し(読み取り専用)で渡します。つまり、メソッドは in パラメータの値を変更することはできません。これは、コピーによるオーバーヘッドを回避しつつ、意図しない変更を防ぐために、大きな構造体を渡す際に役立ちます。
▶ サンプル
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);
}
}
A=100, B=200
A=100, B=200
ℹ️ 注: 呼び出し箇所では
inキーワードを省略できます。コンパイラが自動的に参照渡しを行います。
6. params 変数引数
params キーワードを使用すると、メソッドは可変個の引数を受け取ることができます。コンパイラはこれらの引数を配列にまとめます。メソッドに params パラメータを指定できるのは 1 つだけであり、かつパラメータリストの最後にある必要があります。
▶ サンプル
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());
}
}
6
150
0
params は1次元配列でのみ使用でき、メソッドが持つ params パラメータは最大1つまでです。
7. 再帰の原理と代表的な例
再帰とは、メソッドが自分自身を呼び出すプログラミングの手法です。すべての再帰には終了条件(基本ケース)が必要です。そうでないと、スタックオーバーフローが発生してしまいます。
(1) 階乗
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)}");
}
}
}
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
(2) フィボナッチ数列
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();
}
}
0 1 1 2 3 5 8 13 21 34 55
8. ローカル関数
C# 7 ではローカル関数が導入され、メソッド内で他のメソッドを定義できるようになりました。ローカル関数は、それを含むメソッド内でのみアクセス可能であり、特定のメソッド内でのみ使用されるヘルパーロジックを抽出するのに役立ちます。
▶ サンプル
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));
}
}
4, 16, 36, 64
9. 変数のスコープ
変数のスコープは、その変数がどこからアクセスできるかを決定します。C# には、主に 3 つのスコープレベルがあります。ブロックスコープ(if/for/while ブロック内)、メソッドスコープ(メソッド内で定義されたローカル変数)、およびクラススコープ(クラス内で定義されたフィールド)です。
▶ サンプル
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();
}
}
classField=100, localVar=10
blockVar=20
classField=100
✅ ルール: 同じメソッド内で、同じ名前のローカル変数を宣言することはできません。内側のコードブロックからは外側の変数にアクセスできますが、外側のブロックからは内側のブロックの変数にアクセスすることはできません。
❓ よくある質問
📖 まとめ
- メソッドのオーバーロード:名前は同じだが、引数が異なる。戻り値の型は関係しない。
- オプションのパラメータ:宣言時にデフォルト値を指定し、必須のパラメータの後に記述する必要があります
- 名前付きパラメータ:パラメータ名で値を渡し、位置指定の制約を解除する
- ref: 参照渡し。呼び出す前に初期化する必要があります。
- out: 参照渡し。メソッド内で代入を行う必要がある。呼び出し前に初期化を行う必要はない。
- in: 参照渡しによる読み取り専用。変更を防止し、コピーを回避する
- params: 引数の数は可変で、配列としてコンパイルされる。引数は1つしか指定できず、かつ最後でなければならない。
- 再帰:関数が自分自身を呼び出すこと。終了条件が必要であり、パフォーマンスやスタックオーバーフローに注意が必要である。
- ローカル関数:メソッド内で定義されるメソッド。C# 7以降で導入された機能で、ラムダ式よりもオーバーヘッドが低い
- 変数のスコープ:ブロック < メソッド < クラス;内側の変数は外側の変数にアクセスできるが、外側の変数は内側の変数にアクセスできない
📝 練習問題
- 2つのint、2つのdouble、および3つのintを受け取り、それぞれ積を返す、
Multiplyのオーバーロードされたメソッドを3つ記述してください。 - メソッド
void Log(string msg, string level = "INFO", bool timestamp = true)を作成し、名前付きパラメータの順序を変えて、少なくとも3回呼び出してください。 - 引数として渡された整数を1増やす
void Increment(ref int value)メソッドを作成し、Main内でその変数が実際に変更されていることを確認してください。 - b が 0 の場合は false を返し、それ以外の場合は out を通じて除算の結果を返す
bool TryDivide(int a, int b, out double result)メソッドを作成してください。 - すべての引数の平均を計算し、空の配列の場合も適切に処理する
double Average(params double[] values)メソッドを作成してください。 - 1+2+3+…+n を計算する再帰的なメソッドを実装し、その結果を反復処理による実装の結果と比較してください。
- 数値が素数であるかどうかを判定するために内部でローカル関数を使用し、1から50までのすべての素数を返すメソッドを作成してください。