自作メソッド(関数)の定義

メソッドを自作する

メソッド(関数)は特定の処理をひとまとめにしたものです。
今までの説明ではC#に最初から用意されているConsole.WriteLineConsole.ReadLineの二つのメソッドを使用してきましたが、メソッドは自分で作ることもできます。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        //Mainメソッド
        static void Main(string[] args)
        {
            int a = 10, b = 20;
            int ret = Sum(a, b); //自作メソッドSumの実行
            Console.WriteLine("メソッドSumの戻り値:");
            Console.WriteLine(ret);
            Console.ReadLine();
        }

        //自作メソッドSum
        static int Sum(int number1, int number2)
        {
            int sum = number1 + number2;
            Console.WriteLine("メソッドSumの実行結果:");
            Console.WriteLine(sum);
            return sum;
        }
    }
}

上のコードの22行目~28行目でSumいう名前の自作メソッドを定義しています。
実行結果は以下になります。

メソッドSumの実行結果:
30
メソッドSumの戻り値:
30

コードの実行順

C#のコードはMainメソッドから始まり、その後は基本的に上から順番に実行されていきます。
その途中でメソッドを実行すると、処理はメソッド内に移ります。

上のコードの実行順を行番号で記すと、
14、15→(Sumメソッドへ)→24、25、26、27→(Mainメソッドへ)→15、16、17、18
となります。
15行目が二回出現していますが、15行目は大きく分けて「メソッドの実行」と「メソッドの実行結果の変数への代入」の二つの処理があり、前半の終了後に処理がSumメソッドに移り、処理が戻ってきてから後半の処理が行われるためです。

上記のコードでは「"メソッドSumの戻り値:"」の行が先に書かれていますが、「"メソッドSumの実行結果:"」の行のほうが先に実行されます。

メソッドの作り方

メソッドを作るにはMainメソッドの隣に以下の書式で記述します。
(Mainメソッドの前後どちらでも良い)


static 戻り値のデータ型 メソッド名(引数1, 引数2,...)
{
    //メソッドの処理
    //.
    //.
    //.

    return 戻り値;
}

このサンプルコードの自作メソッドSumは、外部から値(引数)をふたつ受け取り、それを足し算した結果を返すメソッドです。
以下、順に用語の意味を説明します。

「Mainメソッドの隣に記述する」というのは必ずしも正しくはないのですが、今はそう考えておいてください。

static

staticというキーワードはメソッドの定義に必須なわけではありません。
しかし、ここではとりあえずメソッド定義の最初にstaticを書く、と覚えてください。
これの意味はクラスの機能説明で改めて解説します。

戻り値のデータ型

メソッドは、メソッド内で行った処理の結果を戻り値という形でメソッドの呼び出し元に返すことができます。
「データ型」は変数の項で説明したデータ型と同じで、int型やstring型などの任意のデータ型を指定することができます。

サンプルコードのSumメソッドでは、int型同士の足し算の結果を得たいので、戻り値の型にint型を指定しています。

処理結果が必要ない場合

メソッドによっては処理結果を返す必要がないことがあります。
その場合はデータ型にはvoidという型を指定します。
「void」は「空」を意味し、何もないことを示す特殊なデータ型です。


//int型を返すメソッド
static int Test1()
{
}

//何も値を返さないメソッド
static void Test2()
{
}

メソッド名

メソッド名は基本的に自由につけることができます。
メソッドの処理内容を表すわかりやすい名前を付けると良いでしょう。

ただし、変数名の時と同じく「半角英数文字とアンダースコアが使える」「先頭文字に数値は使えない」「C#が使用しているいくつかのキーワードは使えない」というルールがあります。

変数やメソッドなどの「名前」は基本的に同じものは付けられません。
(例外はあります)
これは、コンパイラは「名前」でそれぞれの変数やメソッドを見分けているからです。
変数名やメソッド名のことを識別子といいます。

引数

メソッドは、外部から値を受け取ってその値をメソッド内で使用することができます。
これを引数といいます。


//number1、number2が引数
static int Sum(int number1, int number2)
{
}

引数は「そのメソッド内でのみ使用できる、最初から初期化されている変数」と考えるといいでしょう。
初期化に使用する値はメソッドの呼び出し側で指定します。

引数の定義の仕方は、メソッド名の後に丸括弧を書き、その中に記述します。
引数にもデータ型がありますから、データ型に続いて引数名を書きます。
これは変数の宣言と同じようなものと考えて構いません。

引数が複数ある場合は,(セミコロン)で区切り、二つ目以降を書きます。
引数は何個使用しても構いません。

引数が複数ある場合、一つ目の引数を第一引数、二つ目を第二引数、以降は第三、第四...と言います。
メソッドが要求する「引数の数」「データ型の種類」「引数の並び順」を引数リストといいます。

サンプルコードのSumメソッドではnumber1number2の二つのint型の引数を定義し、メソッド内で足し算に利用しています。

仮引数と実引数

メソッドの定義側で指定する引数を仮引数といいます。
メソッドの呼び出し側で指定する引数(実際の値)を実引数といいます。

メソッドの実行時に、実引数の値が仮引数にコピーされ、メソッド内で使用することができます。


static void Main(string[] args)
{
    //「2」「4」が実引数
    Test(2, 4);
}

//「num1」「num2」が仮引数
//メソッドの実行時に「2」と「4」の値が入る
static int Test(int num1, int num2)
{
    return num1 + num2;
}

メソッドの呼び出し側では、呼び出すメソッドの引数リストの通りの順序(データ型と引数の数)で実引数を指定する必要があります。

仮引数とは異なるデータ型の値を実引数に指定することは基本的にはできませんが、「暗黙的な型変換」という機能によって目的のデータ型に変換可能な場合は、異なるデータ型を実引数を指定することができます。
(組み込み型の型変換の項で改めて説明します)

引数を必要としないメソッド

メソッドでは引数は必ず使用しなければならないものではありません。
必要がなければ空にしておきます。
ただし、中身が空でも丸括弧を省略することはできません。


//引数を取らず、int型を返すメソッド
static int Test()
{
	return 0;
}

このメソッドの呼び出し側も丸括弧は省略することはできません。


//引数を取らず、int型を返すメソッド
static int Test()
{
	return 0;
}

static void Main(string[] args)
{
	//「Test」メソッドの実行
    Test();
}

メソッドの呼び出し側で記述される丸括弧は関数呼び出し演算子と言います。
これはその直前に記述されているメソッドを実行する、という機能があります。
これがないとメソッドは実行されません。

メソッド内の処理

引数リストの次に、波括弧{}を書きます。
この波括弧の中にメソッドの処理を書いていきます。

この波括弧で囲われた領域をブロックといいます。
ブロックは処理の区切りを作成するもので、メソッドの定義以外でも度々使用されます。
ブロックの内部には複数の行を含めることができます。

return文

最後に、return文で戻り値を指定します。

メソッドの定義で最初に書いた「戻り値のデータ型の指定」と同じデータ型の値をreturn文に指定します。
ここで指定した値がメソッドの処理結果として返されることになります。


static void Main(string[] args)
{
    //メソッドTestの実行結果をint型変数retに代入
    int ret = Test(2, 4);

    //「6」を表示
    Console.WriteLine(ret);
}

static int Test(int num1, int num2)
{
    //num1とnum2を足した値を返す
    return num1 + num2;
}

メソッドの呼び出し元では、必要がなければ戻り値を受け取らなくても構いません。
また、void型のメソッドは戻り値がないので受け取ることはできません。


static void Main(string[] args)
{
    //戻り値は受け取らなくても良い
    Test1(2, 4);

    //void型メソッドの戻り値は受け取れない
    //↓はエラー
    //int ret = Test2(2, 4);
}

static int Test1(int num1, int num2)
{
    int ret = num1 + num2;
    Console.WriteLine(ret);
    return ret;
}

//値を返さないvoid型のメソッド
static void Test2(int num1, int num2)
{
    int ret = num1 + num2;
    Console.WriteLine(ret);
}

戻り値のデータ型とreturn文で指定するデータ型とが一致しないとエラーになります。


