WebSurfer's Home

Filter by APML

GeoCoordinateWatcher で緯度経度を取得

by WebSurfer 24. May 2025 19:13

.NET Framework 4.8/4.8.1 の GeoCoordinateWatcher クラスを使っての緯度経度データ取得について調べたことを備忘録として書いておきます。

Microsoft のドキュメント GeoCoordinateWatcher class の Remarks に書いてありますが、GeoCoordinateWatcher は位置プロバイダから座標データを取得して位置情報を提供するそうです。

位置プロバイダには GPS, Wi-Fi triangulation, cell phone tower triangulation などがあるそうです。cell phone tower triangulation は関係なさそうに思いましたが、ノート PC にはモバイルルーターを使わず直接携帯データネットワーク経由でインターネットにアクセスできるものがあって、そのような PC に搭載されているのかもしれません。(未確認&想像です)

(Microsoft 位置情報サービスは IP アドレスも位置情報を得るための手段の一つとして使うそうですが GeoCoordinateWatcher クラスが使うかどうかは不明です)

複数の位置プロバイダが搭載されている場合は優先付けがされますが、その優先順序は状況に応じて変わり、例えば GPS の電波が届かない場所に移動した場合は Wi-Fi triangulation プロバイダが使われるそうです。

それらのプロバイダのいずれも使えない場合、Windows OS の「既定の場所」から情報を取得できます。Windows 10 の場合[設定]⇒[プライバシー]⇒[位置情報]と進み、下の画像の[規定値に設定]ボタンをクリックして表示される画面で設定します。

既定の場所

ちなみに自分の Windows 10 ノート PC には Wi-Fi しか搭載されていませんので、GeoCoordinateWatcher クラスが使う位置プロバイダは Wi-Fi triangulation のみということになります。

なので、自分が検証できるのは Wi-Fi triangulation プロバイダを使った場合と、Wi-Fi をオフにして「既定の場所」の情報を使った場合だけですが、実際に GeoCoordinateWatcher クラスを使ったコンソールアプリを作って試してみました。アプリのコードはこの記事の下の方に載せておきます。

まず「既定の場所」を設定します。下の画像は[規定値に設定]ボタンをクリックして表示される画面で既定の位置を設定したところです。(Wi-Fi と違う結果になるのがすぐ分かるよう今回の検証では皇居に設定しています)

既定の位置

Wi-Fi をオンにした状態でアプリを走らせたコンソール出力は以下の通りとなりました。(緯度経度は自宅の位置になるので小数点以下を xxxx で隠しています)

Permission: Unknown, Status: NoData
Permission: Granted, Status: Ready
Lat: 35.xxxxxxxxxxxxx, Long: 139.xxxxxxxxxxx

アプリを走らせるたびに計測を行うようで結果は毎回微妙に異なりますが、Google Map に緯度経度を入力して表示されたポイントを見ると計測ごとの差は 10m もありませんでした。

自宅(PC が置いてある場所)の周りに適切な Wi-Fi サービス提供者のアクセスポイントが複数存在するということが精度に影響するのでしょうが、自宅の環境ですとかなり正確に Google Map 上で自宅の位置が特定できてしまいました。悪用されるとかなりヤバそうです。(汗)

ちなみに triangulation は三角測量という意味です。自宅(PC が置いてある場所)の近くに存在する複数の Wi-Fi サービス提供者のアクセスポイントを、PC に搭載された Wi-Fi triangulation プロバイダがスキャンしてその位置を知るためのデータ (MAC アドレスという記事を見ましたが真偽不明) と電波強度を取得し、電波強度からアクセスポイントとの距離を計算し、三角測量により PC の位置を特定するというプロセスだそうです。

Wi-Fi triangulation

そのプロセスの過程でアクセスポイントの位置情報(緯度経度とか)が必要になります。もし仮に、スキャンして得たデータがアクセスポイントの MAC アドレスだとすると、MAC アドレスとアクセスポイントの緯度経度がデータベースに登録してあって、プロバイダはアクセスポイントの緯度経度を得るためにそのデータベースに照会すると言った操作が必要になるはずです。しかし、そのデータベースというのは何で、どこにあって、どのように位置情報を得ているのかは調べても分かりませんでした。

