インタフェースなのに実装済みメソッドが追加できた。public
でなくprotected static
な。
成果物
前回
公式ドキュメントのコードがエラーになって動作確認できなかった。よって自前で動作確認コードを書いてみる。
確認項目
- C#8.0インタフェース新機能(仕様変更)
- メンバ変数やメソッドを実装できるようになった
static protected
メソッドで規定処理を定義できる(新機能)- インタフェースメソッドをオーバーライドすれば独自実装できる(今まで通り)
- メンバ変数やメソッドを実装できるようになった
BOT作成インタフェース
テキストを返すだけのインタフェースを作る。
Mastodonでつぶやくインタフェース
Mastodon)はTwitterに似たサービス用サーバを自前で作れるソフトウェア。
発言することはtoot
と呼ぶ。1回あたり500字までのテキストを入力できる。
守備範囲
- 実際にtootはしない。単に文字列を返すだけ
- 500字以内であるかチェックも実装しない
プロジェクト作成
dotnet new console -o CS8_Interface cd CS8_Interface
コード
Program.cs
class Program { static void Main(string[] args) { // Console.WriteLine(IBot::DefaultToot()); // protectedなので参照不可(error CS0432) // Console.WriteLine(IBot::Toot()); // staticでないため::で参照不可(error CS0432) // Console.WriteLine(IBot.Toot()); // newせねば.で参照不可(error CS0120) // Console.WriteLine(new IBot().Toot()); // interfaceはnewできない(error CS0144) // Console.WriteLine(new DefaultBot().Toot()); // (error CS1061) Console.WriteLine(new FixedBot().Toot()); Console.WriteLine(new DateTimeBot().Toot()); Console.WriteLine(new GreetingBot().Toot()); } }
IBot.cs
本題。C#8.0からインタフェースでもメンバ変数やメソッドを定義できるようになった。
そこで、インタフェース用メソッドと、そのデフォルト処理を実装してみる。
public interface IBot { public string Toot() => IBot.DefaultToot(); protected static string DefaultToot() => "デフォルトのtootです。"; }
static
にすることで1つだけ生成する。protected
にすることで子のみ参照できる。
でもこれ、ふつうのクラスと何が違うの? 以下、これを継承したクラスを実装する。
DefaultBot.cs
public class DefaultBot : IBot {}
コンパイルは成功する。ただしProgram.csで呼び出すとエラーになる。
Program.cs
Console.WriteLine(new DefaultBot().Toot()); // (error CS1061)
Program.cs(13,48): error CS1061: 'DefaultBot' に 'Toot' の定義が含まれておらず、型 'DefaultBot' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Toot' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください。 [/tmp/work/CSharp.Interface.8.0.20191026095218/src/CS8_Interface/CS8_Interface.csproj]
なぜ? IBotインタフェースは継承している。そこにはToot
メソッドを実装してある。public
なので公開されているから参照できるはずなのに……。インタフェースで実装されたメソッドはオーバーライドせねば参照できないのか?
前回の既定の実装を拡張するをみてみると、継承先で呼出している。どうやら以下のように呼出せねばならないようだ。
public class DefaultBot : IBot { public string Toot() => IBot.DefaultToot(); }
でも、継承先で単にデフォルト動作をしたいだけの場合は面倒ではないか? いちいち実装せねばならないとか……。仕様を知らずとも未実装箇所はデフォルト定義を継承するようにして欲しい……。
たとえばインタフェースメソッドが5つあるとき、4つはデフォルトで1つだけ独自実装したいときもあると思う。そういう場合、オーバーライドしたメソッドだけ独自処理になってくれると楽で助かる。いちいちデフォルト実装を書かねばならないとかダルすぎる。
でも、それってインタフェースの役割としてどうなんだ? 「実装を強いる」のがインタフェースのキモだったはず。なら、先述のような要件のときはどう実装すべきなの? public static
にする? それともクラスを作って継承させる?
FixedBot.cs
public class FixedBot : IBot { public string Toot() => "これは固定tootです。"; }
DateTimeBot.cs
public class DateTimeBot : IBot { public string Toot() => $"これは{DateTime.Now:yyyy-MM-dd HH:mm:ss}時点におけるtootです。"; }
GreetingBot.cs
public class GreetingBot : IBot { public string Toot() => DateTime.Now.Hour switch { int n when (3 < n && n < 10) => "おはようございます。", int n when (9 < n && n < 17) => "こんにちは。", int _ => "こんばんは。", }; }
実行結果
デフォルトのtootです。 これは固定tootです。 これは2019-10-26 12:26:10時点におけるtootです。 こんにちは。
所感
インタフェースの使い方
インタフェースの使い方が相応しくない気がする。以下のようにすべきでは?
interface IBot { public string Generate(); }
継承したクラスが実装すべき。
class FixedBot : IBot { public string Generate() => "これは固定tootです。"; }
これが本来のインタフェースだと思う。IBotを継承していながらGenerate
メソッドを実装していなかったらコンパイルエラーになるはず。
でも、これだと「C#8.0ではインタフェースにメンバ変数やメソッドが追加できるようになった」ことが確認できない。ぐぬぬ。いい例が思いつかない。
範囲における分岐
以下みたいに簡単に0
〜23
の範囲で分岐できたら嬉しかったんだけども。そういう構文はないらしい。
switch DateTime.Now.Hour, { 4..9 => "おはよう"; 10..17 => "こんにちは"; 0..23 => "こんばんは"; _ => throw new Exception("不正な時間です。0〜23の整数値のみ有効です。"); }
- もし
0..23
の範囲外なら例外。これは省略可 - もし
0..23
以内で4..9
,10..17
以外ならdefault
以下参考。
switch式
C#8.0新機能であるswitch式。rust言語でいうmatch式。既存のswitch
文と混同するからmatch
式にしてくれたらよかったのに。
public string Toot() => DateTime.Now.Hour switch { int n when (3 < n && n < 10) => "おはようございます。", int n when (9 < n && n < 17) => "こんにちは。", int _ => "こんばんは。", };
- メソッドをラムダ式
=>
で定義しているため、末尾に;
が必要である - switch式は
パターン => 戻り値
を,
で羅列し、全パターン網羅するもの
パターンの記法が冗長。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux