WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

interface メンバーの「既定の実装」

by WebSurfer 2024年1月17日 14:54

C# 8.0 以降で、interface のメンバーに「既定の実装 (default implementation)」を設定できるようになり、それに関連してアクセス修飾子に private, protected, internal などを設定することが可能になりました。

ちなみに、C# 8.0 より前 (.NET Core 3 より前、.NET Framework はすべて) では interface のメンバーのアクセス修飾子は public しか許されておらず、継承する class 側でメンバーを実装する際にもアクセス修飾子に public と明示的に指定する必要がありました。

public 以外が許されなかった理由は、自分が調べたことの要約ですが、以下のようなことと理解しています。 (理由が明確に書いてある Microsoft のドキュメントは見つかりませんでした)

  • class が interface を継承すると、その class が必ず interface に定義されているメンバーを実装して公開するという外部との契約となる。(Interface の仕様に "An interface defines a contract. A class or struct that implements an interface shall adhere to its contract." と書いてあります)
  • 別の言い方をすると、そもそも interface に指定されるメンバーを実装して class を利用する外部に公開するのが目的なのに、private とか protected で隠ぺいするのは理にかなってない。

上記にもかかわらず、C# 8.0 以降で interface のメンバーに private や protected などを設定できるようになったのは何故か、その理由に興味があったので調べてみました。以下に調べたことを備忘録として書いておきます。

調べたことを簡単に書くと、C# 8.0 で interface に「既定の実装」という機能を追加する際に、ついでに public 以外のあらゆるアクセス修飾子を設定できるようにし、「既定の実装」に対するアクセスコントロールを可能にするというのが目的らしいです。

そのあたりのことが書いてあったドキュメントと、関係する部分の抜粋を以下に書いておきます。

  1. アクセス修飾子 (C# プログラミング ガイド) の「その他の型」
    "インターフェイス メンバー宣言には、あらゆるアクセス修飾子を含めることができます。 そのことは、クラスを実装するあらゆるもので必要になる共通実装を静的メソッドから与えるときに最も役に立ちます。Interface member declarations may include any access modifier. This is most useful for static methods to provide common implementations needed by all implementors of a class."
  2. interface (C# リファレンス)
    "インターフェイスによってメンバーの既定の実装を定義できます。 共通の機能を 1 回で実装する目的で static メンバーも定義できます。 An interface may define a default implementation for members. It may also define static members in order to provide a single implementation for common functionality."
  3. default interface methods
    "Add support for virtual extension methods - methods in interfaces with concrete implementations. A class or struct that implements such an interface is required to have a single most specific implementation for the interface method, either implemented by the class or struct, or inherited from its base classes or interfaces."
  4. インターフェイスのデフォルト実装
    "メソッド、プロパティ、インデクサー、イベントのアクセサーの実装を持てるようになった。アクセシビリティを明示的に指定できるようになった。静的メンバーを持てるようになった・・・中略・・・狭義にはこの1番目の機能こそが「デフォルト実装」です。 ただ、これのついでに実装されたものなので2番目、3番目には具体的な名前がついていません"

以下に、interface のメンバーを「既定の実装」とし、アクセス修飾子に public, private, protected, internal を使ったサンプルを載せておきます。説明はコード中にコメントとして書きましたのでそちらを見てください。

namespace ConsoleAppInterface
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Sample sample = new();
            ((ISampleDerived)sample).PublicMethod();
            sample.PublicMethod2();
            sample.Protected3();
            //((ISampleDerived)sample).Protected2();  // アクセス不可
        }
    }

    // C# 8 以降で、interface のメンバーに「既定の実装 (default
    // implementation)」を設定できるようになり、関連してアクセス修飾子に
    // private, protected, internal などを設定することが可能になった
    interface ISample
    {
        // デフォルトで public なのは以前と同じ。なので、アクセス修飾子を付
        // けない場合は public になる
        void Public()
        {
            Console.WriteLine("ISample.Public");
            Private();    // private メンバーにアクセス
        }

        internal void Internal()
        {
            Console.WriteLine("ISample.Internal");
        }

        protected void Protected()
        {
            Console.WriteLine("ISample.Protected");
        }

        // private の場合「既定の実装」は必須。無いと以下のエラー:
        // エラー CS0501 'ISample.Private()' は abstract、extern、または
        // partial に指定されていないため、本体を宣言する必要があります
        private void Private()
        {
            Console.WriteLine("ISample.Private");
        }
    }

    interface ISampleDerived : ISample
    {
        void PublicMethod()
        {
            // interface で interface を継承する場合、継承元の public, 
            // internal, protected メソッドを呼べる。「既定の実装」の有無
            // も関係なく呼べる
            Public();
            Internal();
            Protected();
            // Private();  private はもちろんダメ
        }

        void Default()
        {
            Console.WriteLine("ISampleDerived.Defualt");
        }

        // 派生先から protected メンバーにアクセスできるのは interface
        // だけ。class から呼ぶことはできない。呼ぶとエラーになる。下の
        // Sample の実装の PublicMethod2 メソッド内の説明を参照
        protected void Protected2()
        {
            Console.WriteLine("ISampleDerived.Protected2");
        }

        // 以下のような「既定の実装」がない場合、継承する class 側で実装が
        // 必要。ただし、継承する class 側では public にしないとエラー
        protected void Protected3();
    }

    public class Sample : ISampleDerived
    {
        // 継承元の ISampleDerived 内で「既定の実装」がされているメソッド
        // (この例では PublicMethod, Default, Protected2)は継承するクラ
        // スでの実装が無くてもエラーにならない

        public void PublicMethod2()
        {
            // Default を呼ぶには 1 段キャストが必要。単に Default(); とし
            // たのではエラー
            ((ISampleDerived)this).Default();

            // interface と違って class では protected なものは呼べない。

            //((ISampleDerived)this).Protected2();

            // ・・・とすると以下のエラーとなる:
            // エラー CS1540 'ISampleDerived' 型の修飾子をとおしてプロ
            // テクト メンバー 'ISampleDerived.Protected2()' にアクセスす
            // ることはできません。修飾子は 'Sample' 型、またはそれから派
            // 生したものでなければなりません
        }

        // ISampleDerived に「既定の実装」がない Protected3() があるので
        // class側で実装が必要。

        public void Protected3()
        {
            Console.WriteLine("Sample.Protected3");
        }

        // ただし、アクセス修飾子を public にしないと以下のエラー:
        // エラー CS0737 'Sample' は、インターフェイス メンバー
        // 'ISampleDerived.Protected3()' を実装し��いません。
        // 'Sample.Protected3()' は public ではないため、インターフェイス
        // メンバーを実装できません。

        // と言って class 側で Protected3 を実装しないと以下のエラー:
        // エラー CS0535 'Sample' はインターフェイス メンバー
        // 'ISampleDerived.Protected3()' を実装しません
    }
}

