C# 初級講座
VB2019 Visual Studio 2022

第19回 メソッドを作る

2022/9/18

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
Visual Studio 2019 Visual Studio 2019 対象です。
Visual Studio 2017 Visual Studio 2017 対象外ですが、参考になります。
Visual Studio 2015 Visual Studio 2015 対象外ですが、参考になります。
Visual Studio 2013 Visual Studio 2013 対象外ですが、参考になります。
Visual Studio 2012 Visual Studio 2012 対象外ですが、参考になります。
Visual Studio 2010 Visual Studio 2010 対象外ですが、参考になります。
Visual Studio 2008 Visual Studio 2008 対象外ですが、参考になります。
Visual Studio 2005 Visual Studio 2005 対象外ですが、参考になります。
Visual Studio.NET 2003 Visual Studio.NET 2003 × 対象外です。
Visual Studio.NET 2002 Visual Studio.NET (2002) × 対象外です。
  Visual Studio Code 対象外ですが、参考になります。

 

目次

 

1.Windows フォーム アプリで メソッドを試す

今回は自分でメソッドを作成する方法を説明します。

いろいろなメソッドの例を紹介しながら学んでいくスタイルでやってみます。

ちゃんと学習したい人は登場する例を実際に入力して動かしてみることをお勧めします。

その場合、Windows フォーム アプリ として作成してください。

コンソール アプリだと、通常プログラムする位置(Program.Main)が静的メソッドでちょっと説明しにくいのです。この点については今後別途説明します。今回は Windows フォーム アプリで。

 

2.ウォーミングアップ

2-1.たし算をするメソッド

まずはウォーミングアップです。はじめは丁寧にやっていきます。

次のメソッドは、たし算をするメソッドです。Add という名前にしてみました。

/// <summary>
/// たし算します。
/// </summary>
public int Add(int x, int y)
{
    return x + y;
}

たし算は + だけで実行できるので、メソッドを作る必要はありません。あくまで勉強用のメソッドです。

 

Windows フォーム アプリに、この Add メソッドを組み込むと次のようになります。

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// たし算します。
        /// </summary>
        public int Add(int x, int y)
        {
            return x + y;
        }
    }
}

 

2-2.XMLコメント

/// からはじまるコメントは XMLコメント という特別なコメントで、プログラマー向けのヒントを提供します。

XMLコメントは最後に入力すると楽です。Visual Studioで /// のように / を3個連続で入力すると自動的に状況に応じた雛形を自動生成してくれるからです。

XMLコメントはあくまでコメントなので、なくても機能は変わりません。書かないこともよくあります。間違ったXMLコメントの書き方をするとVisual Studioが警告を表示する場合がありますが、それでもプログラムがエラーで実行できなくなるということはありません。

 

2-3.呼び出してみる

メソッドの 呼び出し方は自作のメソッドであっても、既定で存在するフレームワークのメソッドと変わりません。

ためしにbutton1_Clickから呼び出すプログラムを書くと次のようになります。

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// たし算します。
        /// </summary>
        public int Add(int x, int y)
        {
            return x + y;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int result = this.Add(3, 4);
            MessageBox.Show(result.ToString());
        }

    }
}

this. というキーワードは「同じインスタンス内の」という意味です。ここでは Form1 クラスにプログラムしているので、同じForm1のインスタンス内にあるAddメソッドを呼び出すという意味になります。このキーワードは省略可能ですが、this. と入力すると入力可能なキーワードの一覧が表示される点が便利なので、私は記述することがあります。

それから、ヒントとしてXMLコメントに入力した「たし算します」というコメントが表示されているのもポイントです。XMLコメントとして訳に立つ情報を書いておけばプログラムが格段にやりやすくなります。

この画像で Add 以外に並んでいる選択肢は Form1 がもともともっているフォームのメンバーです。Form1はFormクラスを継承しているため、何もプログラムしていない状態でもFormクラスのメンバーが実装され、使用可能な状態になっています。継承については別途詳しく説明しますが、このように他のクラスが持っている機能を引き継いで、自分が欲しいメソッドだけ追加したり、場合によっては引き継いだメンバーの機能を変更したりできるのでとても強力な機能です。

 

2-4.式形式のメソッド

C#には「式形式のメソッド」というメソッドの記述方法もあります。

今回はこの式形式のメソッドについては扱いません。

これは2015年に導入された機能で、複雑なメソッドは記述できない代わりに、単純なメソッドであれば簡潔に書けるという機能です。

通常のメソッドの記述方法であれば、どのような複雑なメソッドでも作成できるので、まずは通常のメソッドの記述方法から勉強するのが良いです。

さきほどの Addメソッドを式形式のメソッドで記述したものを参考に紹介します。1行で書けてしまいます。


public int Add(int x, int y) => x + y ;

 

3.戻り値のないメソッド

3-1.最もシンプルなメソッド

ウォーミングアップが終わったところで、初心に戻って最もシンプルなメソッドから取り上げていきます。だんだん複雑にしていきます。

次のメソッドはHelloと出力するだけのシンプルなメソッドです。

public void Test()
{
    System.Diagnostics.Debug.WriteLine("Hello!");
}

Debug.WriteLineで出力される場所

「public void メソッド名()」というのが、このようなシンプルなメソッドを定義する構文です。

後に () が必ず必要です。

 

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


this.Test();

this. は省略可能なので単に Test() と書いても呼び出せます。( ) は必ず必要です。忘れずに記述してください。

 

3-2.文字列型の引数を持つメソッド

今度は、引数を1つもつメソッドを作ってみます。

引数をそのまま出力するだけの機能です。Echo (エコー)という名前にしました。「こだま」という意味です。

public void Echo(string value)
{
    System.Diagnostics.Debug.WriteLine(value);
}

Debug.WriteLineで出力される場所

見るとわかるように、メソッドを定義するときに( ) の中にそのメソッドで使う引数を定義します。変数の定義のように型名 に続けて 引数名 を記述します。

こうすることで、このメソッドの中ではこの引数が通常の変数のように使用できます。

 

呼び出すときには次のようにします。


this.Echo("あいうえお");

 

3-3.文字列を逆にして出力してみる

あまりにも芸がないので、逆順で出力するようにしてみます。

public void Echo(string value)
{
    string output = string.Concat(value.EnumerateRunes().Reverse());
    System.Diagnostics.Debug.WriteLine(output);
}

このようにメソッドの中は何行でもプログラムできます。

このプログラムの意味はメソッドの作り方と直接関係ないし、ちゃんと理解するにはちょっと面倒な知識が必要なので今回は気にしないでください。気になる人向けに下の囲み記事に一応簡単に書いておきます。読まなくて良いです。

 

次のように呼び出すと今度は「おえういあ」と出力します。


this.Echo("あいうえお");

 

3-4.数値型の引数

次はstring ではなく、int の引数を持つメソッドです。

メソッドの定義はかっこの中の引数の型を int にするだけですね。

受け取った引数が奇数か偶数かを出力する IsOdd メソッドの例を紹介します。

public void IsOdd(int number)
{
    if (number % 2 == 1)
    {
        System.Diagnostics.Debug.WriteLine($"{number} は奇数です。");
    }
    else
    {
        System.Diagnostics.Debug.WriteLine($"{number} は偶数です。");
    }
}

このメソッドは % を使うとわり算のあまりを取得できることを利用しています。2で割った余りが1なら奇数ということです。

 

呼び出し例


this.IsOdd(12345);

 

4.戻り値のあるメソッド

4-1.必ず 123 を返すメソッド(勉強用以外に使い道なし)

次のメソッドは、必ず 123 を返すメソッドです。何の意味もありません。メソッド初心者の勉強に少し役立つだけです。

public int SayNumberYouLike()
{
    return 123;
}

定義で void の代わりに int が使われている点、値を返すときに return が使われている点を確認してください。

このように 戻り値がある場合は、void の代わりに戻り値の型を指定します。このメソッドは 123 という int 型の値を返すので int で定義しています。

値を返すにはメソッド内で return (読み方:return=リターン)を使用します。

 

呼び出し例

int result = this.SayNumberYouLike();
System.Diagnostics.Debug.WriteLine(result); //123 を受け取ったことを確認できます。

 

4-2.2倍にする

ここまでできれば、引数を2倍にして返すことも簡単ですね。

public int ToTwice(int number)
{
    return number * 2;
}

 

呼び出し例

int result = this.ToTwice(444);
System.Diagnostics.Debug.WriteLine(result); //888 を受け取ったことを確認できます。

 

4-3.最大値をオーバーしてしまう

でも、ちょっと考えてみると問題があることがわかります。

int の最大値は 2147483647 なので、2倍にするとこれをオーバーしてしまうかもしれません。

最大値をオーバーすると最小値に回り込むという性質を覚えていますか? つまり、int の計算では 2147483647 + 1 は -2147483648
 です。頭にマイナスが付いているのに注意してください。ですので、最大値を2倍すると -2  になります。(2147483647  + 2147483647 を1個ずつ指を追って数えると最小値に回り込んで-2になります。最大値と最小値では最小値の方が絶対値が1多いのです。)

試してみてください。

int result = this.ToTwice(2147483647);
System.Diagnostics.Debug.WriteLine(result); //なんと -2 と表示されます。

 

これが嫌な場合、解決方法はいくつかあります。たとえば、戻り値をもっと桁数の多い long にするというのはどうでしょうか?

public long ToTwice(int number)
{
    return number * 2;
}

呼び出し側でも戻り値を受け取る変数を long にしましょう。

実行すると、…まだ -2 と表示されます。

long result = this.ToTwice(2147483647);
System.Diagnostics.Debug.WriteLine(result); //でもまだ-2 と表示されます。

 

何が起こっているのでしょうか?

number * 2 という式が原因です。number が int 型です。定数 2 も int 型です。だから、これは int * int です。C#は int しか出てこない式では答えも int だと決めつけます。最大値をオーバーするから気を利かせて答えは long にするなどということはしてくれません。というわけで、この式の答えは int の -2 になります。メソッドの戻り値が long なので、この -2 が long 型として返されるだけです。longにした意味がない…。式自体を long で計算させる必要があります。

こんなとき特別な構文があります。number を long として扱って欲しいとき (long)number という書き方ができます。これを「キャスト」と呼びます。

そこで次のように書き換えましょう。

public long ToTwice(int number)
{
    return (long)number * 2;
}

これで大丈夫です。

今度はこの式は long * int です。型が違う値を計算するときはより大きい方の型に合わせようとする性質があるので、答えは long になります。

ちなみに、キャストは他の型でも使えますが、文字列から数値のような変換では使えません。その場合はConvertやint.Parseなどを使った型変換が必要です。

 

long として扱えれば良いという理屈なのでConvert.ToInt64 でも大丈夫です。

public long ToTwice(int number)
{
    return Convert.ToInt64(number) * 2;
}

 

定数 2 の方に、L を付けてlong型の定数にしてしまうという解決方法もあります。

public long ToTwice(int number)
{
    return number * 2L;
}

これだと int * long という計算になるので、やはり答えは long になります。

このようにリテラル(=プログラム中に直接記述する値)の後に特別な文字を付けてリテラルの型を明示することができる場合があります。M だと decimal, D だと double になります。

 

2をキャストして (long)2 にするという解決方法もあります。

 

5.return になれる

5-1.ログファイルへの書き込み

次の WriteLog メソッドは 引数の文字列を C:\temp\CSLogTest.txt ファイルに書き込みます。

最初に C:\temp フォルダーが存在するか確認し、存在しない場合は何もしません。この「何もしない」という部分で return を使用しています。