//int型を返すメソッド
static int Test1()
{
    //戻り値に文字列を指定しているのでエラー
    return "あいうえお";
}

return文はメソッドを終了させる

return文を実行すると、そこでメソッドは終了します。
return文以降にどれだけメソッドの処理を書いていようと関係なくメソッドは終了します。


static int Test()
{
    //何か処理
    return 0;  //ここでメソッドの処理は終わり

    //return文以降は何を書いても
    //プログラムの動作に影響しない

    Console.WriteLine("この文字列は出力されない");
}

void型メソッドのreturn文

戻り値のデータ型にvoid型指定した場合はreturn文は必要ありません。
return文を書くことはできますが、何も値を指定できません。


//void型のメソッドはreturn文は必要ない
static void Test1()
{
    //何か処理

    //return文は書かなくて良い
}

//return文を書く場合は値を指定しない
static void Test2()
{
    //何か処理

    //↓これはエラー
    //return 0;

    //「return;」とだけ記述する
    return;
}

//return文を利用して処理を途中で強制終了することもできる
static void Test3()
{
    //何か処理
    return;	//ここでメソッドの処理は終了

    Console.WriteLine("この文字列は出力されない");
}

なお、void型以外のメソッドではreturn文を省略することはできません。
必ず指定したデータ型の値を返す必要があります。


//int型の戻り値を指定しているのに
//return文が無いのでエラー
static int Test1()
{
    Console.WriteLine("Test1");
}

//int型の戻り値を指定しているのに
//return文に戻り値がセットされていないのでエラー
static int Test2()
{
    Console.WriteLine("Test2");
    return;
}

メソッドの使いどころ

今回のサンプルコードは単なる足し算なので、わざわざメソッド化する意味はありません。
足し算程度の処理はそのまま書いたほうが早く、見た目にもわかりやすいでしょう。
メソッドは呼び出しにわずかながらコスト(実行時間など)がかかるので無駄も多くなります。

ある程度複雑なプログラムになってくると、コード中のいろいろなところで同じような処理が出現することがあります。
ほとんど同じ処理なのに毎回同じようなコードを書いていたのでは効率が悪いです。
コードの行数も多くなりますし、コンパイル後の実行ファイルのサイズも大きくなります。

そのような場合に、共通化できる処理をメソッドとしてまとめてしまえば、必要な場所からメソッドを呼び出すことができるようになります。
引数を変えればある程度処理内容も変えることができます。

仮にメソッド内の処理にバグがあっても、修正するのはそのメソッドだけで済みます。
もしメソッド化していないと、同じようなコードを書いた場所すべてを修正する必要があります。
作業効率が悪いだけでなく、修正漏れや記述ミスなどによりバグが混入する可能性も高くなってしまいます。

共通化できそうな処理が複数出てきたらメソッド化を検討すると良いでしょう。

また、「処理に名前(メソッド名)を付けることができる」というのもメソッド化のメリットです。
数行で済むような処理でも、あえてメソッド化して名前を付けコードの意味を明確にすることもあります。

ステップインによるメソッド内部のデバッグ

デバッグ機能の基本で軽く触れた通り、デバッグの実行方法にはステップインステップオーバーの二種類があります。
ステップアウト実行は単純にコードを上から順番に実行します。
自作メソッドもそのまま実行し、次の行に移ります。
ステップイン実行は自作メソッドに差し掛かった時にそのメソッドの内部に処理を移し、メソッド内のコードを一行ずつ実行して動作を確認することができます。

イメージとしては下図の赤矢印の順に一行ずつ実行していくことができます。
ステップイン実行

ステップイン実行ができるのは自作メソッドなどのソースコードが手元にあるものだけです。
例えばConsole.WriteLineメソッドなどはソースコードがないのでステップインで内部に移動することはできません。

ちなみにステップイン実行のショートカットキーは「F11」です。

ステップアウト

ステップイン実行やメソッドの内部にブレークポイントを設定した場合などで「現在実行している行」が(Mainメソッド以外の)メソッド内にあるとき、ステップアウトを実行するとそのメソッドの終了直後まで一気に処理を進めることができます。

ステップアウトのショートカットキーは「Shift + F11」です。