// 結果は:
// ISample.Public
// ISample.Private
// ISample.Internal
// ISample.Protected
// ISampleDerived.Defualt
// Sample.Protected3

Tags: , ,

.NET Framework

Visual Studio 2022 のブラウザーリンク

by WebSurfer 2023年12月10日 18:38

Visual Studio 2022 で、ブラウザーリンクの機能を使わない場合は不要となる Visual Studio とブラウザ間の通信を行わないようにする方法を書きます。2023 年 12 月 10 日現在の最新版 17.8.3 の話で、更新プログラムが適用されると話が変わるかもしれませんのでご注意ください。

Visual Studio 2022 のブラウザーリンク

Visual Studio 2022 では、上の画像の赤枠で示した「ブラウザーリンクを有効にする」にはデフォルトでチェックは入っていません。しかし、チェックを入れなくてもブラウザーリンクの機能による Visual Studio とブラウザー間の通信が行われます。ブラウザーリンクは使わないのにそのための通信が行われるのは避けたい場合、通信を行わないようにするにはどうすればいいかという話です。

ブラウザーリンクというのは何かですが、Visual Studio 2013 で新たに搭載された機能で、Edge, Chrome, Forefox など複数のブラウザを同時に立ち上げて表示させたり、Visual Studio 上でのコードの修正を複数のブラウザに同時に反映させたりする機能だそうです。詳しくは以下のMicrosoft のドキュメントを見てください。

「ブラウザーリンクを有効にする」にチェックが入ってない状態でも ASP.NET が生成する html ソースの body 要素の最後に以下のようなブラウザーリンクのためのコードが追加されます。(その下の script タグの aspnetcore-browser-refresh.js はホットリロード機能を実現するために使うものらしいです)

