C#の関数とは?メソッドの書き方・呼び出し方・引数と戻り値を初心者向けに解説

はじめに

C#でプログラムを作成するとき、ほとんどの場面で使用するのが「関数」です。関数を使うと、複数の処理をひとまとまりにし、必要な場所から繰り返し呼び出せます。

ただし、C#では関数に相当するものを「メソッド」と呼ぶのが一般的です。そのため、C#を学び始めたばかりの方は、「関数とメソッドは何が違うのか」「引数や戻り値はどのように書けばよいのか」と迷うことがあります。

この記事では、C#の関数であるメソッドについて、基本的な書き方や呼び出し方、引数、戻り値を初心者向けに解説します。後半では、オーバーロードやジェネリックメソッド、ラムダ式、非同期メソッドといった応用的な機能も紹介します。

1. C#の関数とは

1-1. 関数は処理をひとまとまりにしたもの

関数とは、特定の処理をひとまとまりにして、名前を付けたものです。

例えば、画面にメッセージを表示する処理を関数としてまとめると、次のように記述できます。

static void ShowMessage(){Console.WriteLine("こんにちは");}

この例では、ShowMessageという名前の関数に、文字列を表示する処理をまとめています。

定義した関数は、次のように名前を指定して呼び出します。

ShowMessage();

同じ処理を何度も記述する代わりに関数を呼び出せるため、コードの重複を減らせます。

1-2. C#では関数を「メソッド」と呼ぶことが多い

一般的なプログラミング用語では、処理をまとめたものを関数と呼びます。一方、C#では、クラスや構造体などの型に属する関数を「メソッド」と呼びます。

次のAddは、Calculatorクラスに定義されたメソッドです。

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

C#において「関数を作る」「関数を呼び出す」という表現が使われる場合、その多くはメソッドの定義や呼び出しを意味します。

この記事でも、一般的な説明では「関数」という言葉を使いつつ、C#の構文を説明する場面では「メソッド」と表記します。

1-3. 関数を使うメリット

C#で関数を使う主なメリットは、次のとおりです。

  • 同じ処理を繰り返し利用できる

  • コードの重複を減らせる

  • 処理の内容を名前で表現できる

  • プログラムを機能ごとに分割できる

  • 修正する場所を限定しやすい

  • テストやデバッグを行いやすい

例えば、税込価格を計算する処理を複数の場所で使う場合、毎回計算式を書くと修正漏れが発生する可能性があります。

static decimal CalculateTaxIncludedPrice(decimal price){return price * 1.1m;}

計算処理をメソッドとしてまとめておけば、税率や計算方法を変更するときも、基本的にはメソッドの中だけを修正すれば済みます。

1-4. 関数とメソッドの違い

関数とメソッドの主な違いは、特定の型やオブジェクトに属しているかどうかです。

一般に、どのクラスにも属さず独立して定義されるものを関数、クラスやオブジェクトに属するものをメソッドと呼びます。

C#では、通常のメソッドはクラスや構造体などの中に定義します。

class Greeting{public void SayHello(){Console.WriteLine("こんにちは");}}

このSayHelloGreetingクラスに属しているため、厳密には関数ではなくメソッドです。

ただし、プログラミング学習や日常的な会話では、メソッドを含めて関数と表現することも珍しくありません。C#を学ぶ際は、「C#における関数の多くはメソッドとして定義される」と理解しておくとよいでしょう。

2. C#の関数(メソッド)の基本的な書き方

2-1. メソッドを定義する基本構文

C#でメソッドを定義するときの基本構文は、次のとおりです。

アクセス修飾子 staticの有無 戻り値の型 メソッド名(引数){処理}

実際のコードでは、次のようになります。

public static int Add(int a, int b){int result = a + b;return result;}

それぞれの要素には、次の役割があります。

  • public:どこから呼び出せるかを指定するアクセス修飾子

  • static:インスタンスを作らずに呼び出せることを示す

  • int:戻り値の型

  • Add:メソッド名

  • int a, int b:メソッドが受け取る引数

  • { }:実行する処理を記述するメソッド本体

  • return result:呼び出し元へ値を返す処理

すべてのメソッドでpublicstaticが必要なわけではありません。目的に応じて指定する内容が変わります。

2-2. アクセス修飾子の役割

アクセス修飾子は、メソッドをどこから呼び出せるかを制御するためのキーワードです。

よく使うアクセス修飾子には、次のものがあります。

アクセス修飾子主な意味
publicどこからでもアクセスできる
private同じクラスの内部からのみアクセスできる
protected同じクラスまたは派生クラスからアクセスできる
internal同じアセンブリ内からアクセスできる
protected internal同じアセンブリ内、または派生クラスからアクセスできる
private protected同じアセンブリ内の派生クラスからアクセスできる

クラス内部だけで使用するメソッドは、通常privateにします。

class UserService{public void RegisterUser(string name){ValidateName(name);Console.WriteLine($"{name}を登録しました");}
private void ValidateName(string name){if (string.IsNullOrWhiteSpace(name)){throw new ArgumentException("名前を入力してください");}}

}

外部から呼び出す必要がないメソッドをprivateにすると、意図しない利用を防ぎやすくなります。

なお、クラスのメンバーにアクセス修飾子を書かなかった場合、通常はprivateとして扱われます。ただし、読みやすさを高めるために明示することもあります。

2-3. staticの意味と使い方

staticを付けたメソッドは、クラスのインスタンスを作らずに呼び出せます。

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

呼び出すときは、クラス名の後ろにメソッド名を指定します。

int result = Calculator.Add(10, 20);Console.WriteLine(result);

一方、staticを付けないメソッドはインスタンスメソッドです。呼び出すには、先にクラスのインスタンスを作成します。

class Greeting{public void SayHello(){Console.WriteLine("こんにちは");}}
Greeting greeting = new Greeting();greeting.SayHello();

staticメソッドは、特定のオブジェクトが持つ状態に依存しない処理に向いています。例えば、数値計算や文字列変換などが代表例です。

2-4. 戻り値の型を指定する方法

メソッドが呼び出し元へ値を返す場合は、メソッド名の前に戻り値の型を指定します。

static int GetNumber(){return 100;}

文字列を返す場合はstringを指定します。

static string GetMessage(){return "処理が完了しました";}

真偽値を返す場合はboolを指定します。

static bool IsAdult(int age){return age >= 18;}

指定した型と、returnで返す値の型は一致していなければなりません。

static int GetNumber(){return 10;}

戻り値が不要な場合は、型の代わりにvoidを指定します。

static void ShowMessage(){Console.WriteLine("メッセージを表示します");}

2-5. メソッド名を付けるときのルール

C#のメソッド名には、通常PascalCaseを使用します。PascalCaseとは、単語の先頭を大文字にしてつなげる命名方法です。

CalculateTotalPrice()GetUserName()SaveFile()SendEmail()

メソッド名には、処理内容が分かる動詞や動詞句を使用すると読みやすくなります。

例えば、次のような名前は処理の目的を理解しやすいでしょう。

GetUser()CalculateAverage()ValidatePassword()DeleteFile()

一方、次のような抽象的すぎる名前は避けるのが一般的です。

Do()Run()Process()Method1()

C#の識別子には、英字、数字、アンダースコアなどを使用できますが、数字から始めることはできません。

// 使用できるGetUserName()Calculate2Values()_saveData()

// 使用できない2CalculateValues()

また、classreturnなどの予約語は、そのままメソッド名にできません。先頭に@を付ければ識別子として使用できますが、特別な理由がなければ避けたほうが分かりやすいでしょう。

2-6. メソッド本体に処理を記述する方法

メソッドで実行したい処理は、波かっこ{ }の中に記述します。

static void IntroduceUser(string name, int age){string message = $"名前は{name}です。";Console.WriteLine(message);Console.WriteLine($"年齢は{age}歳です。");}

メソッド本体には、変数の宣言、計算、条件分岐、繰り返し処理、ほかのメソッドの呼び出しなどを記述できます。

static void ShowEvenNumbers(int max){for (int i = 1; i <= max; i++){if (i % 2 == 0){Console.WriteLine(i);}}}

ただし、1つのメソッドに多くの役割を詰め込みすぎると、読みづらく修正しにくいコードになります。処理が長くなった場合は、複数のメソッドに分割することが大切です。

3. C#の関数を呼び出す方法

3-1. staticメソッドを呼び出す方法

staticメソッドは、基本的に「クラス名.メソッド名」の形式で呼び出します。

class Calculator{public static int Multiply(int a, int b){return a * b;}}
int result = Calculator.Multiply(4, 5);Console.WriteLine(result);

.NETが提供するConsole.WriteLineMath.Maxも、staticメソッドの例です。

Console.WriteLine("こんにちは");

int max = Math.Max(10, 20);Console.WriteLine(max);

同じクラスの内部からstaticメソッドを呼ぶ場合は、クラス名を省略できます。

class Program{static void Main(){ShowMessage();}

static void ShowMessage(){Console.WriteLine("メソッドを呼び出しました");}

}

3-2. インスタンスメソッドを呼び出す方法

インスタンスメソッドを呼び出すには、newを使ってクラスのインスタンスを作成します。

class Person{public void Introduce(){Console.WriteLine("はじめまして");}}
Person person = new Person();person.Introduce();

インスタンスメソッドは、オブジェクトごとのデータを使用できます。

class Person{public string Name { get; set; } = "";

public void Introduce(){Console.WriteLine($"私は{Name}です");}

}

Person person1 = new Person { Name = "田中" };Person person2 = new Person { Name = "佐藤" };

person1.Introduce();person2.Introduce();

同じIntroduceメソッドを呼び出しても、インスタンスが持つNameの値によって結果が変わります。

3-3. 同じクラス内のメソッドを呼び出す方法

同じクラス内にあるメソッドは、基本的にメソッド名だけで呼び出せます。

class OrderService{public void CompleteOrder(){ValidateOrder();SaveOrder();Console.WriteLine("注文処理が完了しました");}

private void ValidateOrder(){Console.WriteLine("注文内容を確認します");}private void SaveOrder(){Console.WriteLine("注文内容を保存します");}

}

CompleteOrderの中から、同じクラスにあるValidateOrderSaveOrderを呼び出しています。

インスタンスメソッドから同じインスタンスのメソッドを呼ぶ場合は、thisを付けることもできます。

this.ValidateOrder();

ただし、呼び出し先が明確であれば、thisは省略することが一般的です。

なお、staticメソッドからインスタンスメソッドを直接呼ぶことはできません。インスタンスを作成してから呼び出す必要があります。

3-4. 別のクラスにあるメソッドを呼び出す方法

別のクラスにあるstaticメソッドは、クラス名を指定して呼び出します。

class TextUtility{public static string ToUpper(string text){return text.ToUpper();}}
string result = TextUtility.ToUpper("hello");Console.WriteLine(result);