また、自分が試した限りですが、ネットに接続されているか否かは関係なく Wi-Fi がオンになっていれば正確な緯度経度データを取得できました。その時には外部のデータベースにはアクセスできないはずで、どのようにアクセスポイントの位置情報を得たのかは謎です。Windows の位置情報サービスとプライバシーの [場所の履歴] によると過去に取得した位置情報が一定時間 (Windows 10 では 24 時間) ローカルストレージに保存されるそうなので、そこから取得しているのかもしれません。(未検証、未確認です)

次に、Wi-Fi をオフにしてアプリを走らせるとコンソール出力は以下の通りとなります。下の Lat, Long の値を Google Map に入力して表示される場所を見ると「既定の場所」に設定した通り皇居となってました。

Permission: Unknown, Status: NoData
Permission: Granted, Status: Ready
Lat: 35.6839239255279, Long: 139.750550645143

以下に自分が検証に使ったサンプルコードを載せておきます。

using System;
using System.Device.Location;

namespace GeoCoordinateWatcherSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            GetLocationProperty2();
        }

        static void GetLocationProperty2()
        {
            GeoCoordinateWatcher geoWatcher = new GeoCoordinateWatcher();
            Console.WriteLine($"Permission: {geoWatcher.Permission}, " +
                              $"Status: {geoWatcher.Status}");

            geoWatcher.Start();

            // geoWatcher.Position を取得する時点で Status がまだ NoData
            // の場合は位置情報は取得できないので Ready になるまで待つ。
            // geoWatcher.Permission != GeoPositionPermission.Denied も
            // while の条件に入れないと、位置情報の取得が有効になってな
            // い場合は無限ループに陥るので注意
            while ((geoWatcher.Status != GeoPositionStatus.Ready) &&
                (geoWatcher.Permission != GeoPositionPermission.Denied))
            {
                System.Threading.Thread.Sleep(100);
            }

            if (geoWatcher.Permission == GeoPositionPermission.Denied)
            {
                Console.WriteLine($"Permission: {geoWatcher.Permission}, " +
                                  $"Status: {geoWatcher.Status}");
            }
            else
            {
                Console.WriteLine($"Permission: {geoWatcher.Permission}, " +
                                  $"Status: {geoWatcher.Status}");
                GeoCoordinate coord = geoWatcher.Position.Location;

                if (coord.IsUnknown != true)
                {
                    Console.WriteLine("Lat: {0}, Long: {1}",
                        coord.Latitude,
                        coord.Longitude);
                }
                else
                {
                    Console.WriteLine("Unknown latitude and longitude.");
                }
            }
        }
    }
}

Microsoft のドキュメント GeoCoordinateWatcher Class のサンプルコードはそのままでは動かないので注意してください。(環境によっては動くこともあるようですが)

動かない理由は、GeoCoordinateWatcher.TryStart メソッドでデータの取得が始まり即 true が返されますが、その時点では GeoCoordinateWatcher.Status はまだ NoData だからです。緯度経度データを取得するには TryStart メソッド実行後 Status が Ready になるまで待つ必要があります。

Tags: , ,

.NET Framework

App.Debug.config と App.Release.config を使った構成ファイル書き換え

by WebSurfer 10. November 2024 18:31

.NET Framework 版の ASP.NET Web アプリは構成ファイルに Web.config を使っており、Visual Studio で開発を行う場合は運用環境にデプロイする際の書き換えを定義した Web.Release.config というファイルを作っておき、Visual Studio の発行ツールを使ってのデプロイ時に自動的に書き換えることが可能になっています。

詳しくはMicrosoft のドキュメント「Visual Studio を使用した ASP.NET Web 配置: Web.config ファイル変換」を見てください。

Windows Forms アプリや WPF アプリにはそのような機能は用意されていません。しかし、ネットの記事などを読むと、ASP.NET Web アプリと同様に、書き換え定義ファイルを作って構成ファイルを書き換えることができるそうです。

