本サイトからスピンオフした姉妹サイトです。前回は一般論としてのインターフェースとその課題を見た。今回はC#にインターフェースのデフォルト実装を導入すると、どのようなコードが書けるようになるのか、導入するために必要な修正点などについて見ていく。前編では、一般的にインターフェースがどのように実装されているかと、インターフェースが抱える問題を説明し、その問題はインターフェースが実装を持てれば解決するという話をした。前置きが長くなったが、今回と次回ではC#における事情について見ていこう。C#でも、インターフェースに実装を持てるようにしたいという動きが出始めている。要するに、これはインターフェースに対して過剰に掛かっていた制限を緩めるというものであり、技術的な課題はそれほど大きくない。C#コンパイラーだけでなく、.NETランタイムの修正が必要となる。2.0(2005年)以来のことであり、これまでになかった配慮が必要になる。デフォルト実装と一部同じ目的に使えるものとして、C#にはこれまでも拡張メソッドという機能があった。この機能との差については次回説明する。C#への「インターフェースのデフォルト実装」の導入インターフェースが持つ実装は、互換性を壊さないように取りあえず既定動作として実装しておくものである。多くの場合、実装するクラス側でoverrideして使う想定である。既定動作のメソッド)と呼ばれる。implementation)と呼ばれている。.NETランタイムの修正C#では、前編で説明したような型情報テーブルの読み込み・仮想関数テーブルの構築は、C#コンパイラーではなく、.NETランタイムの仕事である。C#にインターフェースのデフォルト実装を導入するに当たっては、C#だけではなく、.NETランタイムに手を加える必要がある。現在、インターフェースが実装を持てないという制限を掛けているのは.NETランタイムであり、この制限を取り払う修正が必要になる。C#コンパイラーだけの修正では済まないため、需要がある割にはこれまで優先的に取り組まれてこなかった。一方で、前回に話した通り、インターフェースに実装を持てないのはどちらかというと思想的な判断であって、技術的にはそれほど難しい問題ではない。クラスでやっていることをインターフェースでも認めるだけである。一応、.NETの型システムの根幹に関わる部分の修正なので関連する作業量はそれなりに多いが、特に革新的なアイデアを必要とするわけではなく、淡々と作業できる類いのものだ。要するに、一度「.NETランタイムの修正をやろう」と決まりさえすればすぐにでも実装に取り掛かれるものである。ランタイム修正による機能追加を試みる最初の試金石として最適な機能といえるだろう。その他の制限緩和インターフェースに実装を持てるようにするついでに、以下のようなインターフェースの制限を緩和することも検討されている。静的なメンバーを持てるようにするprivateやprotected、internalなどのアクセシビリティも認める要するに、インスタンスフィールドを持てない以外はほとんど抽象クラスと同じである。「状態を持っていると多重継承が難しい」という問題に対する解決策がインターフェースだったのだから、必要十分なライン(フィールドさえなければいい)にまで制限を緩めようという話である。C#の文法C#的にも、基本的にやることは「制限を緩めるだけ」である。リスト3に示す通り、インスタンスフィールドとコンストラクターを除いた全てが使えるようになるだろう。現状のクラスやインターフェースでも起きていることだが、継承に伴って同名の別メンバーができることがあって、それに伴う問題回避のためのルールがいくつか必要となるのだ。基底型に同名のメンバーを追加リスト4に示すように、派生クラスに同名のメンバーがすでにあると気付かず、基底クラスにメンバーを足してしまうことがあり得る。基底クラスに、派生クラスと同名のメンバーを追加してしまう例こういう状況に対してC#では、Derivedクラス側の既存動作を壊さないようにするため、override修飾子が付いていない限り、基底クラスの同名メソッドとは無関係の別メソッド扱いをするようにしている。具体的にはリスト5に示すような挙動になる。override修飾子を付けない限り、基底クラスの同名メンバーとは無関係インターフェースのメンバーがデフォルト実装を持つ場合、これと同様の配慮が必要である。この辺りは検討中ではあるが、恐らくリスト6のような仕様になるだろう。インターフェースのメンバーは、デフォルト実装を持つかどうかによって実装の仕方が変わってくる。デフォルト実装持ちのメンバーは、クラスのメンバーと同様の扱いになる。別インターフェースの同名メソッドインターフェースは多重継承できるということは、同名のメンバーを持つ別のインターフェースを2つ以上継承してしまう場合があり得るということだ。これに対して、C#では、リスト7に示すように、暗黙的実装という書き方で、個別実装・呼び分けができる。別インターフェースの同名メンバーの実装これに、デフォルト実装を追加すると、恐らくリスト8のようなルールが必要になる。デフォルト実装がある場合の別インターフェースの同名メンバーこの例のAクラスのように、クラス内で特に何もしない場合は、明示的実装をしたときと同様の挙動になる。A自身にはMメソッドは作られず、IまたはJにキャストした場合にだけ、それぞれのMを呼び出せる。Bクラスは、前節での説明の通りである。デフォルト実装があるメンバーに関しては、override修飾子を付けない限り、無関係な別のメソッドと見なされる。Cクラスのような書き方を認めるかどうかには議論の余地がある。IとJのどちらか片方だけがMメソッドのデフォルト実装を持っていて、もう片方には後からデフォルト実装を足すような場合に問題を起こすかもしれない。Dクラスは明示的実装をする例である。ここでも1点議題がある。よって、同じインターフェースのメソッドに対する別実装が起こり得る。クラスも交えたリスト9のような例や、インターフェースで菱形を作るリスト10のような例があり得るだろう。インターフェースの菱形継承の例リスト9の例であれば恐らく、基底クラスのメンバーを優先すべきと思われる。Derived().M()の呼び出しは認めてよく、Base.Mメソッドが呼ばれるべきだろう。リスト10のクラスAは、どちらを選ぶべきか決められず、コンパイルエラーになる見通しだ。クラスBのように、どの実装を採用するかの呼び分けの仕組みが提供される予定である。2.0以来のことになる。Vista(この記事が公開されるころにちょうどサポート終了)ですら標準でインストールされている。7.0を含め、現在のC#の機能は全て、やろうと思えばサポートされている全てのWindowsの、クリーンインストール状態で動く。「やろうと思えば」というのは、ランタイムだけでなくライブラリの対応が必要な機能が一部あるからだが、それも、ライブラリの移植をすれば古いバージョンの.NETランタイム上で動かせる。2.0上でも動く(ちなみに、必要とする型の自作も含めたコード全体をGist上に置いてある)。Frameworkの所定のバージョンをインストールしてもらう必要があった。Coreであれば、ランタイムの導入方法に以下のような選択肢がある。これまで通り、OSに対してインストールして、全てのアプリで共有して使うアプリ単位で最新のランタイムをダウンロードして使うビルド時にランタイム機能もアプリ自体に組み込んだ実行可能形式を作るこれらの仕組みによって、ランタイム更新のハードルはだいぶ下がっている。ランタイム更新が必要な言語機能の追加を考えてもいい頃合いである。機能フラグただ問題は、使いたい機能を使えるランタイムと使えないランタイムができることである。「このライブラリはインターフェースのデフォルト実装機能を使っている」というようなフラグを持つ必要があるだろう。現在でも、.NET向けのライブラリには依存しているフレームワークに関する情報が入っている。4.6以上をターゲットとしたアプリからしか参照できなくなる。同様に、ライブラリを作るときにランタイム機能も指定して作ることになるだろう。「デフォルト実装を使う」という設定をしているライブラリでだけこの機能が使える代わりに、参照できるアプリが限られることになる。まとめ現在のC#で、インターフェースが実装を持てないというのは、.NETランタイムであえて制約を掛けているためである。この制約を緩めるため、.NETランタイム自体の仕様変更を行う予定である。インターフェースに掛かっていた制限がだいぶ緩和され、インスタンスフィールドを持てない以外はほとんど抽象クラスと同じ状態になる。現在のインターフェースやクラスでも同様だが、同名別実装のメンバーをどう扱うかに少々工夫が必要で、現在その検討も行われている。2.0以来のことであり、これまでなかった配慮が求められる。「どのライブラリがどのランタイム機能依存しているか」などの機能フラグ管理が必要になるだろう。次回は、インターフェースのデフォルト実装と拡張メソッドを比較しながら、それぞれができること、得手・不得手なこと、なぜ既存のインターフェースの機能を拡張する方法が複数必要なのかについて考えてみよう。1.0がプレビュー版だった頃からC#によるプログラミング入門を公開していて、C#とともに今年で15年になる。最近の自己紹介は「C#でググれ」。※以下では、本稿の前後を合わせて5回分(第12回~第16回)のみ表示しています。INDEX]を参照してください。null参照問題(後編)最近のC#ではnullの存在が大きな問題となっている。前回(前編)で説明したnullの事情を踏まえ、今回(後編)は、将来のC#がnullをどう取り扱っていくのかを見ていく。C#への「インターフェースのデフォルト実装」の導入(前編)C#におけるインターフェースとは、ある型が持つべきメソッドを示す「契約」であり、実装は持てない。このことが大きな問題となりつつある。今回から全3回に分けて、C#がこの問題にどう対処しようとしているかを見ていく。C#への「インターフェースのデフォルト実装」の導入(中編)前回は一般論としてのインターフェースとその課題を見た。今回はC#にインターフェースのデフォルト実装を導入すると、どのようなコードが書けるようになるのか、導入するために必要な修正点などについて見ていく。C#への「インターフェースのデフォルト実装」の導入(後編)破壊的な影響を他に及ぼすことなくインターフェースの機能を拡張するには、デフォルト実装に加えて拡張メソッドも使用できる。今回はこれら2つの方法がなぜ必要なのか、それぞれが得意としている分野について詳しく見る。7.0で登場した新しいタプル(ValueTuple構造体)は、複数の値をひとまとめにして扱うのに便利なデータ構造だが、その実装は一般的な構造体のガイドラインに従っていない。なっているのか、技術的背景を追う。機械学習エンジニアを目指す人~初中級者のための技術情報サイトです。「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。開発者のためのSlackチーム、作りました:「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。2020最適なビューへの切り替えを推奨します:スマートフォン用表示に。