<!-- Visual Studio Browser Link -->
<script type="text/javascript" 
  src="/_vs/browserLink" 
  async="async" 
  id="__browserLink_initializationData" 
  data-requestId="14aa4d281859488cbb19bdfef314a877" 
  data-requestMappingFromServer="false" 
  data-connectUrl="http://localhost:57421/8a979671883c4acf85fb8bd2fd42853e/browserLink">
</script>
<!-- End Browser Link -->
<script src="/_framework/aspnetcore-browser-refresh.js">
</script>

Visual Studio からアプリを実行すると、上のスクリプトが動いて Visual Studio とブラウザが自動的に通信を行います。アプリを実行して Fiddler で要求・応答をキャプチャすると、その中には上のスクリプトによる通信が含まれます。下の画像を見てください。

Fiddler で要求・応答をキャプチャ

画像の #13, #16, #18 が script 要素の data-connectUrl 属性に設定された localhost:57421 との通信です。#23 の localhost:44320 は、応答が HTTP 101 Switching Protocols となっており、応答ヘッダに Upgrade: websocket が含まれているところを見ると、やはりブラウザーリンクの関係のようです (ブラウザーリンクは通信に SignalR を使います)。

Visual Studio を使っての開発中であれば、ブラウザーリンクの機能を使う使わないに関わらず、デフォルトの設定のままにしておいて不都合はないかもしれません。しかし、使わないのに Visual Studio とブラウザー間のやり取りがあるのは煩わしいという場合があると思います。

その余計なやり取りをなくすにはどうしたらいいかですが、ググって調べてみると How to disable Browser Link in ASP.NET Core (.NET 6, VS 2022) という記事が見つかりました。Visual Studio の[ツール(T)]⇒[オプション(O)...]でオプション画面を開いてホットリロード関係の設定を変更する必要があるとのことです。

設定画面を開いてみると、デフォルトでは以下のように「CSS ホットリロード」が「有効」に設定されています。

デフォルトは「有効」

これを無効に変更すると、ASP.NET が生成する html ソースにはブラウザーリンクのため script 要素は含まれなくなり localhost:57421 との通信は行われなくなくなります。上の Fiddler の画像の #48 以降のやり取りがその結果です。

「CSS ホットリロード」無効

ただし、ここまででは #51 に示されるように依然として localhost:44320 との通信が行われています。また、html にはホットリロード用の script タグは残ったままになります。

localhost:44320 との通信およびホットリロード用の script タグも無くしたい場合は、「自動ビルドと更新のオプション」も「なし」に設定し、

「自動ビルドと更新のオプション」設定

さらに、デバッグ > .NET/C++ ホットリロードの設定画面で以下の赤枠の 2 項目のチェックを外します。

ホットリロードの設定

詳しいことは分かりませんが、ブラウザーリンクとホットリロードとは関係があるようで、上のようなホットリロードの設定がブラウザーリンクに影響しているようです。

Tags: , ,

DevelopmentTools

fetch API の mode:"no-cors"

by WebSurfer 2023年11月28日 17:14

JavaScript の fetch() グローバル関数 のリクエストオプションに mode:"no-cors" を設定するとどうなるかという話を書きます。

mode:'no-cors' に設定した結果

上の画像がその結果です。先の記事「Web API に CORS 実装 (CORE)」で紹介した CORS を実装した Web API に対し、リクエストオプションを mode:"no-cors" に設定した fetch 関数を使って GET 要求を出し、fetch 関数の戻り値の Response オブジェクトを Chrome 119.0.6045.160 のディベロッパーツールで開いて表示したものです。

Fetch Living Standard というドキュメントが fetch の仕様書ですが、このドキュメントの mode の説明に mode:"no-cors" に設定した場合どういう結果になるかが書いてあります。以下の通りです。

"Restricts requests to using CORS-safelisted methods and CORS-safelisted request-headers. Upon success, fetch will return an opaque filtered response."

上の前半は、HTTP 要求メソッドが GET, POST, HEAD に、要求ヘッダが CORS セーフリストリクエストヘッダーに制限されると言っています (古い CORS 仕様書で言うシンプルなリクエストの条件を満たすものと同じ)。後半は、成功すると opaque filtered response を返すと言っています。