という訳で、今さらながらですが、Visual Studio 2022 Community のテンプレートを使って作成したターゲットフレームワーク .NET Framework 4.8 の Windows Forms アプリおよび WPF アプリで試してみました。

(注: .NET をターゲットフレームワークにしたアプリでは App.config ではなくて appsettings.json を使うことが推奨されています。その場合の書き換え方法については先の記事「WinForms アプリで構成情報の上書き (CORE)」を見てください)

Windows Forms アプリ、WPF アプリは App.config を構成ファイルのベースに使います。App.config 自体はアプリが使う構成ファイルではないことに注意してください。書き換えを行わない場合は、App.config の内容がそのままコピーされた <プロジェクト名>.exe.config という名前のファイルが作成され、それが構成ファイルになります。

App.Debug.config と App.Release.config は、App.config との差分を指定してデバッグ用とリリース用の構成ファイルを作成するための変換ファイルになります。環境(Debug または Release)ごとに App.config の内容を指定された差分に従って書き換えて <プロジェクト名>.exe.config という名前の構成ファイルを作成し、bin\Debug または bin\Release フォルダに配置するようにします。

以下に手順を書きます。Windows Forms アプリと WPF アプリで同じ手順になります。

(1) App.Debug.config と App.Release.config の追加

App.config は Visual Stidio のテンプレートを使って作成した Windows Forms アプリ、WPF アプリのプロジェクトには最初から含まれています。

それに App.Debug.config と App.Release.config という名前の 2 つのファイルをプロジェクトに追加します。ソリューションエクスプローラーのプロジェクトノードを右クリックし、表示されたコンテキストメニューから[追加(D)]⇒[新しい項目(W)]⇒[アプリケーション構成ファイル]と進んで、App.Debug.config と App.Release.config を追加してください。

追加したらプロジェクトファイルを開いて、

<None Include="App.config" />
<None Include="App.Debug.config" />
<None Include="App.Release.config" />

<None Include="App.config" />
<None Include="App.Debug.config">
  <DependentUpon>App.config</DependentUpon>
</None>
<None Include="App.Release.config">
  <DependentUpon>App.config</DependentUpon>
</None>

に書き換えます。

これによりソリューションエクスプローラーで構成ファイルを見た時、下の画像のように階層表示されるようになります。

App.Debug.config と App.Release.config

なお、これをやらなくても、自分が試した限りですが、書き換えには影響ありませんでした。Visual Studio のソリューションエクスプローラーでの見た目の問題だけなのかもしれません。

(2) App.config の例

今回の例では App.config には以下のように接続文字列と基本情報を配置してみました。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  
  <connectionStrings>
    <add name="Database"
      connectionString="Data Source=(localdb)\mssqllocaldb;Initial Catalog=Database;Integrated Security=True"
      providerName="System.Data.SqlClient" />
    <add name="MyDB"
      connectionString="Data Source=(localdb)\mssqllocaldb;Initial Catalog=Northwind;Integrated Security=True"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="FilePath" value="C:\Users\surfe\Documents"/>
    <add key="FileName" value="original.txt"/>
  </appSettings>
</configuration>

(3) App.Debug.config の例

App.Debug.config には App.config の FileName を original.txt から development.txt に変更するための変換定義を書きます。

上にも書きましたが、App.Debug.config と App.Release.config は書き換え方法を指定する XML ファイルです。 変換操作は xdt プレフィックスにマップされる XML-Document-Transform 名前空間で定義されている XML 属性を使用して指定します。

詳しい方法は Microsoft のドキュメント「Web アプリケーション プロジェクト配置の Web.config 変換構文」が参考になると思います。(日本語版は翻訳がアレなので英語版を読むことをお勧めします)

configuration 要素に xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" という属性を追加し、書き換え方法を指定するコードを追加します。

以下の定義で FileName を original.txt から development.txt に変更します。

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="FileName" value="development.txt"
         xdt:Transform="Replace"
         xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

(4) App.Release.config の例

App.Release.config には、App.config の接続文字列を本番用に変更し、FileName を release.txt に変更し、さらにリリース版でのみ必要な情報 AdditionalInfo を追加するための変換定義を書きます。

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <connectionStrings>
    <add name="Database"
      connectionString="Data Source=Release;Initial Catalog=DB1;Integrated Security=True"
      xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
    <add name="MyDB"
      connectionString="Data Source=Release;Initial Catalog=DB2;Integrated Security=True"
      xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
  </connectionStrings>

  <appSettings>
    <add key="FileName" value="release.txt"
      xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
    <add key="AdditionalInfo" value="release version specific info"
      xdt:Transform="Insert"/>
  </appSettings>
</configuration>

(5) プロジェクトファイルに書き換え指示を追加

プロジェクトファイルの下の方にある、

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

の下に以下のコードを追加します。追加する場所に制約があるようで、場所を間違えると変換されないので注意してください。

<UsingTask TaskName="TransformXml"
  AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
  <TransformXml Source="app.config"
    Destination="$(IntermediateOutputPath)$(TargetFileName).config"
    Transform="app.$(Configuration).config" />
  <ItemGroup>
    <AppConfigWithTargetPath Remove="app.config" />
    <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
      <TargetPath>$(TargetFileName).config</TargetPath>
    </AppConfigWithTargetPath>
  </ItemGroup>
</Target>

上のコードの意味については、この記事の下の方にオマケとして書きましたので興味があれば見てください。

(6) 書き換え結果

以上で、Visual Studio でプロジェクトをビルドすると、構成マネージャーの「構成」(Debug または Release)の設定に応じて、

構成マネージャ

App.config の内容が、変換ファイル App.Debug.config または App.Release.config に従って書き換えられて <プロジェクト名>.exe.config という名前の構成ファイルが作成され、bin\Debug または bin\Release フォルダに 配置されます。下の画像はプロジェクト名が WindowsFormsApp3 という名前の Windows Forms アプリのものです。

bin フォルダの構成ファイル

この記事の例で、bin\Relese フォルダに生成される WindowsFormsApp3.exe.config は以下のようになります。期待通り App.config の内容が App.Release.config に従って書き換えられています。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
  </startup>
  
  <connectionStrings>
    <add name="Database" 
      connectionString="Data Source=Release;Initial Catalog=DB1;Integrated Security=True" 
      providerName="System.Data.SqlClient"/>
    <add name="MyDB" 
      connectionString="Data Source=Release;Initial Catalog=DB2;Integrated Security=True" 
      providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>
    <add key="FilePath" value="C:\Users\surfe\Documents"/>
    <add key="FileName" value="release.txt"/>
    <add key="AdditionalInfo" value="release version specific info"/>
  </appSettings>
</configuration>

(7) アプリの実行例

もちろんアプリのコードで構成ファイルから取得する情報には上の書き換え結果が反映されます。例えば、Windows Forms アプリの場合、以下のコードを、

using System.Configuration;
using System.Windows.Forms;

namespace WindowsFormsApp3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            this.label1.Text = ConfigurationManager
                               .ConnectionStrings["Database"]
                               .ConnectionString;

            this.label2.Text = ConfigurationManager
                               .ConnectionStrings["MyDB"]
                               .ConnectionString;

            this.label3.Text = ConfigurationManager
                               .AppSettings["FilePath"];

            this.label4.Text = ConfigurationManager
                               .AppSettings["FileName"];
            
            this.label5.Text = ConfigurationManager
                               .AppSettings["AdditionalInfo"];
        }
    }
}

Visual Studio の構成マネージャーの「構成」を Release にして実行すると以下の結果になります。

アプリを実行して構成情報を取得


以下はオマケの情報です。上の「(5) プロジェクトファイルに書き換え指示を追加」に書いたコードについて調べていろいろ分かったことがあったので、備忘録として残しておくことにしました。100% 間違いないところまで深く調べたわけではなく、想像と Copilot に聞いた話が混じっていますが。

以下にコードを再掲して説明を書きます。

