白猫のメモ帳

JavaとかC#とかJavaScriptとかHTMLとか機械学習とか。

C#のオプション引数とオーバーライド

こんばんは。

朝晩は涼しくなってきた…と思いきや急に暑かったり翻弄されている私です。
でも、もう9月ですね。

今日はC#のオプション引数のお話。

オプション引数ってなんじゃら

void Fuga(int a, string b)
{
    Console.WriteLine($"a:{a} b:{b}");
}

たとえばこんなメソッドがあったとして。

void Fuga(int a)
{
    this.Fuga(a, default(string));
}

void Fuga(string b)
{
    this.Fuga(default(int), b);
}

void Fuga()
{
    this.Fuga(default(int), default(string));
}

こんな感じにオーバーロードを作ると、引数の省略ができますよね。

void Fuga(int a = default(int), string b = default(string))
{
    Console.WriteLine($"a:{a} b:{b}");
}

それをこうやって書くと、簡単にかけるよってやつです。

オーバーライドメソッドでもやってみる

abstract class HogeBase
{
    public abstract void Fuga(string s = "fuga");
}

class Hoge : HogeBase
{
    public override void Fuga(string s = "fuga")
    {
        Console.WriteLine(s);
    }
}

オーバーライドするときにはabstractメソッドにもoverrideメソッドにもオプション引数を設定できます。

abstract class HogeBase
{
    public abstract void Fuga(string s);
}

class Hoge1 : HogeBase
{
    public override void Fuga(string s = "fuga1")
    {
        Console.WriteLine(s);
    }
}

class Hoge2 : HogeBase
{
    public override void Fuga(string s = "fuga2")
    {
        Console.WriteLine(s);
    }
}

なんかバラバラにしてもちゃんとコンパイルできてしまいます。
雲行きが怪しくなってきました。

コンパイル時に決定されます

interface IHoge
{
    void Fuga(string s = "interface");
}

abstract class HogeBase : IHoge
{
    public abstract void Fuga(string s = "abstract class");
}

class Hoge : HogeBase
{
    public override void Fuga(string s = "class")
    {
        Console.WriteLine(s);
    }
}

なるほどこいつはポリモーフィズムってやつだなって感じでこんな定義をしてみると、

class Program
{
    static void Main(string[] args)
    {
        IHoge a = new Hoge();
        a.Fuga();   // interface

        HogeBase b = new Hoge();
        b.Fuga();   // abstract class

        Hoge c = new Hoge();
        c.Fuga();   // class
    }
}

予想外の挙動に混乱します。
コンパイル時にリテラルとして埋め込まれるので、変数の型によって既定値は決まるようです。

class HogeEx : Hoge
{
    public override void Fuga(string s = "class2")
    {
        Console.WriteLine(s + " ex!");
    }
}

ので、こうやって定義を追加すると、

class Program
{
    static void Main(string[] args)
    {
        IHoge a = new HogeEx();
        a.Fuga();   // interface ex!

        HogeBase b = new HogeEx();
        b.Fuga();   // abstract class ex!

        Hoge c = new HogeEx();
        c.Fuga();   // class ex!

        HogeEx d = new HogeEx();
        d.Fuga();   // class2 ex!
    }
}

メソッドのディスパッチはインスタンスの型で決まるにも関わらず、
既定値は変数の型で決まるというややこしい挙動になります。

便利だけどややこしいねっていうお話。