opaque filtered response とは何かというと、上に紹介した fetch の仕様書に以下のように書いてあります。

"An opaque filtered response is a filtered response whose type is "opaque", URL list is « », status is 0, status message is the empty byte sequence, header list is « », body is null, and body info is a new response body info."

この記事の一番上の画像に表示した response の中身を見てください。上の説明通り、type:"opaque", url:"", status:0 となっているのが分かるでしょうか。

Fiddler を使って要求・応答をキャプチャすると以下のようになっています。要求は出ており、応答に含まれる JSON 文字列(画像の赤枠部分)は期待通りです。

Fiddler で要求・応答をキャプチャ

しかしながら、response は opaque filtered されているので、response.json() で Uncaught (in promise) SyntaxEffor: Unexpected end of input というエラーになります。下の画像を見てください。

SyntaxEffor: Unexpected end of input

要するに、fetch のリクエストオプションを mode:"no-cors" とした場合、たとえサーバー側で CORS 対応がしてあったとしても、ブラウザ側では CORS 対応がされず、Web API からの情報(上の例では Web API から返された JSON 文字列)は取得できないという結果になりました。

というわけで、mode:"no-cors" とする理由は、自分が考え付くケースに限っての話ですが、どう考えてもなさそうです。fetch の仕様書にはデフォルトは mode:"no-cors" と書いてありますが、それは "highly discouraged from using it for new features. It is rather unsafe" だそうです。実際、Chrome, Edge, Firefox など主要なブラウザの実装では mode:"cors" がデフォルトになっていますし。

(MDN のドキュメント Request.mode の no-cors の説明に "ドメイン間でデータが漏れることによって生じるセキュリティとプライバシーの問題を防ぐ" と書いてあります。セキュリティとプライバシーの問題を防いだ結果、必要な情報も取れなくなるのでは意味がないと思うのですが・・・)


以上の例はシンプルなリクエストになる場合ですが、プリフライトリクエストが行われる場合はどうなるかも書いておきます。下の画像を見てください。fetch が返すのはや���り opaque filtered response で、前のケースと同じ結果になります。

プリフライトリクエストが行われる場合

ただし、上に述べたように、mode:"no-cors" に設定した場合、HTTP 要求メソッドとヘッダが CORS 仕様書で言うシンプルなリクエストの条件に制限される点が前のケースとは異なります。

下の Fiddler で要求・応答をキャプチャした画像の青枠部分を見てください。プリフライトリクエストで使われる OPTIONS 要求ではなくて、いきなり POST 要求が出ています。また、fetch のパラメータで Content-Type を application/json と指定したにもかかわらず text/plain となっています。

Fiddler で要求・応答をキャプチャ

サーバーからの応答が 415 Unsupported Media Type になっていますが、これは要求ヘッダの Content-Type が text/plain に書き換えられてしまったからです (ASP.NET Core Web API では Content-Type:text/plain はサポートされません)。結果、415 エラーとなってエラーメッセージ(赤枠部分)が返されています。

fetch が返すのは opaque filtered response なので response.json() の結果は、上のケースと同様に、Uncaught (in promise) SyntaxEffor: Unexpected end of input というエラーになります。下の画像を見てください。

SyntaxEffor: Unexpected end of input


ちなみに、mode:"no-cors" を削除(デフォルトの mode:"cors" となる)した場合はどうなるかも書いておきます。下の画像を見てください。CORS の操作には成功し、API からのデータも期待通り取得できています。

mode:'no-cors' を削除

fetch のリクエストオプションから mode を削除しても(ブラウザの実装ではデフォルトが mode:"cors" になるはず)期待通りに動くことは、自分の環境の Windows 10 の Chrome 119.0.6045.160, Edge 119.0.2151.93, Firefox 120.0, Opera 105.0.4970.21 で確認しました。


最後に、fetch の要求先が同一ドメインで mode:"no-cors" に設定するとどうなるかも書いておきます。 Chrome 119.0.6045.160 で試した結果ですが、fetch が返すのはクロスドメインの時のような opaque filtered response ではなくて、以下のようになりました。

同一ドメインの場合

type:"basic" というのは同一ドメインのレスポンスを意味します (opaque filtered response になる場合は type:"opaque" となります)。どうも、mode:"no-cors" は無視されて、CORS に関係ない通常のやり取りがされるように見えます。

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar