プログラミングを進める上で、何度も似たような処理を書くのは非効率的であり、ソースコードの可読性を下げる要因にもなります。
その為、何度も必要とされる処理は1度だけまとめて書いておくことで、それを複数の箇所から呼び出せるようにすることができます。これを "メソッド" と呼びます。
※他の言語では「関数」「サブルーチン」と呼ばれるものです。
メソッド
基本的なメソッドの書き方は次のようになります
// [修飾子] 戻り値 メソッド名(型 パラメータ1, 型 パラメータ2, ...)
int Add(int x, int y)
{
// return 戻り値;
return x + y;
}
メソッドは "パラメータ" として値を受け取ることができ、反対に "戻り値" として値を返すことができます。
また、 "return文" で戻り値として定義した型の値を返すことができます。
上記Addメソッドはint型の値を2つ受け取り、その和を戻します。
static void Main(string[] args)
{
// 呼出し方は メソッド名(パラメータ, パラメータ, ...)
Console.WriteLine(Add(1, 2));
Console.WriteLine(Add(3, 5));
}
static int Add(int x, int y)
{
return x + y;
}
> 3
> 8
エントリポイントもメソッドであり、戻り値はなし(void)かつパラメータとしてstring型配列のargsを受け取っています。
※戻り値の前に付いている "static" は "修飾子" のひとつですが、これは "クラス" の話をするときにまとめて触れます。
値渡しと参照渡し
メソッドのパラメータとして値を指定する時、定義上のパラメータを "仮引数" 。メソッドの呼出し側が関数に渡す引数を "実引数" と呼びます。
※ただの用語で、あまり重要ではありません。
static void Main(string[] args)
{
// 3, 5が実引数
Console.WriteLine(Add(3, 5));
}
// x, yは仮引数
static int Add(int x, int y)
{
return x + y;
}
上記のように単純にパラメータを指定した場合、 "値渡し" と言います。
この時、メソッド内部で仮引数の内容を変更したとしても、当然それは実引数側に反映されることはありません。
static void Main(string[] args)
{
int x = 5;
Temp(x);
Console.WriteLine(x);
}
static void Temp(int x)
{
x = 10;
}
> 5
上記に対してパラメータの前にin/out/refを付けることで "参照渡し" を使用することができます。
このin/out/refなどは "パラメータ修飾子" と呼ばれます。
static void Main(string[] args)
{
int x = 0;
InMethod(in x);
OutMethod(out x);
Console.WriteLine(x);
RefMethod(ref x);
Console.WriteLine(x);
}
static void InMethod(in int x)
{
// xへの代入はエラー
// x = 1;
Console.WriteLine(x);
}
static void OutMethod(out int x)
{
// xが未割当てのままメソッドを抜けようとするとエラー
// return;
x = 2;
}
static void RefMethod(ref int x)
{
x = 3;
}
> 0
> 2
> 3
それぞれ少しずつ動作が異なるので表にまとめると以下のようになります。
修飾子 | 呼出し前の初期化 | メソッド内での変更・代入 |
---|---|---|
in | 必須 | 不可 |
out | 不要 | 必須 |
ref | 不要 | 可能 |
-
in修飾子
in修飾子は呼出し側で最初に値を代入し、初期化しておかなければ使用することができません。
またメソッド内部ではin修飾子の付いたパラメータを変更することはできません。
これはメソッドの呼出し側に「値が書き換わらないこと」を保証することになりますが、これは先述の値渡しと同じ動作になります。
値渡しと違い、実引数→仮引数へのデータのコピーが発生しないので、巨大なデータを取り扱いやすいというメリットがあります。 -
out修飾子
out修飾子は呼出し側でのパラメータの初期化を必要としません(初期化しても問題ではありませんが、メソッド内部での代入を強制するので意味がありません)。
メソッド内部ではout修飾子のついたパラメータは何らかの値を代入する必要があり、その値をメソッドの呼出し側へ返却することになります。
つまり、return文による戻り値とは別に値を返したい場合などに使用されます。 -
ref修飾子
ref修飾子はメソッド呼出し側およびメソッド内部のどちらでも初期化や代入を強制することなく、自由に使用することができます。
使い勝手は一番いいですが、反対にメソッド内部と呼出し側でやり取りされるデータの管理が煩雑になりやすいことが挙げられます。
上記は値型についての話でしたが、参照型についても同様に値渡しと参照渡しを行うことができます。
代表的な参照型のひとつである配列でその動きを見てみましょう。
static void Main(string[] args)
{
int[] x = new int[] { 1, 2, 3 };
int[] y = new int[] { 4, 5, 6 };
Update(x, ref y);
Console.WriteLine(x[0]);
Console.WriteLine(y[0]);
}
static void Update(int[] x, ref int[] y)
{
x[0] = 10;
y[0] = 10;
}
> 10
> 10
参照型は値型と違い、持っているデータはメモリ上の実体を指す参照アドレスである為、値渡しでも「アドレスのコピー」を渡すことになり、そのアドレスが指している実体は同じものになります。
なので、参照先の実体に対する変更は、値渡しと参照渡しのどちらでも同じような結果になります。
しかし一方で、メソッド内部で新たに参照を割当てた場合、値型と似たような挙動になります。
static void Main(string[] args)
{
int[] x = new int[] { 1, 2, 3 };
int[] y = new int[] { 4, 5, 6 };
Update(x, ref y);
Console.WriteLine(x[0]);
Console.WriteLine(y[0]);
}
static void Update(int[] x, ref int[] y)
{
x = new int[] { 10, 20, 30 };
y = new int[] { 40, 50, 60 };
}
> 1
> 40
値渡しの時に参照アドレスをコピーしましたが、そこに新たに別の参照先を割当てることで、メソッド内部での仮引数には別の実体2が割当てられました。
しかし実引数の方は変わらず元の実体1を指したままになります。
参照渡ししている方は値型と同様に、元の実引数に対して新たに参照先を割当てるため、メソッドの呼び出し側でも値が変更されていることが確認できます。
デフォルト引数
メソッドに渡すパラメータはデフォルトの値を設定しておくことが可能です。
これを "デフォルト引数" と呼びます
static void Main(string[] args)
{
Output(3);
Output(3, 7);
}
static void Output(int x, int y = 5)
{
Console.WriteLine(x + y);
}
> 8
> 10
デフォルト引数は呼出し側から指定しなかった場合にデフォルトの値となり、指定した場合にはその指定された値となります。
また、デフォルト引数はメソッド定義上、通常のパラメータよりも後ろにのみ設定することが可能です。
オーバーロード
基本的に同名のメソッドを複数定義することはできませんが、パラメータが異なれば別のメソッドとして認識されます。
これをメソッドの "オーバーロード" と呼びます。
static void Main(string[] args)
{
Console.WriteLine(Add(2, 3));
Console.WriteLine(Add(2.5, 3.2));
}
static int Add(int x, int y)
{
return x + y;
}
static double Add(double x, double y)
{
return x + y;
}
> 5
> 5.7
パラメータの型や個数が異なればオーバーロードが可能です。
ただし、戻り値のみ異なる同名のメソッドではオーバーロードとしては認識されません。
メソッドについてはここまで。