今更ながらC# で湯婆婆を単文で実装してみる

最近、C#ですさまじく長いメソッドチェーンを見てしまいました。

基本的に長いメソッドチェーンは好まれませんが、ネタ的にはワンライナー実装はよくあることです。
よく考えれば湯婆婆くらいなら単文実装できるんじゃないかと思い立ったのでやってみようと思います。
流行りはもう1年以上前なので今更感は否めないですが、そんなもん。

湯婆婆についてはこちら

https://qiita.com/Nemesis/items/c7192a7c510788d2cba2

# 最初に結論
```C#:yubaba.cs
Console.WriteLine(String.Format("フン。{0}というのかい。贅沢な名だねぇ。\r\n今からお前の名前は{1}だ。いいかい、{1}だよ。分かったら返事をするんだ、{1}!!",((typeof(Console)?.GetMethod("WriteLine", new[]{typeof(string)})?.Invoke(null, new []{"契約書だよ。そこに名前を書きな。"}) as string) + Console.ReadLine()) is string yourname ? new object[]{yourname, new System.Globalization.StringInfo(yourname).SubstringByTextElements(new Random().Next(new System.Globalization.StringInfo(yourname).LengthInTextElements),1)} : new object[]{string.Empty, string.Empty}));
```

# 雑な解説
1文ですがそのままだと見づらいのでインデントを入れます。
~~~C#:yubaba.cs
Console.WriteLine(
    String.Format(
        "フン。{0}というのかい。贅沢な名だねぇ。\r\n今からお前の名前は{1}だ。いいかい、{1}だよ。分かったら返事をするんだ、{1}!!",
        (
            (
                typeof(Console)?
                    .GetMethod("WriteLine", new[]{typeof(string)})?
                    .Invoke(null, new []{"契約書だよ。そこに名前を書きな。"})
                    as string
            )
            + Console.ReadLine()
        ) 
        is string yourname
            ? new object[]{
                yourname,
                new System.Globalization.StringInfo(yourname)
                    .SubstringByTextElements(new Random().Next(new System.Globalization.StringInfo(yourname).LengthInTextElements),1)
            }
            : new object[]{
                string.Empty,
                string.Empty
            }
    )
);
~~~
湯婆婆を1文で実装する場合の問題点は
* 名前を受け取る前に一度標準出力がある
* 受け取った名前(1回)と加工済みの名前(3回)をそれぞれ出力する必要がある

この2つ。普通にやるなら全く意識しない問題です。
結局どちらも言語ハックで解決。
* Reflectionによる戻り値の変更
* is演算子の型パターン

## Reflectionによる戻り値の変更
メソッドチェーンは内側から外側へ、左側から右側へ順に実行されますが、戻り値がvoidのメソッドを呼ぶと、その時点で式を続けることができなくなってしまいます。
内側で標準入力を受け付けたいので、戻り値voidをnullに変換することで解決します。
Reflectionで強引にメソッドを呼ぶと、戻り値voidの場合はnullを、それ以外の場合はobjectを返すのでstring型との加算が可能になります。
```C#
(
    typeof(Console)?
        .GetMethod("WriteLine", new[]{typeof(string)})?
        .Invoke(null, new []{"契約書だよ。そこに名前を書きな。"}) 
        as string
)
+ Console.ReadLine()
```

これにより式で契約書を突きつけつつ、名前を受け取ることができました。
## is演算子の型パターン
C#では原則、変数宣言は宣言単独で書く必要があるのですが、C# 7.0から式の途中でも条件付きで宣言ができる構文が追加されています。
```C#
if (hoge is string fuga){
    Console.WriteLine(fuga.Length);
}

int hoge = fuga is string moge ? moge.Toint() : 0;
```
本来は上記のように型変換とnullチェックのために使うものですが、悪用することで1文に1つまでなら変数の宣言利用を同時に行うことができます。

if文とよく似た3項演算子でもis演算子の型パターンが使えるので、これを利用します。
```C#
(
    (
        typeof(Console)?
            .GetMethod("WriteLine", new[]{typeof(string)})?
            .Invoke(null, new []{"契約書だよ。そこに名前を書きな。"})
            as string
    )
    + Console.ReadLine()
) 
is string yourname
    ? new object[]{
        yourname,
        new System.Globalization.StringInfo(yourname)
            .SubstringByTextElements(new Random().Next(new System.Globalization.StringInfo(yourname).LengthInTextElements),1)
    }
    : new object[]{
        string.Empty,
        string.Empty
    }
```
長い上に読みにくいですが、やってることは「受け取った名前」と「新しい名前」が格納された配列を返すだけです。
新しい名前を生成する部分については真新しさが何もないので説明は省きます。

あとはString.Formatで文章に代入するだけ。
文字列補完式はis演算子のスコープから外れてしまう関係で変数名が定義されないので使えないようです。

```C#:yubaba.cs
Console.WriteLine(
    String.Format(
        "フン。{0}というのかい。贅沢な名だねぇ。\r\n今からお前の名前は{1}だ。いいかい、{1}だよ。分かったら返事をするんだ、{1}!!",
        (
            (
                typeof(Console)?
                    .GetMethod("WriteLine", new[]{typeof(string)})?
                    .Invoke(null, new []{"契約書だよ。そこに名前を書きな。"})
                    as string
            )
            + Console.ReadLine()
        ) 
        is string yourname ?
            new object[]{
                yourname,
                new System.Globalization.StringInfo(yourname)
                    .SubstringByTextElements(new Random().Next(new System.Globalization.StringInfo(yourname).LengthInTextElements),1)
            }
            : new object[]{
                string.Empty,
                string.Empty
            }
    )
);
```

# 実行結果
```shell
D:\> yubaba.exe
契約書だよ。そこに名前を書きな。
山田太郎
フン。山田太郎というのかい。贅沢な名だねぇ。
今からお前の名前は郎だ。いいかい、郎だよ。分かったら返事をするんだ、郎!!
```

```shell
D:\> yubaba.exe
契約書だよ。そこに名前を書きな。

Unhandled exception. System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'startingTextElement')
Actual value was 0.
   at System.Globalization.StringInfo.SubstringByTextElements(Int32 startingTextElement, Int32 lengthInTextElements)
   at Program.<Main>$(String[] args) in D:\Yubaba.cs:line 1
```
結果は仕様通り。空白でクラッシュするのも同じ。

𠮷田さん問題には対応していますが、Windowsだとコマンドプロンプトに入力できない仕様なのでLinuxなどでないと動かないようです。

# 参考にさせていただいた記事など
https://qiita.com/okazuki/items/03049da368222b562da9

# 余談
同じ.Net系言語でもPowerShellだと動的言語だけあって文中で変数宣言したり代入したりできるのでもっと楽に書けます。
```powershell
[Console]::WriteLine("フン。"+($name = [System.Globalization.StringInfo]::new([string](Read-Host "契約書だよ。そこに名前を書きな。`r`n")))+"というのかい。贅沢な名だねぇ。`r`n今からお前の名前は"+($newName = $name.SubstringByTextElements([Random]::new().Next($name.LengthInTextElements), 1))+"だ。いいかい、"+$newName+"だよ。分かったら返事をするんだ、"+$newName+"!!");
```