別のクラスにあるインスタンスメソッドは、そのクラスのインスタンスを作成して呼び出します。

class MessageService{public void Send(string message){Console.WriteLine($"送信内容:{message}");}}
MessageService service = new MessageService();service.Send("お知らせです");

別のクラスから呼び出すには、対象のメソッドへアクセスできる必要があります。例えば、メソッドがprivateの場合、クラスの外部からは呼び出せません。

3-5. メソッドの戻り値を変数で受け取る方法

戻り値があるメソッドを呼び出すと、その結果を変数へ代入できます。

static int Add(int a, int b){return a + b;}
int total = Add(10, 20);Console.WriteLine(total);

戻り値は、ほかのメソッドの引数として直接使用することもできます。

Console.WriteLine(Add(10, 20));

また、式の一部として使用することも可能です。

int result = Add(10, 20) * 2;

戻り値を変数で受け取るときは、メソッドが返す型と代入先の型を一致させます。

string message = GetMessage();

型をコンパイラに推論させたい場合は、varも使用できます。

var message = GetMessage();

4. C#の関数で引数を使う方法

4-1. 引数とはメソッドに渡す値

引数とは、メソッドへ渡す値のことです。引数を使うと、呼び出すたびに異なる値を使って処理できます。

static void Greet(string name){Console.WriteLine($"こんにちは、{name}さん");}
Greet("田中");Greet("佐藤");

実行結果は次のとおりです。

こんにちは、田中さんこんにちは、佐藤さん

引数を使うことで、同じメソッドをさまざまな値に対して再利用できます。

4-2. 仮引数と実引数の違い

メソッドを定義するときに記述する変数を「仮引数」、メソッドを呼び出すときに渡す値を「実引数」と呼びます。

static void Greet(string name){Console.WriteLine($"こんにちは、{name}さん");}

Greet("田中");

この例では、次のように区別できます。

  • name:仮引数

  • "田中":実引数

仮引数は、メソッドの内部で使用する変数です。実引数として渡された値が仮引数へ設定され、メソッド内の処理で利用されます。

日常的な説明では、仮引数と実引数をまとめて「引数」と呼ぶこともあります。

4-3. 複数の引数を指定する方法

メソッドには、複数の引数を指定できます。複数の引数はカンマで区切ります。

static int CalculateTotal(int price, int quantity){return price * quantity;}

呼び出すときも、定義した順番に値を渡します。

int total = CalculateTotal(500, 3);Console.WriteLine(total);

この場合、priceには500quantityには3が渡されます。

引数ごとに異なる型を指定することも可能です。

static void ShowProduct(string name, int price, bool inStock){Console.WriteLine($"商品名:{name}");Console.WriteLine($"価格:{price}円");Console.WriteLine($"在庫あり:{inStock}");}

引数が多すぎると呼び出し方が分かりにくくなるため、必要に応じてクラスやレコードにまとめる方法も検討します。

4-4. 名前付き引数を使う方法

名前付き引数を使うと、仮引数の名前を指定して値を渡せます。

static void CreateUser(string name, int age, bool isAdmin){Console.WriteLine($"{name}, {age}, {isAdmin}");}

通常の呼び出し方は次のとおりです。

CreateUser("田中", 25, false);

名前付き引数を使う場合は、次のように記述します。

CreateUser(name: "田中", age: 25, isAdmin: false);

引数名を明示するため、それぞれの値が何を表しているのか分かりやすくなります。

また、すべてを名前付き引数にする場合は、定義順と異なる順番でも指定できます。

CreateUser(isAdmin: false, name: "田中", age: 25);

特にbool型の引数や、同じ型の引数が複数ある場合に便利です。

SendEmail(address: "user@example.com",subject: "お知らせ",sendImmediately: true);

4-5. オプション引数で初期値を設定する方法

オプション引数は、呼び出し時に省略できる引数です。メソッドの定義で初期値を指定します。

static void Greet(string name, string message = "こんにちは"){Console.WriteLine($"{message}、{name}さん");}

第2引数を省略すると、初期値の"こんにちは"が使用されます。

Greet("田中");

第2引数を指定すると、渡した値が使用されます。

Greet("田中", "こんばんは");

オプション引数は、必須引数より後ろに記述する必要があります。

static void PrintReport(string title, bool includeDate = true){// 処理}

複数のオプション引数がある場合は、名前付き引数と組み合わせると必要な項目だけ変更できます。

static void Configure(int width = 800,int height = 600,bool fullScreen = false){Console.WriteLine($"{width} × {height}, fullScreen={fullScreen}");}

Configure(fullScreen: true);

4-6. paramsで可変長引数を受け取る方法

paramsを使用すると、引数の個数が一定でないメソッドを定義できます。

static int Sum(params int[] numbers){int total = 0;

foreach (int number in numbers){total += number;}return total;

}

呼び出すときは、任意の個数の値を渡せます。

Console.WriteLine(Sum(1, 2));Console.WriteLine(Sum(1, 2, 3, 4, 5));Console.WriteLine(Sum());

配列を直接渡すことも可能です。

int[] values = { 10, 20, 30 };int total = Sum(values);

paramsを付けた引数は、引数リストの最後に配置します。また、1つのメソッドで使用できるparams引数は1つだけです。

4-7. ref・out・in引数の違いと使い方

C#では、refoutinを使って値を参照渡しできます。

通常の値型の引数は、値のコピーがメソッドへ渡されます。

static void ChangeValue(int number){number = 100;}

int value = 10;ChangeValue(value);

Console.WriteLine(value); // 10

refを使用すると、呼び出し元の変数をメソッド内で変更できます。

static void ChangeValue(ref int number){number = 100;}

int value = 10;ChangeValue(ref value);

Console.WriteLine(value); // 100

ref引数へ渡す変数は、呼び出す前に初期化が必要です。

outも呼び出し元の変数へ値を設定するために使用します。ただし、呼び出す前に変数を初期化する必要はなく、メソッド内では必ず値を代入しなければなりません。

static bool TryParseAge(string text, out int age){return int.TryParse(text, out age);}

if (TryParseAge("25", out int age)){Console.WriteLine($"年齢は{age}歳です");}

inは参照渡しを行いつつ、メソッド内で値を変更させたくない場合に使用します。

static int Calculate(in int value){return value * 2;}

それぞれの主な違いは、次のとおりです。

キーワード呼び出し前の初期化メソッド内での代入主な用途
ref必要可能既存の値を読み書きする
out不要必須メソッドから値を受け取る
in必要原則不可値を変更せず参照渡しする

refoutを多用すると、どこで値が変更されたのか追いにくくなることがあります。単純な結果を返すだけなら、通常の戻り値やタプルも検討しましょう。

5. C#の関数で戻り値を返す方法

5-1. return文の基本的な使い方

メソッドから値を返すには、return文を使用します。

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

呼び出し元では、返された値を変数へ代入できます。

int result = Add(3, 5);Console.WriteLine(result);

戻り値の型としてintを指定した場合、returnではintとして扱える値を返す必要があります。

static string GetUserName(){return "田中";}

returnが実行されると、その時点でメソッドの処理は終了します。そのため、returnより後ろにある同じブロック内の処理は実行されません。

5-2. 戻り値がない場合はvoidを指定する

呼び出し元へ値を返さないメソッドでは、戻り値の型にvoidを指定します。

static void ShowMessage(){Console.WriteLine("処理を実行しました");}

voidメソッドは、値を返さず、表示、保存、送信、状態の変更などを行うときに使用します。

static void SaveLog(string message){Console.WriteLine($"ログ:{message}");}

voidメソッドでも、値を伴わないreturn;は使用できます。

static void ShowPositiveNumber(int number){if (number <= 0){return;}

Console.WriteLine(number);

}

この場合、numberが0以下ならメソッドを途中で終了します。

5-3. 条件によって異なる値を返す方法

条件分岐を使うと、条件に応じて異なる値を返せます。

static string GetResultMessage(int score){if (score >= 80){return "合格です";}

return "不合格です";

}

複数の条件を判定することも可能です。

static string GetGrade(int score){if (score >= 90){return "A";}else if (score >= 80){return "B";}else if (score >= 70){return "C";}else{return "D";}}

すべての分岐でreturnが実行されることが明確でなければ、コンパイルエラーになる場合があります。

switch式を使って簡潔に書くこともできます。

static string GetGrade(int score){return score switch{>= 90 => "A",>= 80 => "B",>= 70 => "C",_ => "D"};}

5-4. 複数の値をタプルで返す方法

C#では、タプルを使って1つのメソッドから複数の値を返せます。

static (int min, int max) GetMinMax(int[] numbers){int min = numbers.Min();int max = numbers.Max();

return (min, max);

}

戻り値を受け取るときは、各要素の名前を使用できます。

var result = GetMinMax(new[] { 8, 3, 12, 5 });

Console.WriteLine(result.min);Console.WriteLine(result.max);

分解して別々の変数へ代入することもできます。

(int min, int max) = GetMinMax(new[] { 8, 3, 12, 5 });

Console.WriteLine(min);Console.WriteLine(max);

タプルは、関連する少数の値をまとめて返す場合に便利です。ただし、返す項目が多い場合や、データ自体に明確な意味がある場合は、専用のクラスやレコードを作成したほうが分かりやすいこともあります。

5-5. return文でメソッドの処理を途中終了する方法

return文は、値を返すだけでなく、メソッドを途中で終了する目的でも使用できます。

static void RegisterUser(string name){if (string.IsNullOrWhiteSpace(name)){Console.WriteLine("名前が入力されていません");return;}

Console.WriteLine($"{name}を登録しました");

}

このように、処理を続けられない条件を先に判定して終了する書き方を、早期リターンと呼ぶことがあります。

早期リターンを使うと、条件分岐の入れ子を減らせます。

static void ProcessOrder(bool isValid, bool hasStock){if (!isValid){Console.WriteLine("注文内容が不正です");return;}

if (!hasStock){Console.WriteLine("在庫がありません");return;}Console.WriteLine("注文を確定しました");

}

処理を深いif文の中へ入れずに済むため、正常な処理の流れを読みやすくできます。

6. C#の関数の種類

6-1. staticメソッドとインスタンスメソッド

staticメソッドはクラスそのものに属し、インスタンスメソッドは作成したオブジェクトに属します。

staticメソッドの例は次のとおりです。

class MathUtility{public static int Square(int number){return number * number;}}
int result = MathUtility.Square(5);

インスタンスメソッドの例は次のとおりです。

class Counter{private int _count;

public void Increment(){_count++;}public int GetCount(){return _count;}

}

Counter counter = new Counter();

counter.Increment();counter.Increment();

Console.WriteLine(counter.GetCount());

オブジェクトごとに異なる状態を持たせる場合は、インスタンスメソッドを使用します。特定の状態に依存しない共通処理には、staticメソッドが適しています。

6-2. コンストラクターと通常のメソッド

コンストラクターは、クラスのインスタンスを作成するときに実行される特別な処理です。

class User{public string Name { get; }

public User(string name){Name = name;}

}

User user = new User("田中");Console.WriteLine(user.Name);

コンストラクターには、次の特徴があります。

  • クラス名と同じ名前を使用する

  • 戻り値の型を書かない

  • newでインスタンスを作成するときに呼ばれる

  • 主に初期値の設定や初期化処理を行う

通常のメソッドには戻り値の型を書き、必要なタイミングで明示的に呼び出します。

public void ShowName(){Console.WriteLine(Name);}

コンストラクターは通常のメソッドと似た構文を持ちますが、インスタンスの初期化を担当する特別なメンバーです。

6-3. ローカル関数

ローカル関数は、メソッドの内部に定義する関数です。

static int CalculateTotal(int price, int quantity){int Multiply(int a, int b){return a * b;}

return Multiply(price, quantity);

}

ローカル関数は、定義されたメソッドの内部から使用できます。特定のメソッド内でしか使わない補助処理をまとめたいときに便利です。

外側のメソッドで定義された変数を参照することもできます。

static void ShowMessage(string name){string prefix = "こんにちは";

void Print(){Console.WriteLine($"{prefix}、{name}さん");}Print();

}

ただし、ローカル関数を増やしすぎると、メソッド全体が長くなることがあります。複数の場所で使う処理は、通常のメソッドとして分離したほうがよいでしょう。

6-4. 匿名メソッド

匿名メソッドは、名前を付けずに処理を定義する機能です。delegateキーワードを使用します。

Action<string> showMessage = delegate (string message){Console.WriteLine(message);};

showMessage("こんにちは");

戻り値がある匿名メソッドも作成できます。

Func<int, int, int> add = delegate (int a, int b){return a + b;};

Console.WriteLine(add(10, 20));

匿名メソッドは、デリゲートへ一時的な処理を代入するときに使用できます。

現在のC#では、より簡潔に書けるラムダ式が使われることが多いものの、既存コードで匿名メソッドを見かけることがあります。

6-5. ラムダ式

ラムダ式は、名前のない関数を簡潔に記述する構文です。=>演算子を使用します。

Func<int, int, int> add = (a, b) => a + b;

Console.WriteLine(add(3, 5));

処理が複数行になる場合は、波かっこを使用します。

Func<int, int, int> multiply = (a, b) =>{int result = a * b;return result;};

ラムダ式は、LINQによるコレクション操作でも頻繁に使用します。

int[] numbers = { 1, 2, 3, 4, 5, 6 };

IEnumerable<int> evenNumbers = numbers.Where(number => number % 2 == 0);

foreach (int number in evenNumbers){Console.WriteLine(number);}

number => number % 2 == 0は、「numberを受け取り、偶数であればtrueを返す処理」を表しています。

6-6. 非同期メソッド

非同期メソッドは、時間のかかる処理の完了を待っている間に、ほかの処理を進められるようにするためのメソッドです。

C#では、主にasyncawaitを使用します。

static async Task ShowMessageAsync(){await Task.Delay(1000);Console.WriteLine("1秒後に表示されました");}

呼び出す側でもawaitを使用します。

await ShowMessageAsync();

戻り値がある非同期メソッドでは、一般的にTask<T>を使用します。

static async Task<int> GetNumberAsync(){await Task.Delay(1000);return 100;}
int number = await GetNumberAsync();Console.WriteLine(number);

非同期メソッドは、ファイル操作、ネットワーク通信、データベースアクセスなど、待ち時間が発生する処理でよく使用されます。

7. C#の関数でよく使う応用テクニック

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

メソッドのオーバーロードとは、同じクラス内に同じ名前のメソッドを複数定義する機能です。

ただし、引数の個数や型などを変える必要があります。

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

}