<UsingTask TaskName="TransformXml"
  AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
  <TransformXml Source="app.config"
    Destination="$(IntermediateOutputPath)$(TargetFileName).config"
    Transform="app.$(Configuration).config" />
  <ItemGroup>
    <AppConfigWithTargetPath Remove="app.config" />
    <AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
      <TargetPath>$(TargetFileName).config</TargetPath>
    </AppConfigWithTargetPath>
  </ItemGroup>
</Target>

UsingTask の AssemblyFile 属性には書き換えに使うアセンブリ Microsoft.Web.Publishing.Tasks.dll へのパスが指定されており、それを用いて書き換えを行っています。Web という名前のフォルダにあるということは、もともとは ASP.NET Web アプリの web.config を書き換えるのに用いているもののようです。

Target は、変換ファイル app.$(Configuration).config が���在する場合はコンパイル後に書き換えを実行するという設定をしています。$(Configuration) は構成オプションによって Debug または Release になります。

TransformXml は、プロジェクトの App.config ファイルを変換ファイル app.$(Configuration).config をベースに書き換えて、中間出力ディレクトリ(通常 obj\Debug または obj\Release)に $(TargetFileName).config という名前で保存するという設定になっています。

$(Configuration) は構成オプションによって Debug または Release になります。$(TargetFileName) はビルドで生成される exe ファイルの名前で、<プロジェクト名>.exe となります。

ItemGroup の中の最初の AppConfigWithTargetPath Remove="app.config" は最終出力フォルダ(bin\Debug または bin\Release)にオリジナルの App.config が書き込まれないことを確実にするためだそうです。

copilot に聞いた話ですが、AppConfigWithTargetPath は bin\Debug または bin\Release フォルダを意味するわけではないそうです。copilot によると "AppConfigWithTargetPath doesn't directly refer to the bin/Debug or bin/Release folders themselves, but it is related to how the app.config file is processed and used within those output directories." とのことでした。

(自分が AppConfigWithTargetPath Remove="app.config" を削除してビルドして試した限りでは、App.config が bin\Debug または bin\Release に書き込まれることはなかったのですが・・・)

ItemGroup の中の 2 つ目の AppConfigWithTargetPath Include=... は、中間出力ディレクトリに置かれた $(TargetFileName).config ファイルを、最終出力フォルダ(bin\Debug または bin\Release)に <プロジェクト名>.exe.config という名前で配置するという設定になっています。


もう一つオマケを書きます。

x64 Native Tools Command Prompt を使ってプロジェクトファイルの $(MSBuildExtensionsPath) などの値を調べる方法です。

x64 Native Tools Command Prompt は Visual Studio Installer で「C++ によるデスクトップ開発」のワークロードを追加し、インストールの詳細で「最新の v143 ビルドツール用 C++ ATL」と「最新の v143 ビルドツール用 C++ MFC」にチェックを入れてインストールすれば、Windows の「スタート」メニューに現れるようになります。

x64 Native Tool

そのツールを使ってプロジェクトファイルの $(MSBuildExtensionsPath) などの値を調べるには、プロジェクトファイルに以下の様なコードを追加します。

<Target Name="PrintProperties">
  <Message Text="MSBuildExtensionsPath="$(MSBuildExtensionsPath)""/>
  <Message Text="VisualStudioVersion="$(VisualStudioVersion)""/>
  <Message Text="Configuration="$(Configuration)""/>
  <Message Text="IntermediateOutputPath="$(IntermediateOutputPath)""/>
  <Message Text="TargetFileName="$(TargetFileName)""/>
  <Message Text="AppConfigWithTargetPath="$(AppConfigWithTargetPath)""/>
</Target>

そして x64 Native Tools Command Prompt を起動し、プロジェクトファイルのあるディレクトリに移動してから、msbuild <プロジェクトファイル名> /t:PrintProperties とタイプして実行すれば、以下のように結果が表示されます。

Command Prompt

Tags: , , , ,

.NET Framework

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

by WebSurfer 17. January 2024 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

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  June 2025  >>
MoTuWeThFrSaSu
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

View posts in large calendar