名前付き引数, デフォルトの引数, 可変長引数
《 初回公開:2020/06/28 , 最終更新:未 》
visual studio2019 C#8.0で動作確認。
【 目次 】
名前付き引数
メソッド呼び出し時に引数名を使って値を指定できる。
名前付きの引数を使ってメソッドを呼び出すからと言って、呼び出される側のメソッドに特別の指定が必要なわけでは無い。
メソッドを定義して
public static void MyMethod(string arg1, string arg2, string arg3) { Console.WriteLine($"arg1 = {arg1}, arg2 = {arg2}, arg3 = {arg3}"); }
このメソッドを普通に呼び出すと
上記のMyMethodを呼び出すには
MyMethod("aaa", "bbb", "ccc");
これを名前付きで呼び出すと
名前付き引数によるメソッド呼び出し
MyMethod(arg3: "ccc", arg2: "bbb", arg1: "aaa");
みてのとおり名前付きの引数を使う場合には引数の指定順を気にする必要が無くて、どんな順番で指定しても良い。
上記の2とおりのメソッドの呼び出し方の結果は同じになる。
実行結果
arg1 = aaa, arg2 = bbb, arg3 = ccc
名前付き引数の指定で間違いやすいのは「:」では無く「=」を指定しまいがちな事。
これはコンパイルエラー。
コンパイルエラー。
MyMethod(arg3 = "ccc", arg2 = "bbb", arg1 = "aaa");
名前無しの引数と名前付き引数を同時に指定する場合は引数の順番に注意
// これはOK MyMethod("aaa", arg2: "bbb" ,arg3: "ccc"); // これはダメ コンパイルエラー MyMethod("aaa", arg3: "ccc", "bbb"); // これならOK MyMethod("aaa", arg2: "bbb", "ccc");
名前無しの引数と名前付き引数が混在する場合は、当然ながら引数の順番が矛盾するような指定はできない。
2番目のメソッド呼び出しがエラーになるのは、2つ目の引数にarg3を指定したのに、3番目の引数は名前無しなので再度arg3を指定し直す事になるため。
コードの可読性が悪くなるのでできれば「名前無しの引数と名前付き引数の混在」は避けた方が良いように思う。
省略可能な引数
省略可能な引数はデフォルトの引数とかオプション引数とも呼ばれる。
このような呼ばれ方がされるのは、メソッド定義時に引数に既定値を指定する事が出来て,引数値の指定はオプションであり省略可能で省略した場合はデフォルトの値が使われるという事。
省略可能な引数を持つメソッドの例
public static void DefaultArgsMethod(string arg1, string arg2 = "yyy", string arg3 = "zzz") { Console.WriteLine($"arg1 = {arg1}, arg2 = {arg2}, arg3 = {arg3}"); }
上記のメソッドでarg1は省略可能ではない引数、そしてarg2とarg3は省略可能。
以下のように異なる数の引数を指定して呼び出す事ができる。
DefaultArgsMethod("aaa"); DefaultArgsMethod("aaa", "bbb"); DefaultArgsMethod("aaa", "bbb", "ccc");
実行結果
arg1 = aaa, arg2 = yyy, arg3 = zzz arg1 = aaa, arg2 = bbb, arg3 = zzz arg1 = aaa, arg2 = bbb, arg3 = ccc
でも以下のように、カンマで区切って途中のarg2引数の指定を省略してarg3を指定する事はできない。
コンパイルエラー。
DefaultArgsMethod("aaa", , "ccc");
名前付き引数を省略可能な引数と組み合わせる事ができて、このような場合は名前付き引数を使えばよい。
以下ならOK
DefaultArgsMethod(arg3: "ccc", arg1: "aaa");
実行結果
arg1 = aaa, arg2 = yyy, arg3 = ccc
デフォルトの引数にはクラスや構造体,インターフェースも指定できるようで、
ただ、指定できる値に制限があるようで
デフォルトの引数にクラスや構造体,インターフェースを指定
public struct MyStruct { public string name; public int age; public override string ToString() => $"name = {name}, age = {age}"; } public class MyClass { public string name; public int age; public override string ToString() => $"name = {name}, age = {age}"; } public interface IMyInterface { string Name { get; } } public class MyClass2 : IMyInterface { private string _name= "MyClass2"; public string Name { get => _name; } } public static void DefaultObjectArgsMethod(MyStruct arg1 = new MyStruct(), MyClass arg2 = default(MyClass), IMyInterface arg3 = default(MyClass2)) { if(arg2!=null && arg3!=null) { Console.WriteLine($"arg1 = {arg1}, arg2 = {arg2}, arg3 = {arg3.Name}"); } else { Console.WriteLine($"arg1 = {arg1}, arg2 = null, arg3 = null"); } }
メソッドの呼び出し
DefaultObjectArgsMethod(); DefaultObjectArgsMethod(new MyStruct { name="愚鈍人", age=999}, new MyClass { name = "愚鈍人の妻", age=17 }, new MyClass2());
実行結果
arg1 = name = , age = 0, arg2 = null, arg3 = null arg1 = name = 愚鈍人, age = 999, arg2 = name = 愚鈍人の妻, age = 17, arg3 = MyClass2
よく分からないが、デフォルトの引数の値としてクラスの場合にはnullしか指定できないようであまり使い道が無いかな。
可変長引数 params
C言語のprintf関数のように可変数個の引数を受け取るメソッドが定義できると何かと便利。
このようなメソッドを実装するためにはparamsキーワードが必要。
paramsキーワードを使って可変長引数を受け取るメソッドを定義してみる。
可変数個の引数を受け取るメソッド
public static void ParamsArgsMethod1(params string[] paramsArg) { Console.WriteLine("===================="); if (paramsArg == null) { Console.WriteLine("null"); } else if (paramsArg.Length == 0) { Console.WriteLine("引数はありません。"); } else { for (int i = 0; i < paramsArg.Length; i++) { if (paramsArg[i] == null) paramsArg[i] = "null"; Console.WriteLine($"{i}番目の引数の値は{paramsArg[i]}"); } } }
このメソッドを呼び出してみる。
// 引数無し ParamsArgsMethod1(); // 引数が1つ ParamsArgsMethod1("xxx"); // 引数が2つ ParamsArgsMethod1("xxx", "yyy"); // 引数が3つでももっと増やせる ParamsArgsMethod1("xxx", "yyy", "zzz");
実行結果
==================== 引数はありません。 ==================== 0番目の引数の値はxxx ==================== 0番目の引数の値はxxx 1番目の引数の値はyyy ==================== 0番目の引数の値はxxx 1番目の引数の値はyyy 2番目の引数の値はzzz ==================== null ==================== 0番目の引数の値はxxx 1番目の引数の値はyyy 2番目の引数の値はzzz
可変長の引数を配列としてまとめて渡す事ができる。
可変長引数を配列として渡す
string[] arystr = { "xxx", "yyy", "zzz" }; ParamsArgsMethod1(arystr);
実行結果
==================== 0番目の引数の値はxxx 1番目の引数の値はyyy 2番目の引数の値はzzz
可変長引数にnullを渡す
引数にnullを1つだけ渡すと可変個の引数を受け取る配列変数そのものにnull値が渡される。
// nullを渡してみる ParamsArgsMethod1(null);
実行結果
==================== null
可変個の引数を受け取る配列の要素にnull値を与えるには、null値を要素に持つ配列を指定すれば良い。
// 配列の要素にnull値を与える ParamsArgsMethod1(new string[]{ null});
実行結果
0番目の引数の値はnull
複数の値を渡す場合にその中にnullが含まれている場合は単純で
ParamsArgsMethod1("xxx", null, "zzz");
実行結果
0番目の引数の値はxxx 1番目の引数の値はnull 2番目の引数の値はzzz
いろいろな型をもつ引数値を可変長の引数として渡すには
いろいろな型の引数を可変長の引数として受け取るには引数の型にobject型を指定すれば良い。
なぜならobject型はすべての型のスーパクラス。
いろいろな型をもつ引数値を可変長の引数として受け取るメソッド
public static void ParamsArgsMethod2(params object[] paramsArg) { Console.WriteLine("===================="); if (paramsArg == null) { Console.WriteLine("paramsArgがnull"); } else if (paramsArg.Length == 0) { Console.WriteLine($"引数はありません。"); } else { for (int i = 0; i < paramsArg.Length; i++) { if (paramsArg[i] == null) Console.WriteLine($"{i}番目の引数の値はnull"); else { Type type = paramsArg[i].GetType(); if (type.IsArray) { var sb = new StringBuilder(); foreach (object o in (Array)paramsArg[i]) { sb.Append(o).Append(","); } sb[sb.Length - 1] = '}'; Console.WriteLine($"{i}番目の引数の型は{type.Name}, 値は{{{sb}}}"); } else { Console.WriteLine($"{i}番目の引数の型は{type.Name}, 値は{paramsArg[i]}"); } } } } }
このメソッドを呼び出す。
public static void ParamsArgsTest2() { ParamsArgsMethod2(); ParamsArgsMethod2("aaa"); ParamsArgsMethod2("bbb", 10); ParamsArgsMethod2("ccc", "xxx", 10); ParamsArgsMethod2("ddd", 15.3, new MyStruct() { name = "struct", age = 20 }, new MyClass() { name = "class", age = 30 }); // 可変長引数を配列として渡す。 object[] aryObj = { "eee", new int[]{ 2,3,4}, "zzz" }; ParamsArgsMethod2(aryObj); // 名前付き引数として呼び出す ParamsArgsMethod2(paramsArg: aryObj); // あるいは ParamsArgsMethod2(new object[]{ "eee", new int[] { 2, 3, 4 }, "zzz" }); // 可変長引数の要素の中に配列を渡す ParamsArgsMethod2(new int[] { 2, 3 }); // object型として渡すと引数が分解される ParamsArgsMethod2(new object[] { 2, 3 }); // object型として渡す際に分解されないようにするには ParamsArgsMethod2(new object[]{new object[] { 2, 3 }}); // nullを渡すとparamsArgがnullになる ParamsArgsMethod2(null); // paramsArgの要素にnullを渡したい場合は ParamsArgsMethod2((object)null); // 複数の引数として渡したければそのまま単純に ParamsArgsMethod2(null,null); } public struct MyStruct { public string name; public int age; public override string ToString() => $"name = {name}, age = {age}"; } public class MyClass { public string name; public int age; public override string ToString() => $"name = {name}, age = {age}"; }
実行結果
==================== 引数はありません。 ==================== 0番目の引数の型はString, 値はaaa ==================== 0番目の引数の型はString, 値はbbb 1番目の引数の型はInt32, 値は10 ==================== 0番目の引数の型はString, 値はccc 1番目の引数の型はString, 値はxxx 2番目の引数の型はInt32, 値は10 ==================== 0番目の引数の型はString, 値はddd 1番目の引数の型はDouble, 値は15.3 2番目の引数の型はMyStruct, 値はname = struct, age = 20 3番目の引数の型はMyClass, 値はname = class, age = 30 ==================== 0番目の引数の型はString, 値はeee 1番目の引数の型はInt32[], 値は{2,3,4}} 2番目の引数の型はString, 値はzzz ==================== 0番目の引数の型はString, 値はeee 1番目の引数の型はInt32[], 値は{2,3,4}} 2番目の引数の型はString, 値はzzz ==================== 0番目の引数の型はString, 値はeee 1番目の引数の型はInt32[], 値は{2,3,4}} 2番目の引数の型はString, 値はzzz ==================== 0番目の引数の型はInt32[], 値は{2,3}} ==================== 0番目の引数の型はInt32, 値は2 1番目の引数の型はInt32, 値は3 ==================== 0番目の引数の型はObject[], 値は{2,3}} ==================== paramsArgがnull ==================== 0番目の引数の値はnull ==================== 0番目の引数の値はnull 1番目の引数の値はnull
名前付き引数とデフォルトの引数と可変長引数の組み合わせ
引数の指定は通常の引数(というか位置固定の引数 - 今後は位置引数と呼ぶ事にする)とデフォルトの引数と可変長引数を組み合わせて使う事ができる。
位置引数,デフォルトの引数,可変長引数の順に指定しなければならない。
以下のメソッドは第一引数に位置引数,第二引数にデフォルトの引数,第三引数に可変長引数をとるメソッドの例。
public static void ParamsArgsMethod3(string arg1,string arg2="arg2", params object[] paramsArg) { Console.WriteLine("===================="); Console.WriteLine($"arg1 = {arg1}, arg2 = {arg2}"); if (paramsArg == null) { Console.WriteLine("paramsArgがnull"); } else if (paramsArg.Length == 0) { Console.WriteLine($"可変長引数はありません。"); } else { for (int i = 0; i < paramsArg.Length; i++) { if (paramsArg[i] == null) Console.WriteLine($"{i+2}番目の引数の値はnull"); else { Type type = paramsArg[i].GetType(); if (type.IsArray) { var sb = new StringBuilder(); foreach (object o in (Array)paramsArg[i]) { sb.Append(o).Append(","); } sb[sb.Length - 1] = '}'; Console.WriteLine($"{i + 2}番目の引数の型は{type.Name}, 値は{{{sb}}}"); } else { Console.WriteLine($"{i + 2}番目の引数の型は{type.Name}, 値は{paramsArg[i]}"); } } } } }
このメソッドは以下のように呼び出す事ができる。
ParamsArgsMethod3("aaa"); ParamsArgsMethod3("bbb"); ParamsArgsMethod3("ccc", "xxx"); ParamsArgsMethod3("ddd", "xxx",15.3, new MyStruct() { name = "struct", age = 20 }, new MyClass() { name = "class", age = 30 }); object[] aryObj = { "eee", new int[] { 2, 3, 4 }, "zzz" }; ParamsArgsMethod3( paramsArg: new object[]{ "paramsArg", new int[] { 2, 3, 4 }}, arg1: "eee");
実行結果
==================== arg1 = aaa, arg2 = arg2 可変長引数はありません。 ==================== arg1 = bbb, arg2 = xxx 可変長引数はありません。 ==================== arg1 = ccc, arg2 = xxx 2番目の引数の型はDouble, 値は15.3 3番目の引数の型はMyStruct, 値はname = struct, age = 20 4番目の引数の型はMyClass, 値はname = class, age = 30 ==================== arg1 = eee, arg2 = arg2 2番目の引数の型はString, 値はparamsArg 3番目の引数の型はInt32[], 値は{2,3,4}}
ふと気が付いたのだが、可変長の引数が指定されない場合可変長の引数の配列の長さは0になる。
これを利用すると可変長の引数にデフォルトの値を持たせる事ができる。
可変長の引数がデフォルトの値を持つようにメソッドを修正
public static void ParamsArgsMethod3(string arg1, string arg2 = "arg2", params object[] paramsArg) { Console.WriteLine("===================="); Console.WriteLine($"arg1 = {arg1}, arg2 = {arg2}"); if (paramsArg == null) { Console.WriteLine("paramsArgがnull"); } else { if (paramsArg.Length == 0) { paramsArg = new object[] { "デフォルトの可変長の引数の値", 2, 3.14, 'X' }; } for (int i = 0; i < paramsArg.Length; i++) { if (paramsArg[i] == null) Console.WriteLine($"{i + 2}番目の引数の値はnull"); else { Type type = paramsArg[i].GetType(); if (type.IsArray) { var sb = new StringBuilder(); foreach (object o in (Array)paramsArg[i]) { sb.Append(o).Append(","); } sb[sb.Length - 1] = '}'; Console.WriteLine($"{i + 2}番目の引数の型は{type.Name}, 値は{{{sb}}}"); } else { Console.WriteLine($"{i + 2}番目の引数の型は{type.Name}, 値は{paramsArg[i]}"); } } } } }
このメソッドを呼び出すと
実行結果
==================== arg1 = aaa, arg2 = arg2 2番目の引数の型はString, 値はデフォルトの可変長の引数の値 3番目の引数の型はInt32, 値は2 4番目の引数の型はDouble, 値は3.14 5番目の引数の型はChar, 値はX ==================== arg1 = bbb, arg2 = xxx 2番目の引数の型はString, 値はデフォルトの可変長の引数の値 3番目の引数の型はInt32, 値は2 4番目の引数の型はDouble, 値は3.14 5番目の引数の型はChar, 値はX ==================== arg1 = ccc, arg2 = xxx 2番目の引数の型はDouble, 値は15.3 3番目の引数の型はMyStruct, 値はname = struct, age = 20 4番目の引数の型はMyClass, 値はname = class, age = 30 ==================== arg1 = eee, arg2 = arg2 2番目の引数の型はString, 値はparamsArg 3番目の引数の型はInt32[], 値は{2,3,4}}
メソッドのオーバーロードと優先順位
以下のようにオーバロードされたメソッドがある場合、
省略可能な引数は引数の指定が省略できるし可変長の引数は呼び出し時に引数の数が固定ではないので、C#はどのメソッドを呼び出すのだろうか。
public static void TestMethod(string arg1) { Console.WriteLine("public static void ParamsArgsMethod3(string arg1)"); } public static void TestMethod(string arg1, string arg2 = "default") { Console.WriteLine("public static void TestMethod(string arg1, string arg2 = \"default\""); } public static void TestMethod(params string[] args) { Console.WriteLine("public static void TestMethod(params string[] args)"); }
実際にメソッドを呼び出してテストしてみると。
TestMethod("aaa"); TestMethod("aaa","bbb"); TestMethod("aaa", "bbb","ccc");
実行結果
public static void ParamsArgsMethod3(string arg1) public static void TestMethod(string arg1, string arg2 = "default" public static void TestMethod(params string[] args)
メソッドの優先順位が位置引数,省略可能な引数,可変長引数の順に選択されているのがわかる。
ただこのような場合であっても、どのメソッドを指定しているのか明確にできれば目的のメソッドを呼び出す事も。
TestMethod(args: new string[] { "ccc" });
実行結果
public static void TestMethod(params string[] args)
メソッドのオーバーライドのふるまい
以下の参考記事にあるサンプルプログラムをパクッてきてちょっと蛇足をくわえたものだが。
class Base { public virtual void X(string sb = "base") => Console.WriteLine(sb + " in base"); } class Derived : Base { public override void X(string sd = "derived") => Console.WriteLine(sd + " in derived"); } static void Test() { Base x = new Base(); x.X(); // base in base x.X(sb: "aaa"); Derived y = new Derived(); y.X(); // derived in derived y.X(sd: "aaa"); //y.X(sb: "aaa"); // これはコンパイルエラー Base z = new Derived(); z.X(); // base in derived z.X(sb: "aaa"); // base in derived //z.X(sd: "aaa"); // ; これはコンパイルエラー }
実行結果
base in base aaa in base derived in derived aaa in derived base in derived aaa in derived
オーバーライドしたメソッドをベースクラスをとおして呼び出すと、メソッドのシグネチャ(デフォルトの引数値や名前付き引数の引数名)はベースクラスの定義がメソッド内の処理は派生クラスのコードが使われるようだ。