呼び出し時に渡した引数によって、適切なメソッドが選択されます。

Calculator calculator = new Calculator();

Console.WriteLine(calculator.Add(1, 2));Console.WriteLine(calculator.Add(1.5, 2.5));Console.WriteLine(calculator.Add(1, 2, 3));

戻り値の型だけを変えてオーバーロードすることはできません。

// 戻り値だけが異なるため定義できないint GetValue(){return 1;}

string GetValue(){return "1";}

オーバーロードを使うと、目的が同じ処理を統一した名前で提供できます。

7-2. ジェネリックメソッド

ジェネリックメソッドを使うと、特定のデータ型に限定されないメソッドを作成できます。

static T GetFirst<T>(T[] values){return values[0];}

Tは型を表す仮の名前です。呼び出すときに具体的な型が決まります。

int firstNumber = GetFirst<int>(new[] { 10, 20, 30 });string firstName = GetFirst<string>(new[] { "田中", "佐藤" });

多くの場合、型は引数から推論されるため、省略できます。

int firstNumber = GetFirst(new[] { 10, 20, 30 });string firstName = GetFirst(new[] { "田中", "佐藤" });

型パラメーターへ制約を付けることも可能です。

static void ShowName<T>(T item) where T : class{Console.WriteLine(item);}

ジェネリックメソッドを使うと、型ごとに似たメソッドを何度も作らずに済みます。

7-3. 式形式メンバーでメソッドを簡潔に書く方法

処理が1つの式だけで完結するメソッドは、式形式メンバーを使って簡潔に記述できます。

通常の書き方は次のとおりです。

static int Square(int number){return number * number;}

式形式メンバーを使うと、次のように書けます。

static int Square(int number) => number * number;

戻り値のないメソッドにも使用できます。

static void Greet(string name) => Console.WriteLine($"こんにちは、{name}さん");

短い計算や単純な処理には便利ですが、複雑な処理まで1行に詰め込むと読みづらくなります。処理内容が一目で理解できる場合に使用するとよいでしょう。

7-4. 再帰関数の仕組みと注意点

再帰関数とは、関数の中から自分自身を呼び出す関数です。

例えば、階乗を計算するメソッドは次のように書けます。

static int Factorial(int number){if (number <= 1){return 1;}

return number * Factorial(number - 1);

}

Factorial(5)を呼び出すと、次のように計算されます。

5 × 4 × 3 × 2 × 1

再帰処理には、必ず終了条件が必要です。この例では、number <= 1が終了条件です。

終了条件がない場合、呼び出しが繰り返され、最終的にスタックオーバーフローが発生する可能性があります。

static void InvalidRecursiveMethod(){InvalidRecursiveMethod();}

再帰関数は、木構造の探索や入れ子になったデータの処理などに適しています。一方、単純な繰り返しで表現できる処理では、forwhileのほうが分かりやすい場合があります。

7-5. デリゲートにメソッドを代入する方法

デリゲートは、メソッドを値として扱うための型です。

まず、デリゲート型を定義します。

delegate int Calculation(int a, int b);

デリゲートと同じ引数・戻り値を持つメソッドを作成します。

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

メソッドをデリゲート型の変数へ代入できます。

Calculation calculation = Add;

int result = calculation(10, 20);Console.WriteLine(result);

.NETには、よく使う形式のデリゲートとしてActionFuncが用意されています。

Actionは戻り値がない処理に使用します。

Action<string> print = Console.WriteLine;print("こんにちは");

Funcは戻り値がある処理に使用します。最後の型引数が戻り値の型です。

Func<int, int, int> add = Add;Console.WriteLine(add(3, 5));

デリゲートは、イベント処理やコールバック、処理の差し替えなどに使用されます。

7-6. async・awaitを使った非同期関数の書き方

asyncを付けたメソッドでは、awaitを使って非同期処理の完了を待てます。

static async Task DownloadAsync(){using HttpClient client = new HttpClient();

string content = await client.GetStringAsync("https://example.com");Console.WriteLine(content.Length);

}

awaitで待機している間、スレッドを占有し続けずに処理を戻せるため、アプリケーションの応答性を保ちやすくなります。

戻り値がある場合は、Task<T>を使用します。

static async Task<string> GetContentAsync(string url){using HttpClient client = new HttpClient();return await client.GetStringAsync(url);}

呼び出し側は次のように記述します。

string content = await GetContentAsync("https://example.com");Console.WriteLine(content);

非同期メソッドの名前には、非同期処理であることが分かるようにAsyncを付けるのが一般的です。

原則として、非同期メソッドの戻り値にはTaskまたはTask<T>を使用します。イベントハンドラーなど一部の例外を除き、async voidは呼び出し側で完了や例外を扱いにくいため避けましょう。

8. C#の関数を使った実践サンプル

8-1. 文字列を表示する関数

文字列を受け取って表示する、基本的なメソッドです。

static void ShowMessage(string message){Console.WriteLine(message);}

ShowMessage("C#の関数を学習しています");

戻り値がないため、戻り値の型にはvoidを指定しています。

表示内容を引数として受け取ることで、さまざまなメッセージを表示できます。

ShowMessage("処理を開始します");ShowMessage("処理が完了しました");

8-2. 2つの数値を足し算する関数

2つの整数を受け取り、合計を返すメソッドです。

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

int result = Add(10, 20);

Console.WriteLine(result);

abが引数、intが戻り値の型です。

より短く書く場合は、式形式メンバーを使用できます。

static int Add(int a, int b) => a + b;

8-3. 引数を受け取って結果を返す関数

商品の価格と個数を受け取り、合計金額を返す例です。

static decimal CalculateTotalPrice(decimal unitPrice, int quantity){decimal total = unitPrice * quantity;return total;}

decimal totalPrice = CalculateTotalPrice(1_200m, 3);

Console.WriteLine($"{totalPrice}円");

decimal型の数値リテラルには、末尾にmまたはMを付けます。

割引率も引数として受け取る場合は、次のように記述できます。

static decimal CalculateDiscountedPrice(decimal unitPrice,int quantity,decimal discountRate){decimal total = unitPrice * quantity;return total * (1 - discountRate);}

8-4. 条件分岐を含む関数

年齢を受け取り、成人かどうかを判定するメソッドです。

static bool IsAdult(int age){if (age >= 18){return true;}

return false;

}

呼び出し側では、戻り値を条件式に使用できます。

if (IsAdult(20)){Console.WriteLine("成人です");}else{Console.WriteLine("未成年です");}

このメソッドは、比較式の結果を直接返して簡潔に書くこともできます。

static bool IsAdult(int age){return age >= 18;}

8-5. 配列を引数として受け取る関数

整数の配列を受け取り、合計値を返すメソッドです。

static int SumArray(int[] numbers){int total = 0;

foreach (int number in numbers){total += number;}return total;

}

呼び出し方は次のとおりです。

int[] scores = { 80, 75, 90, 85 };

int total = SumArray(scores);

Console.WriteLine(total);

空の配列にも対応できますが、nullが渡される可能性がある場合は確認が必要です。

static int SumArray(int[]? numbers){if (numbers is null){throw new ArgumentNullException(nameof(numbers));}

int total = 0;foreach (int number in numbers){total += number;}return total;

}

8-6. クラス内に複数の関数を定義する例

1つのクラスには、役割に応じて複数のメソッドを定義できます。

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

public int Subtract(int a, int b){return a - b;}public int Multiply(int a, int b){return a * b;}public double Divide(int a, int b){if (b == 0){throw new DivideByZeroException("0では割れません");}return (double)a / b;}

}

呼び出し側は次のとおりです。

Calculator calculator = new Calculator();

Console.WriteLine(calculator.Add(10, 5));Console.WriteLine(calculator.Subtract(10, 5));Console.WriteLine(calculator.Multiply(10, 5));Console.WriteLine(calculator.Divide(10, 5));

関連する処理を同じクラスへまとめることで、プログラムの構造を整理できます。ただし、1つのクラスが多すぎる責任を持たないように注意が必要です。

9. C#の関数で発生しやすいエラーと対処法

9-1. 戻り値の型が一致しない

メソッドで指定した戻り値の型と、returnで返す値の型が一致しない場合はエラーになります。

次のコードでは、戻り値の型がintであるにもかかわらず、文字列を返しています。

static int GetValue(){return "100";}

整数を返すなら、次のように修正します。

static int GetValue(){return 100;}

文字列を返したい場合は、戻り値の型をstringに変更します。

static string GetValue(){return "100";}

暗黙的に変換できない型を返す場合は、明示的な変換が必要になることもあります。

static int GetIntegerValue(){double value = 10.5;return (int)value;}

ただし、型変換によって小数部分が失われるなど、結果が変化する可能性に注意しましょう。

9-2. 引数の数や型が一致しない

メソッドを呼び出す際は、定義された引数の数と型に合わせて値を渡す必要があります。

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

次の呼び出しでは、引数が1つしかないためエラーになります。

int result = Add(10);

正しくは、2つの引数を渡します。

int result = Add(10, 20);

引数の型が一致しない場合もエラーになることがあります。

int result = Add("10", "20");

文字列として受け取った数値を渡す場合は、変換処理が必要です。

if (int.TryParse("10", out int a) &&int.TryParse("20", out int b)){int result = Add(a, b);Console.WriteLine(result);}

9-3. return文が不足している

戻り値があるメソッドでは、実行可能なすべての経路で値を返す必要があります。

次のコードは、scoreが60未満の場合に値を返さないためエラーになります。

static string GetResult(int score){if (score >= 60){return "合格";}}

すべての条件で値を返すように修正します。

static string GetResult(int score){if (score >= 60){return "合格";}

return "不合格";

}

ifelseを使っても構いません。

static string GetResult(int score){if (score >= 60){return "合格";}else{return "不合格";}}

9-4. staticの有無が原因で呼び出せない

staticメソッドから、インスタンスメソッドを直接呼び出すことはできません。

class Program{static void Main(){ShowMessage();}

void ShowMessage(){Console.WriteLine("こんにちは");}

}

Mainはstaticメソッドですが、ShowMessageはインスタンスメソッドです。

ShowMessageをstaticメソッドに変更する方法があります。

static void ShowMessage(){Console.WriteLine("こんにちは");}

または、Programのインスタンスを作成して呼び出します。

static void Main(){Program program = new Program();program.ShowMessage();}

メソッドがオブジェクトの状態を使わないならstatic、インスタンスごとの状態を使うならインスタンスメソッドにするのが基本です。

9-5. アクセス修飾子が原因で参照できない

privateメソッドは、定義されたクラスの外部から呼び出せません。

class MessageService{private void Send(){Console.WriteLine("送信しました");}}

次のような外部からの呼び出しはエラーになります。

MessageService service = new MessageService();service.Send();

外部から呼び出す必要がある場合は、publicへ変更します。

class MessageService{public void Send(){Console.WriteLine("送信しました");}}

ただし、単にエラーを解消するためだけに、すべてのメソッドをpublicにするのは避けましょう。外部へ公開する必要がない処理はprivateにし、クラスの内部へ隠すことが大切です。

9-6. 同じ名前のメソッドを正しく呼び分けられない

オーバーロードされたメソッドは、引数の型や個数によって呼び分けられます。

static void Show(int value){Console.WriteLine($"整数:{value}");}

static void Show(string value){Console.WriteLine($"文字列:{value}");}

Show(100);Show("100");

引数がどの型にも変換できる場合や、複数の候補が同程度に適合する場合は、コンパイラが呼び出すメソッドを決められないことがあります。

その場合は、変数の型を明示したり、キャストしたりして呼び出すメソッドを指定します。

Show((int)100);Show((string)"100");

また、オプション引数を含むオーバーロードを増やしすぎると、どのメソッドが選択されるか分かりにくくなることがあります。役割が異なる処理には、別のメソッド名を付けることも検討しましょう。

10. 初心者がC#の関数を書くときのポイント

10-1. 1つの関数には1つの役割を持たせる

1つのメソッドには、できるだけ1つの役割を持たせます。

例えば、ユーザー情報の検証、保存、メール送信をすべて1つのメソッドで行うと、処理が複雑になります。

void RegisterUser(User user){// 入力確認// データ保存// メール送信// ログ出力}

役割ごとに分割すると、処理の流れが分かりやすくなります。

void RegisterUser(User user){ValidateUser(user);SaveUser(user);SendWelcomeEmail(user);}

各メソッドが小さくなることで、修正やテストもしやすくなります。

10-2. 処理内容が伝わるメソッド名を付ける

メソッド名を読むだけで、何をする処理なのか分かるようにします。

例えば、次の名前では目的が分かりにくいでしょう。

void Process(){// 処理}

ユーザー情報を保存する処理なら、具体的な名前にします。

void SaveUser(){// ユーザー情報を保存する処理}

真偽値を返すメソッドには、IsHasCanなどを付けると意味が伝わりやすくなります。

bool IsAdult(int age)bool HasPermission(User user)bool CanPurchase(Product product)

メソッド名は、コメントを書かなくても目的を理解できる名前を目指しましょう。

10-3. 引数の数を増やしすぎない

引数が多いメソッドは、呼び出し方が分かりにくくなります。

CreateUser("田中",25,"Tokyo","user@example.com",true,false,1);

このコードだけでは、それぞれの値の意味を理解しにくいでしょう。

名前付き引数を使用すると、ある程度読みやすくできます。

CreateUser(name: "田中",age: 25,address: "Tokyo",email: "user@example.com",isActive: true,isAdmin: false,planId: 1);

さらに引数が多い場合は、専用のクラスやレコードへまとめます。

public record CreateUserRequest(string Name,int Age,string Address,string Email,bool IsActive,bool IsAdmin,int PlanId);
void CreateUser(CreateUserRequest request){// 登録処理}

引数をまとめると、項目の追加や変更にも対応しやすくなります。

10-4. 戻り値と副作用を明確にする

副作用とは、戻り値を返す以外に、外部の状態を変更する処理のことです。

例えば、次のような処理は副作用を持ちます。

  • ファイルへ書き込む

  • データベースを更新する

  • 画面へ表示する

  • メールを送信する

  • オブジェクトのプロパティを変更する

計算だけを行うメソッドは、入力と出力の関係が分かりやすくなります。

static int CalculateTax(int price){return (int)(price * 0.1);}

一方、計算と表示を同時に行うと、再利用しにくくなる場合があります。

static int CalculateTax(int price){int tax = (int)(price * 0.1);Console.WriteLine(tax);return tax;}

計算と表示を分離すると、処理の目的が明確になります。

int tax = CalculateTax(1_000);Console.WriteLine(tax);

メソッド名や設計から、値を返す処理なのか、状態を変更する処理なのかが分かるようにしましょう。

10-5. 重複した処理を関数として切り出す

同じ処理を複数の場所へコピーすると、修正時にすべての場所を変更しなければなりません。

例えば、次のような入力確認が何度も登場する場合を考えます。

if (string.IsNullOrWhiteSpace(userName)){Console.WriteLine("名前を入力してください");return;}

共通メソッドとして切り出せます。

static bool IsValidName(string? name){return !string.IsNullOrWhiteSpace(name);}
if (!IsValidName(userName)){Console.WriteLine("名前を入力してください");return;}

重複処理をまとめると、コード量を減らせるだけでなく、仕様変更にも対応しやすくなります。

ただし、見た目が似ているだけで目的が異なる処理まで無理に共通化すると、かえって理解しにくくなることがあります。処理の意味や変更理由が同じかどうかも考えて切り出しましょう。

10-6. 長すぎる関数を分割する

メソッドが長すぎると、処理の全体像を把握しにくくなります。

次のように、複数の処理が1つのメソッドへ詰め込まれている場合は分割を検討します。

void CompleteOrder(Order order){// 注文内容の確認// 在庫の確認// 金額の計算// 注文データの保存// 決済処理// 確認メールの送信// ログの記録}

役割ごとにメソッドを作成すると、処理の流れを読みやすくできます。

void CompleteOrder(Order order){ValidateOrder(order);CheckStock(order);CalculateTotal(order);SaveOrder(order);ProcessPayment(order);SendConfirmationEmail(order);WriteLog(order);}

分割したメソッドには、何を行うのか分かる名前を付けます。メソッドの行数だけで機械的に判断するのではなく、「1つの処理として説明できるか」「異なる役割が混在していないか」を基準にするとよいでしょう。

まとめ

C#の関数とは、特定の処理をひとまとまりにして再利用できるようにしたものです。C#では、クラスや構造体に属する関数を「メソッド」と呼ぶのが一般的です。

基本的なメソッドは、アクセス修飾子、staticの有無、戻り値の型、メソッド名、引数、メソッド本体を組み合わせて定義します。

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

引数を使えば、呼び出すたびに異なる値を渡せます。戻り値を使えば、メソッドで計算・取得した結果を呼び出し元で利用できます。

C#には、通常のメソッドに加えて、ローカル関数、匿名メソッド、ラムダ式、非同期メソッドなどがあります。さらに、オーバーロード、ジェネリックメソッド、デリゲートといった機能を活用すると、柔軟で再利用しやすいコードを作成できます。

初心者のうちは、まず次の点を意識しましょう。

  • 1つのメソッドに1つの役割を持たせる

  • 処理内容が分かるメソッド名を付ける

  • 引数を増やしすぎない

  • 戻り値と副作用を明確にする

  • 重複処理をメソッドとして切り出す

  • 長いメソッドを役割ごとに分割する

関数を適切に使えるようになると、C#のコードを読みやすく整理できるだけでなく、修正やテストもしやすくなります。最初は、文字列の表示や簡単な計算など、小さな処理をメソッドに分けるところから練習するとよいでしょう。