public void WriteLog(string message)
{
    //  ↓この ! の効果でC:\temp が『存在しない』という否定になる。
    if (!System.IO.Directory.Exists(@"C:\temp"))
    {
        return; //何もしないで呼び出し元に戻る。
    }

    System.IO.File.AppendAllText(@"C:\temp\CSLogTest.txt", $"{DateTime.Now} {message}\r\n");
}  

このように戻り値がないメソッド(=voidで定義しているメソッド)では、値を指定せずに return を使用し呼び出し元に戻れます。

文字列内の \r\n はWindowsで改行を表すエスケープシーケンスです。

 

呼び出し例

this.WriteLog("ログのテスト");
this.WriteLog("本日は晴天なり");

 

5-2.!

ところで、このプログラムでは Directoryクラス(読み方:Directory=ディレクトリー)の静的メソッド Exists (読み方:Exists=イグジスツ)でフォルダーが存在するか確認しています。Existsメソッドは フォルダーが存在するとき true を返します。今回は、存在しない時に return したかったので、 ! を使って true なら falseに、false なら true に値を反転させています。

この if 文は次のように明示的に false と比較しても同じです。

if (System.IO.Directory.Exists(@"C:\temp") == false)
{
    return; //何もしないで呼び出し元に戻る。
}

! を使うかどちらで表現するかは好みの問題です。慣れているプログラマーの多くは ! を使う用に感じます。

 

! はbool型の値の前に着けるとtrueならfalseに、false なら true に値を反転させる機能です。

bool value1 = true; // value1 は true になります。
bool value2 = !true; // value2 は false になります。

if (value1)
{
    //これが表示されます。
    System.Diagnostics.Debug.WriteLine("value1 は true です。");
}

if (!value2)
{
    //これが表示されます。
    System.Diagnostics.Debug.WriteLine("value2 は false です。");
}

プログラムに慣れていない人は、if などの条件式は X == Y とか X > Y のように 2つの値を == とか > などの比較演算子で比較する式として記述する傾向にあるように私は感じます。

if 文の 条件式は 最終的には true または false で評価されます。 bool 型の値ははじめから true または false なので if (value1 == true) のように書く必要はなく if (value1) と書けます。慣れてきたプログラマーは99%この書き方をします。 

 

このメソッドでは C:\temp フォルダーが存在しなければ、書き込み命令を実行せずにメソッドの実行を終了します。それを実現しているのが returnステートメント(読み方:return = リターン)です。

returnに実行が到達するとメソッドの実行はその時点で終了し呼び出し元に処理が戻ります。

 

5-3.素数

return は1つのメソッドの中で何度でも記述することができます。

次のIsPriveメソッドは引数の数値が素数であるかどうかを判断して true または false を返します。

素数とは、13 とか 37 のように 他の数字で割り切れない数のことです。

public bool IsPrime(int number)
{
    if (number < 2)
    {
        return false;
    }

    if (Enumerable.Range(2, (int)Math.Sqrt(number)).Any((x) => number % x == 0))
    {
        return false;
    }
    else
    {
        return true;
    }
}

素数かどうか判断する前に 値が 2 より小さいかを確認しています。マイナスや0,1は素数ではないので、さきにこれをチェックしています。該当すればfalse を返して終了します。これが1つ目の return です。

次の if 文では値を割り切れる数があるかどうか確認しています。この 条件式は 初級講座の現段階では説明不可能なので内容は割愛します。一応下の囲み記事で簡単に説明しておきますが読む必要はありません。

割り切れる数があれば素数ではないので false を返します。これが2つめの return です。 そうではない場合 3つ目の return で true を返します

呼び出し例

if (IsPrime(131))
{
    System.Diagnostics.Debug.WriteLine("素数です。");
}
else
{
    System.Diagnostics.Debug.WriteLine("素数ではありません。");
}

 

 

5-4.return 不足

次は季節の名前を取得する GetSeasonName メソッドを紹介します。

引数に月をとり、3,4,5は"春"、6,7,8は"夏" という具合に返します。

ところが、このメソッドはエラーで実行できません。そこがポイントです。

private string GetSeasonName(int month)
{
    if (month == 3 || month == 4 || month == 5)
    {
        return "春";
    }
    else if (month == 6 || month == 7 || month == 8)
    {
        return "夏";
    }
    else if (month == 9 || month == 10 || month == 11)
    {
        return "秋";
    }
    else if (month == 12 || month == 1 || month == 2)
    {
        return "冬";
    }
}

エラーメッセージは次の通りです。

エラー CS0161 'Form1.GetSeasonName(int)': 値を返さないコード パスがあります

ちなみにエラーを確認するには、エラーを示す赤い波線の上にマウスをホバーさせます。

または、エラー一覧を確認します。エラー一覧は[表示]メニューの[エラー一覧]で表示できます。

 

この場合「パス」(Path)とは英語の「路」(みち)のことです。

return を実行しない路があると言っています。このメソッドは string で宣言しているので必ず何か文字列型の値を返す必要があります。

return を通らない抜け道がとこにあるかわかりますか?

 

引数 month に -300 とか 123 とか非常識な値を渡された場合にどの return も実行されませんね。これが問題です。

そこで、一番先頭で引数のチェックを行うことにします。1~12の範囲にない場合、空文字を返すように機能を追加してみましょう。

private string GetSeasonName(int month)
{
    if (month < 1 || month > 12)
    {
        return "";
    }

    if (month == 3 || month == 4 || month == 5)
    {
        return "春";
    }
    else if (month == 6 || month == 7 || month == 8)
    {
        return "夏";
    }
    else if (month == 9 || month == 10 || month == 11)
    {
        return "秋";
    }
    else if (month == 12 || month == 1 || month == 2)
    {
        return "冬";
    }
}

ところが、これでもエラーは解消しません。

もう抜け道はないはずですが…、わかりますか??

 

これは C# のコンパイラー が人間より賢くないために、本当はOKなのをエラーだと認定しているのです。

私たち人間がみれば、これでどんな数字がわたってきても絶対に何か return が実行されるとわかりますが、コンパイラーには そこまで判断する能力がありません。

コンパイラー を納得させる方法はいくつかあります。もっと、わかりやすく絶対 return が実行されるプログラムの構造にするのです。条件判断が分かれる場合は else を使って その他の場合 をプログラムすれば、コンパイラーにも絶対にわかります。

private string GetSeasonName(int month)
{
    if (month == 3 || month == 4 || month == 5)
    {
        return "春";
    }
    else if (month == 6 || month == 7 || month == 8)
    {
        return "夏";
    }
    else if (month == 9 || month == 10 || month == 11)
    {
        return "秋";
    }
    else if (month == 12 || month == 1 || month == 2)
    {
        return "冬";
    }
    else    // ← この else で コンパイラーは は最悪でもここが絶対実行されると認識します。
    {
        return "";
    }
}

 ただ、よく考えてみればこのプログラムはelseがなくても同じですね。次の様にしてもOKです。

private string GetSeasonName(int month)
{
    if (month == 3 || month == 4 || month == 5)
    {
        return "春";
    }
    else if (month == 6 || month == 7 || month == 8)
    {
        return "夏";
    }
    else if (month == 9 || month == 10 || month == 11)
    {
        return "秋";
    }
    else if (month == 12 || month == 1 || month == 2)
    {
        return "冬";
    }
    
    return "";    // ← これでも OK
    
}

 

5-5.return ではなく例外を使う

GetSeasonNameメソッドの場合、13などの妙な値が渡された場合、例外を発生させる方法もあります。

こちらの方が優れています。

private string GetSeasonName(int month)
{
    if (month == 3 || month == 4 || month == 5)
    {
        return "春";
    }
    else if (month == 6 || month == 7 || month == 8)
    {
        return "夏";
    }
    else if (month == 9 || month == 10 || month == 11)
    {
        return "秋";
    }
    else if (month == 12 || month == 1 || month == 2)
    {
        return "冬";
    }
    
    // ↓ これでも OK
    throw new ArgumentException("monthは1~12を指定してください。");
}

すべての例外はクラスとして定義されています。例外を発生させるには、それら例外クラスのインスタンスを throw キーワード (読み方:throw=スロー)で指定します。これを「例外をスローする」と表現することもあります。

returnの代わりに例外を発生させることは認められており、このプログラムは実行できます。

 

6.まとめ

6-1.メソッドを定義する構文

今回やったことを少しまとめておきます。

戻り値のないメソッドの書き方は次の通りです。 赤い部分は必ず記述する必要があります。

public void メソッド名(パラメーターリスト)

{
    メソッドの内容
}

今回はこれの例として引数をそのまま出力するだけのEchoメソッドを紹介しました。

public void Echo(string value)
{
    System.Diagnostics.Debug.WriteLine(value);
}

Debug.WriteLineで出力される場所

 

メソッドの構文はいろいろなオプションがあるのでこれはほんの第一歩です。

パラメーターリストにはこのメソッドがどのような引数を受け取るかを記述します。たとえば、引数が2つある場合は、(string value1, string value2)のようにカンマで区切って記述します。

publicはこのメソッドがどこから呼び出せるかというアクセスレベルを表しています。

 

戻り値のあるメソッドの書き方は次の通りです。

赤い部分は必ず記述する必要があります。

public 戻り値の型 メソッド名(パラメーターリスト)

{
    メソッドの内容
    return 戻り値の値
}

これの例としては受け取った引数を2倍にして返す ToTwice メソッドを紹介しました。

public long ToTwice(int number)
{
    return (long)number * 2;
}

 

6-2.関数

最後に「関数」(かんすう)という言葉について補足しておきます。

「関数」という言葉は、メソッドの別名のように使われます。

この2つの言葉はあいまいで、はっきりとした区別はありません。

一応、区別を試みている人たちもいて、それなりの定義を作ったりはしていますが、結局、大多数のドキュメントの記載や話しをする人はそれぞれ場面場面で適当にこの言葉を使っており、統一的な定義はできません。

というわけですが、だいたいこのような理解というところを説明しておきます。

 

関数とメソッドは、どちらも何かの処理の塊を指します。ファイルのテキストを書き込む処理かもしれませんし、かけ算をする処理かもしれません。

その処理が戻り値を返す場合にそれを「関数」と呼びます。

その処理がクラスのメンバーとして実装されている場合「メソッド」と呼びます。

表にすると次の通りです。

この表の左上の部分は「メソッド」であり、かつ「関数」でもあるという分類です。

なお、クラスのメンバーにはプロパティやコンストラクターもあります。これらのメンバーにも処理を記述できますが、これらとメソッドは明確に形式が異なって区別可能なので、ここではその中でメソッドだけを相手にしています。

前述したように、 これらの言葉は厳密に区別されているモノでもなく、プログラミング言語によっても少し意味が違う場合があるので、話をしている相手がここで説明した内容にしたがって言葉を使うとは限りません。だいたいの言葉の意味を押さえた上で、文脈に応じて理解するようにしてください。

たとえば、一部のドキュメントでは、メソッドではない関数のことを「プライベートメソッド」と表現していたりします。

このようなあいまいな定義で困らないから、あいまいなままみんなこれらの言葉を使っているというわけですから、みなさんもあまりこだわらない方がよいでしょう。

 

7.練習問題

問1.■ に当てはまるキーワードは何でしょうか?
public ■ Clear()
{
    textBox1.Text = "";
    textBox2.Text = "";
    textBox3.Text = "";
}
int
string
void

 

問2.メソッドでは値を返すときに使うキーワードはどれでしょう?
exit
throw
return

 

問3.この画像のようにメソッドの説明を表示するには、メソッドの定義で何を記述しますか?
XMLコメント
属性
void

 

次の回では、for を使った繰り返しを説明します。繰り返しを使ってたくさんのきれいな円を描きます。

C#初級講座講座 次の回へ