実行中のプロセス自身のパスをとる

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

実行してるexe自身のフルパス、もしくはそれが保存されてるフォルダのパスを取りたい。

サンプルコード

// フォルダ
Console.WriteLine(Environment.CurrentDirectory);
Console.WriteLine(Directory.GetCurrentDirectory());
Console.WriteLine(System.AppDomain.CurrentDomain.BaseDirectory);

// フルパス(ファイル名込み)
Console.WriteLine(Assembly.GetExecutingAssembly().Location);
Console.WriteLine(Environment.GetCommandLineArgs()[0]);

Assembly.GetExecutingAssembly().Locationは、「発行」で「単一ファイルの作成」をすると、nullになってしまう。

その場合は、ディレクトリを取る場合はSystem.AppDomain.CurrentDomain.BaseDirectoryを使うべしとMSのページにあった。 ファイル名込みで取りたい場合は、Environment.GetCommandLineArgs()[0]でよさそう。

Taskを調べてると頻繁に出てくる「コンテキスト」とは何か?

やりたいこと

Taskとか非同期処理を調べていると、あちこちに「コンテキスト」「同期コンテキスト」「非同期コンテキスト」という言葉が出てくる。

これが、調べても調べても何のことを言っているのかわからない。

なんとなく、「コンテキスト ≒ スレッド」なのかな?と感じつつも、説明文の「コンテキスト」を「スレッド」に置き換えて読んでみてみ意味がとおらない。

コンテキストとは何なのか?調べたい。もう調べたのだが、いまだよくわからないので、真実が知りたい。

やったこと

いろいろ調べているうちに、 たまたま、下記のページに出会うことができた。とくに、「いまさら async/await」「async/await と SynchronizationContext 」を拝見して、長年の疑問がすーっと消えていった気がする。

いまさら async/await

https://tech.blog.aerie.jp/entry/2015/08/29/022932

async/await と SynchronizationContext (1)

https://tech.blog.aerie.jp/entry/2015/09/17/110258

async/await と SynchronizationContext (2)

https://tech.blog.aerie.jp/entry/2015/09/17/110425

async/await ~非同期なライブラリは楽じゃない~

https://qwerty2501.hatenablog.com/entry/2014/04/24/235849

まだ100%理解したとは言い難いが、頭の中に浮かんできた現状の理解を、とにかく忘れるまにメモする。

メモ

上のページをみればわかる、といえば一言で終わってしまうので、 現状の理解を言語化してみる。(間違い多々あるかも)

  • SynchronizationContextについて

    • .NET2.0からSynchronizationContextクラスが登場した。それが同期コンテキストと呼ばれる場合がある。
      • 「コンテキスト」とは非同期操作の実行状況や環境に関する情報を含むもの。(by chatGPT)
    • SynchronizationContext は、スレッド毎にインスタンスを持っている。
      • WPFの場合、UIスレッドは持ってる。ワーカースレッドはもってない。
    • SynchronizationContextを使うと、立ち上げたワーカースレッドが実行されるスレッドを、立ち上げた側のスレッドと同じにできたりする。
      • WPFの場合、ワーカースレッドが実行されるスレッドを、UIスレッドと同じにしたりできる。
    • WPFのUIスレッドが自分のSynchronizationContextを取得しようと思うと、SynchronizationContext.Currentから取得できる。
      • ワーカースレッドでは、SynchronizationContext.Currentにはなにも入っていない。
    • WPFではそういう感じだが、別のプラットフォーム、フレームワーク上では、全く別だったりする。(例えばASP.NET)
    • ただ、.NET6時点のTaskやasync/awaitを利用した非同期処理では、直接的にSynchronizationContextを見る機会がない。(使わなくても十分非同期処理できる)
  • Taskについて

    • Taskは、Awaitebleオブジェクトである。
    • Taskは、awaitすると、
      • Taskの処理をしている間は別スレッド(ワーカースレッド)で処理を行って、→①
      • 呼び出し元スレッドに一旦戻り、→②
      • 別スレの処理が終わったら、呼び出し元の処理の続きを呼び出し元スレッドで実行する。→③
    • その動きは、コンパイラが下記のように、処理を分解してくれて実現されてる。
      • タスクに登録された処理を別スレッドで実行する。→①に該当
      • TaskAwaiterを取得 → ★???
      • 自分自身(呼び出し元)の処理のawait以降をコールバックに登録して、いったん処理を終わる →②に該当
        • 自分自身を、await前と後に分解して、後の方をコールバックに登録する
      • タスクの処理が終わったら、登録したコールバック(自分自身の続き)を実行する →③に該当
  • TaskAwaiterについて

    • TaskAwaiterは、Taskの呼び出し元のコンテキストをキャプチャーして、
      • コンテキスト(SynchronizationContext)をキャプチャーする≒そのコンテキストがどのスレッドで行われているか、を覚えておくようなもの
    • Task処理が終わったら、SynchronizationContext.Post経由でコールバック(=呼び出し元処理の続き)を実行する
      • キャプチャーしたコンテキストのSynchronizationContext.Post経由で呼び出し元の処理を呼ぶということは、つまり呼び出し元のスレッドに戻るということ
    • という感じで、awaitの動きを実現している。
      • つまり上の★???でTaskAwaiterを取得したときに、呼び出し元のコンテキストを覚えてるから、Taskの処理が終わった後に、呼び出し元に戻ることができる。
  • SynchronizationContext の使いどころはどこか?

    • 現在のWPFでは、自分でSynchronizationContextをコードに書くことは、今まで私が出会ったことがないような何かが出てこない限り、しなくてよいと理解。
      • ただそれはコンパイラが裏でSynchronizationContextを使って、ええようにしてくれているおかげである。
      • 昔は、処理を別スレッドに投げたときに、UIを更新するためにSynchronizationContextを使って、UIスレッドに戻す、みたいなことをしてたっぽい。
  • .configureAwait(false)は何をしてるか?

    • awaitしたあと、元のスレッドに戻る動きをやめて、ワーカースレッドのほうで、await後の処理を進めるようにするということ。
    • なので、TaskをWaitしてUIスレッドを止めたあとに、Taskの処理終了時に、止まってるUIスレッドに帰ろうとすることが原因のデッドロックは起きなくなる。(UIスレッドに帰らずに、ワーカースレッドでそのまま動くから。)

結論

今のところ、「処理を実行するスレッドを操るためのもの」と理解。

備考

まとまらないが、ざっと現状理解を言葉にしてみた。

参考

上で紹介させていただいたいくつかのページ。

ありがとうございました。

WMIのクラスを全部取る・その中のプロパティ値もできるだけ取る

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

WMIでなにか値をとりたいなと思ったときに、どのクラスからどんな値が取れるのかがわからないので、いつも出だしで困る。

あまり良いやり方ではないかもしれないが、とりあえずもう全部のWMIで取れる値を全部取ってしまってから、その中になんか目的のあたいっぽいのがないかなー?という調べ方をしたい。

で、そのために、全部のクラス(のインスタンス?)から、全部のプロパティの値をとって全部表示するようなことがしたい。

※WMIのクラス、とか、インスタンス、とか、プロパティ、とか言っているが、それが用語として正しいのかはっきりわかってないのが今の現状。ゆえにそんな変な調べ方しかできないのだが、知識を付けるのは追々やるとして、いったんそういう変なことができるのかどうか調べてみる。。。

※以前も、似たようなことを言っていて、そのときはWbemTestというツールを使ってみた。(が、以前目的のモノにすぐたどり着けない。。。)

https://zenn.dev/tera1707/articles/87bf99621770e1

やったこと

ObjectQuery()をnewするときに、

var query = new ObjectQuery("SELECT * FROM meta_class");

というクエリにする(FROMにmeta_classを指定する)と、WMIのクラスを全部取ってこれる様子。

下のコードのようにすれば、全部のインスタンスの値が取れてるっぽい。 つまり、Wim32_BIOSとかWin32_ComputerSystemとか、もろもろいつも使うようなクラスの値が全部とれてるっぽい。

ぽい、というのは、下記のコードを実行すると、途中でクォータ違反ですというエラーが出てしまい、途中で終わってしまい、全部みれないため。

※どうも、連続して大量にWMIでデータをとると、そういうエラーになるらしい。

public void GetInfo()
{
    var scope = new ManagementScope("\\\\.\\ROOT\\cimv2");

    //var query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
    var query = new ObjectQuery("SELECT * FROM meta_class");

    var searcher = new ManagementObjectSearcher(scope, query);

    using (ManagementObjectCollection queryCollection = searcher.Get())
    {
        try
        {
            foreach (ManagementObject mo in queryCollection)
            {
                Console.WriteLine($"■{mo.ClassPath}");

                if (mo.ClassPath.ToString().Contains("Win32_LocalTime") || mo.ClassPath.ToString().Contains("Win32_NTLogEvent") || mo.ClassPath.ToString().Contains("Win32_SoftwareElement"))
                    continue;

                ManagementClass mc = new ManagementClass(mo.ClassPath);

                foreach (ManagementObject c in mc.GetInstances())
                {
                    Console.WriteLine($"  ▼{c.ClassPath}");

                    PropertyDataCollection props = c.Properties;
                    // WMIのオブジェクトのプロパティを列挙する
                    foreach (PropertyData p in props)
                    {
                        Console.WriteLine($"   {p.Name,-30} : {p.Value}");
                    }
                    c.Dispose();
                }
                mo.Dispose();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
    searcher.Dispose();

    Console.WriteLine("finished.......");
}

で、途中で落ちるのでは困る。

なので、上記コードでコメントアウトしている

    //var query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
    var query = new ObjectQuery("SELECT * FROM meta_class");

の部分を反対にして、Win32_ComputerSystemの情報を取るようにしてやると、まぁWin32_ComputerSystemに含まれる値はうまいこと取れる。

本番でWMIでデータをとるときは、全部とってくるようなことはしないので、結局大体使えそうなクラスを見定めておいて、SELECT * FROM 使えそうなクラス名にするしかなさそう。

※ちなみに、私のPCで上記コードを実行すると、400万行以上のテキストになった。 本番で使いそうなクラスの値は、たまたま含まれていたが、途中では終わっている様子。

使えそうなクラス名を探すとき

上のコードの、プロパティを全部とる部分を消して、

public void GetInfo()
{
    var scope = new ManagementScope("\\\\.\\ROOT\\cimv2");

    var query = new ObjectQuery("SELECT * FROM meta_class");

    var searcher = new ManagementObjectSearcher(scope, query);

    using (ManagementObjectCollection queryCollection = searcher.Get())
    {
        foreach (ManagementObject mo in queryCollection)
        {
            Console.WriteLine($"■{mo.ClassPath}");
            mo.Dispose();
        }
    }
    searcher.Dispose();

    Console.WriteLine("finished.......");
}

としてやると、クラス名は全部取れる様子。
wbemtestのツールと、上記で吐いたクラス一覧を合わせてみるしかないか。。。。

※上記で吐いたクラスだけの一覧は、私のPCでは1300行程度だった。

ManagementClassを使わないと中身がnullになる ← 追記:間違いだった ←追記:やっぱり間違いじゃなかった(nullになる)

以前調べた、WMIでCPU時間を取る方法のコードと今回のコードだと、foreach (ManagementObject mo in queryCollection)の中で、

今回は

ManagementClass mc = new ManagementClass(mo.ClassPath);
foreach (ManagementObject c in mc.GetInstances())
{・・・

という感じで、ManagementClassをnewして、そいつでGetInstanceをしている部分がことなる。 (CPU時間のときは、それがなかった)

今回は、Win32_ComputerSystemクラスのプロパティの値を取りたかったのだが、ManagementClassを使わないと(前回のように、searcher.Get()でとったManagementObjectCollection の中身を直接見る感じだと)、Win32_ComputerSystemのプロパティの値は全部nullだった。

                ManagementObjectCollection queryCollection = searcher.Get();

                foreach (ManagementObject m in queryCollection)
                {
                    // WMIオブジェクトのプロパティを取得して表示
                    Console.WriteLine("{0}, {1}, {2}, {3}, {4}", m["Name"], m["IDProcess"], m["PercentProcessorTime"], m["PrivateBytes"], (uint.Parse(m["IOWriteBytesPerSec"].ToString())));
                }

クラス(ガワ)には中身の値がないけど、とインスタンス(中身)にはある、みたいな違いがあるのかもしれない。

※現状、勉強不足でよくわかっていない。(CPU時間はそれでとれてたのに、なぜ今回はnullなのか、等)

ManagementClassを使わなくても値が取れた

ManagementClassを使わないと中身がnullになる、は間違いだった。 WMIでCPU時間を取る方法のコードと似たやり方で、値もとれた。

実験していたときに、なにか変なことをしていたっぽい。

下記で、うまく取れた。

public void GetInfo()
{
    var scope = new ManagementScope("\\\\.\\ROOT\\cimv2");

    var query = new ObjectQuery("SELECT * FROM Win32_ComputerSystem");
    //var query = new ObjectQuery("SELECT * FROM meta_class");

    var searcher = new ManagementObjectSearcher(scope, query);

    using (ManagementObjectCollection queryCollection = searcher.Get())
    {
        try
        {
            foreach (ManagementObject mo in queryCollection)
            {
                Console.WriteLine($"■{mo.ClassPath}");
                PropertyDataCollection props = mo.Properties;

                foreach (PropertyData p in props)
                {
                    Console.WriteLine($"   {p.Name,-30} : {p.Value}");
                }
                mo.Dispose();
            }
        }
        catch (Exception ex) 
        {
            Console.WriteLine(ex);
        }
    }
    searcher.Dispose();

    Console.WriteLine("finished.......");
}

※やっぱり、その後もう一度試したらまた値が空になっていた。 なにか条件がある??一度インスタンス取得したら、そのあとはインスタンス取らなくても値が入ってる、とか?

備考

WMIについての知識がたりないせいで、なにか無理やり感、頓珍漢なことしてる感を感じる。

おいおい勉強必要。

参考

全部のWMIのクラス?を取ってくる https://mycsharp.de/forum/threads/69845/lan-adapter-auslesen?page=1

PropertyData クラス https://learn.microsoft.com/ja-jp/dotnet/api/system.management.propertydata?view=dotnet-plat-ext-7.0

WMI クラスの取得

https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/retrieving-a-class

WMI インスタンスの取得

https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/retrieving-an-instance

null許容(null許容値型/null許容参照型)についてのメモ

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

タイトルとは別のしらべものをしているときに、 null許容について知らないことがいろいろわかった気がしたので、理解したかどうかはおいておいて、忘れたくないものを取り急ぎメモ。

別のしらべもの → 型パラメーターの制約条件

メモ

  • C#に「null許容」自体は、昔からある。多分C#2.0頃から。

  • 但し、そのころでいう「null許容」とは、今(C#8.0以降)でいうところの「null許容値型」のこと。

  • null許容値型は、内部で「Nullable型」になっている。

    • 例えばint型だと、「int?型」と「Nullable<int>」は同じ。
  • 参照型には、元々nullを入れることができていた。

    • nullポインタ的なもの?
  • C#8.0以降は、「null許容参照型」が」追加された。

  • VisualStudio2022の下の画面で「Null許容」を有効/無効できるが、その「Null許容」と言われているのが、C#8.0で追加された「null許容参照型」のこと。

  • つまりこの画面は、「null許容参照型」の有効/無効が切り替えられる画面である。

  • null許容参照型を有効にすると、

    • まずnull非許容(?のついてない)参照型にnullを入れることを禁止したうえで、
    • null許容参照型には、nullを入れられるようにする
  • VisualStudioで「Null許容」の設定を無効にしても、C#2.0のころからできていたint?などのnull許容値型が書けなくはならない。

    • あくまで、VSのその設定は「null非許容参照型にnullを入れることを禁止」するかどうかの切り替えである。
  • null非許容参照型にnullを入れることを禁止、といっても、標準ではワーニングがでるだけで、エラーにはならない。出るワーニングはこんな感じ。(ワーニングをエラー扱いする設定だとエラーになるので、破壊的ではある)

  • 参照型に?を付けると、null許容参照型になる。

  • ただし、null許容参照型Tは、null非許容参照型T?と、中身としては変わらない。

    • 昔からnullが入るので、実は中身的には変わってないらしい
    • ?は、コンパイラに、null許容なのか非許容なのかを伝えるだけの役割
    • 値型は、null許容にするとNullable<T>になり、全然別物になる

参考

ufcppを見て勉強した。

null 許容参照型

https://ufcpp.net/study/csharp/resource/nullablereferencetype/

null許容値型(Nullable 型)

https://ufcpp.net/study/csharp/sp2_nullable.html

型パラメーターの制約条件(where T : 〇〇)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

あるとき、ジェネリクスで、

  • T型の戻り値をもつメソッドを引数にとって、
  • 引数で受けたメソッドを実行する。
  • 実行したときに例外なく実行出来たら、そのまま引数で受けたメソッドが返した値を返す
  • もし例外が起きたら、nullを返す

というメソッドを作ろうとした。

具体的には、下記のコードの中のMyMethod1()がそれ。

namespace ConsoleApp2
{
    internal class Program
    {
        enum MyEnum
        {
            Value1,
            Value2,
            Value3,
        }

        static void Main(string[] args)
        {
            MyEnum enm = MyEnum.Value1;

            var a = MyMethod1(() =>
            {
                return enm;
            });
        }

        private static T? MyMethod1<T>(Func<T> action)
        {
            try
            {
                return action.Invoke();
            }
            catch (Exception ex)
            {
                return null;// ←★ここでエラーが出た
            }
        }
    }
}

そうすると、下記のようなエラーが出た。

エラーの中では、'default(T)'を使うように言っているが、 上の例で'MyMethod1()'が呼ばれた場合だと、Tは'MyEnum'になるので、'default(T)'は、MyEnum.Value1になって、nullにはなってくれない。

どうにかしてnullを返す方法はあるか?調べる。

解決方法

「型パラメーターの制約条件」を使う。

具体的には、'MyMethod1()'を下記のように変える。

private static T? MyMethod1<T>(Func<T> action) where T : struct // ←★コレを付ける!
{
    try
    {
        return action.Invoke();
    }
    catch (Exception ex)
    {
        return null;
    }
}

'where T : struct'を付けると、'T'が値型であることになり、エラーが解消される。

なんでエラーが解消されるのか?

以下、私の理解。

まず、enumは、値型である。→参考

値型を、上のMyMethod1()に渡すという前提で、NGなパターン、OKなパターンについて考えてみた。

NGパターン①:戻り値をTにして(戻り値に?はナシ)、制約をつけていない場合

private static T MyMethod1<T>(Func<T> action)

というパターンについて、

制約(where)を付けていないと、MyMethod1<T>()を使う側で、Tには

  • null許容の値型
  • null非許容の値型

のどちらも入れることができる。 →参照

null許容の値型がTに入ってきた場合は、nullを返せるのでreturn nullはおかしくないが、

null非許容の場合は、Tにはnullを入れられないので、上に挙げたエラーになる。

NGパターン②:戻り値をT?にして、制約をつけていない場合

private static T? MyMethod1<T>(Func<T> action)

というパターンについても、パターン①と同じエラーが出る。(Null非許容の値型である可能性があるため・・・)

これが、ずっとなんでエラーになるのかがわからなかったのだが、こちらのページの、

https://ufcpp.net/study/csharp/sp2_nullable.html

「null許容型にできるのは、null許容型を除く値型のみ」「"多重にnull許容"はできない」というのを見て、なるほどと思った。

つまり、Tにnull許容の値型(例えばint?とか)を入れてしまうと、T?int??的な扱い(そんなことは書けないが)になるが、それはC#では不可なので、エラーになる。

と、理解した。(あってるかな...)

OKパターン:戻り値をT?にして、struct(値型)制約をつける場合

「解決方法」のところに書いたやつ。これで、思ったように動く。

private static T? MyMethod1<T>(Func<T> action) where T : struct

Tにstruct制約を付けると、Tは確定で「null非許容の値型」になる。

なので、NGパターン②で挙げたような多重null許容になることはなく、「null許容の値型」を返すことができる。と理解。

これで、今回やりたいことはできた。

思ったこと

「null許容」というものができた経緯は、いろいろ歴史があったようで、そのためか理解するのも個人的に結構難しい。まだ理解しきれてない感じがする。

少しずつ勉強しようと思う。

「型パラメータ制約」のまとめ

下記のMS公式に、制約の種類と意味が載っている。

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

where T : structは、「構造体」縛りではなくて「値型」で、

where T : classは、「クラス」縛りではなくて「参照型」の様子。

うまく使えば、ジェネリクスで作ったメソッドなどの使用範囲を適切に絞って、変な使い方されてバグる、みたいなのを減らせそう。

参考

C#8.0世代の総称型制約 ジェネリクスとnull許容性の関係

https://qiita.com/muniel/items/daa819c64ca66f152a39

MS公式 型パラメーターの制約 (C# プログラミング ガイド)

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters

値型と参照型

https://ufcpp.net/study/csharp/oo_reference.html

C#ジェネリック メソッドから NULL を返すにはどうすればよいですか?

https://stackoverflow.com/questions/302096/how-can-i-return-null-from-a-generic-method-in-c

.net Frameworkの中身のコードを見る

もくじ
https://tera1707.com/entry/2022/02/06/144447

# やりたいこと

C++で、Windowメッセージ(WM_〇〇みたいな)をうけたことをきっかけにして何か処理を行う、みたいなことをしていたのを、C#に移植したいときに、

同じようなきっかけ(イベント)を拾えるものが.netにあるのかどうかを見たい。

で、そのために、.net frameworkの中身を見たい。

(本当は.net6とか7のコードがみたいけど、とりあえず.net frameworkが見れることをしったのでメモ。)

やったこと

下記で見れる。

.netFW

https://referencesource.microsoft.com/

2023/8月末時点ではこんな感じだった。

.net core以降?のはここで見れるっぽい。

https://source.dot.net/#Microsoft.Win32.SystemEvents/artifacts/obj/Microsoft.Win32.SystemEvents/stub/Debug/net9.0/Microsoft.Win32.SystemEvents.ForwardedTypes.cs,320537fc0dd26bd7

参考

.NET Framework のライブラリのコードや実装を確認したい

https://www.ipentec.com/document/windows-dot-net-framework-reference-library-sourcecode

次回のWindows起動時に消してほしいファイルを登録して、消してもらう

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

保存してあるファイルについて、
今すぐは消さない(消せない)けど、次回Windows起動時に、OSに消してもらうということができるらしい。

普通は、アプリは自分自身(exe)を消したりはできないけど、自分自身の削除をOSにお願いしておいて、次回起動時に消してもらう、とかが出来そう。やってみる。

やりかた

MoveFileEx()関数を使う。

サンプルコード

#include <iostream>
#include <Windows.h>

int main()
{
    WCHAR Path[MAX_PATH + 1];

    // 自分自身のexeのパスを取得
    GetModuleFileName(NULL, Path, MAX_PATH);

    // 次回OS起動時の自分自身の削除をOSに予約する
    MoveFileEx(Path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);

    // 管理者で起動していないとエラーコード5になる
    std::cout << GetLastError() << std::endl;
}

MoveFileEx()を、下記のようにして呼ぶと、やりたいことができる。

  • 第一引数に、次回OS起動時に削除したいファイルパスを入れる
  • 第二引数は、NULLにする
  • 第三引数に、MOVEFILE_DELAY_UNTIL_REBOOTを指定する

■注意

管理者でアプリを実行していないとエラーになる。 その際のGetLastError()のエラーコードは、5(アクセスが拒否されました。)。

管理者で上記を実行すると、次回OS起動時に、そのファイルは消えている。

ただ、まだ試してないが、ネットワーク上のファイルを指定するのはNGっぽい。


https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-movefileexa

参考

MoveFileExA 関数 (winbase.h)

https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-movefileexa

GetLastError()のエラーコードの一覧のありか

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

WindowsC++でコードを書いていて、GetLastErrorでエラーコードを見たときに、そのコードの意味を調べるのだが、毎回どこ見たらいいんだったっけ?となるのでメモっておきたい。

エラーコード情報源

GetLastError()のエラーコード一覧

MS公式

https://learn.microsoft.com/ja-jp/windows/win32/debug/system-error-codes--0-499-

Windows Error Codes

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/1bc92ddf-b79e-413c-bbaa-99a5281a6c90

GetLastErrorメッセージ一覧

http://gallery-code.blogspot.com/2010/05/getlasterror.html

標準出力/標準エラー出力をリダイレクトする

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

今更、windowsには「標準出力」だけではなく「標準エラー出力」というものがあるのを知った。

で、日ごろ使っているwindowsのコマンドが出す文言も、「標準出力」を通して表示されているものと、「標準エラー出力」を通して表示されているものがあるのを知った。

で、それをリダイレクトしてファイル出力する方法を知ったので、メモしておく。

やったこと

MSのサンプルにあったように、dir file.xxx というコマンドを実行して、その2つの出力をリダイレクトしてみる。

dir file.xxx は、カレントディレクトリの中でfile.xxxというファイルの情報を探して、ファイルがあればその情報を表示するコマンドだが、ファイルがないと下記のように、普通のメッセージ(標準出力)と、エラーのメッセージ(標準エラー出力)を出す。

基本

標準出力、標準エラー出力には番号があって、

である。

下記のように、番号を指定してリダイレクトすると、それぞれを個別orまとめてリダイレクトできる。

標準出力をファイルにリダイレクト

標準出力をa.txtというファイルにリダイレクトする。

標準出力のみリダイレクトなので、標準エラー出力はコンソールにそのまま表示される。

dir file.xxx > a.txt

もしくは

dir file.xxx 1> a.txt

※「1」は省略できる。

標準エラー出力をファイルにリダイレクト

標準出力をファイルにリダイレクトする。

標準エラー出力のみリダイレクトなので、標準出力はコンソールにそのまま表示される。

dir file.xxx 2> a.txt

標準出力と標準エラー出力の両方をファイルにリダイレクト

標準出力をファイルにリダイレクトする。

標準エラー出力のみリダイレクトなので、標準出力はコンソールにそのまま表示される。

dir file.xxx 1> a.txt 2>&1

もしくは

dir file.xxx 2> a.txt 1>&2

標準出力と標準エラー出力の両方をnulにリダイレクト(出力を抑制する)

標準出力、標準エラー出力を捨てて、表示されないようにする。ファイルにも保存しない。

dir file.xxx 1> nul 2>&1

参考

標準出力 (stdout) と標準エラー (stderr) への出力をファイルに保存する

これを見たらすべてわかる

https://maku77.github.io/windows/io/redirect-stdout-and-stderr.html

MS公式

https://learn.microsoft.com/ja-jp/troubleshoot/developer/visualstudio/cpp/language-compilers/redirecting-error-command-prompt

C#コードから起動したプロセスの標準入力に、なにか入力する(Y/Nみたいなのを自動で入力する)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

以前の記事でやったようなのを、C#から起動したコマンドでもやりたい。

コマンドを起動した後に、YesかNoかを聞かれる場合に、自動で「Y」と入力したことにしたい。

サンプルコード

実験用に、標準出力と標準エラー出力に、出力を行ったあと、標準入力から読み込みを行う子プロセスと、 それを呼び出して、そいつの標準入力に対して入力を行う親プロセスを作ってみた。

親プロセス

using System.Diagnostics;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var si = new ProcessStartInfo()
            {
                FileName = @"TestProcess.exe", 
                //CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true, //★標準入力を渡せるようリダイレクトさせる
                UseShellExecute = false,
            };
            
            using (var proc = new Process())
            {
                proc.StartInfo = si;

                proc.OutputDataReceived += (sender, ev) =>
                {
                    Debug.WriteLine($"標準出力 {ev.Data}");
                };
                proc.ErrorDataReceived += (sender, ev) =>
                {
                    Debug.WriteLine($"標準エラー出力 {ev.Data}");
                };

                proc.Start();
                proc.BeginErrorReadLine();
                proc.BeginOutputReadLine();

                // ★入力のストリームを取得
                using(var sw = proc.StandardInput)
                {
                    if (sw.BaseStream.CanWrite)
                        sw.WriteLine("ABC");
                }

                proc.WaitForExit();
            }

            Debug.WriteLine("プログラム終了");
        }
    }
}

子プロセス

namespace TestProcess
{
    internal class Program
    {
        static void Main(string[] args)
        {

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(i.ToString());
                Thread.Sleep(100);
            }

            for (int i = 0; i < 5; i++)
            {
                Console.Error.WriteLine((i+10).ToString());
                Thread.Sleep(100);
            }

            var input = Console.ReadLine();
            Console.WriteLine("標準入力を受け取りました:" + input);
        }
    }
}

親プロセスの実行出力結果

標準出力 0
標準出力 1
標準出力 2
標準出力 3
標準出力 4
標準エラー出力 10
標準エラー出力 11
標準エラー出力 12
標準エラー出力 13
標準エラー出力 14
標準出力 標準入力を受け取りました:ABC
標準エラー出力 
標準出力 
プログラム終了

子プロセスが、下から4行目で「標準入力を受け取りました:ABC」を出力するが、

親が子の標準出力を自分にリダイレクトしてるので、結局親側のログに残る。 (子側のコマンドプロンプト黒画面には出ない。)

参考

DOSコマンドを実行し出力データを取得する

https://dobon.net/vb/dotnet/process/standardoutput.html

コンソールアプリの、標準出力/標準エラー出力への出力を受け取る(非同期版)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

以前の記事で、コンソールアプリの標準出力を受け取る方法を調べた。

今回は、それを非同期で出来るというのを見たので、やり方をメモしておく。

サンプルコード

実験用に、標準出力と標準エラー出力に、100msおきに出力を行う子プロセスと、 それを呼び出して出力を受け取る親プロセスを作ってみた。

親プロセス(要点)

子プロセスを呼び出して、そこから出力される標準出力、標準エラー出力を受け取る。

using System.Diagnostics;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var si = new ProcessStartInfo()
            {
                // 実行するコマンド(プロセス名)
                FileName = @"TestProcess.exe", 
                // 渡す引数
                Arguments = "a.txt",
                // ウインドウ無しならtrue
                CreateNoWindow = true,
                // 標準出力をこのアプリで受け取るよう指定
                RedirectStandardOutput = true,
                // 標準エラー出力をこのアプリで受け取るよう指定
                RedirectStandardError = true,
                UseShellExecute = false,
            };
            
            using (var proc = new Process())
            {
                proc.EnableRaisingEvents = true;
                proc.StartInfo = si;

                proc.OutputDataReceived += (sender, ev) =>
                {
                    if (ev.Data is not null)
                        Debug.WriteLine($"標準出力 {ev.Data}");
                    else
                        Debug.WriteLine($"標準出力 ev.Data がnull");
                };
                proc.ErrorDataReceived += (sender, ev) =>
                {
                    if (ev.Data is not null)
                        Debug.WriteLine($"標準エラー出力 {ev.Data}");
                    else
                        Debug.WriteLine($"標準エラー出力 ev.Data がnull");
                };
                proc.Exited += (sender, ev) =>
                {
                    Debug.WriteLine($"終了イベント到来");
                };

                // プロセス起動
                proc.Start();
                
                // 非同期出力読出し開始
                proc.BeginErrorReadLine();
                proc.BeginOutputReadLine();

                // 終了まで(同期的に)待つ
                proc.WaitForExit();
            }

            Debug.WriteLine("プログラム終了");
        }
    }
}

子プロセス(親から呼ばれるプロセス(TestProcess.exe))

100msおきに標準出力に数字を出力し、 その後100msおきに標準エラー出力に数字を出力するプログラム。

namespace TestProcess
{
    internal class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(i.ToString());
                Thread.Sleep(100);
            }

            for (int i = 0; i < 5; i++)
            {
                Console.Error.WriteLine(i.ToString());
                Thread.Sleep(100);
            }
        }
    }
}

実行結果

標準出力 0
標準出力 1
標準出力 2
標準出力 3
標準出力 4
標準エラー出力 0
標準エラー出力 1
標準エラー出力 2
標準エラー出力 3
標準エラー出力 4
標準出力 ev.Data がnull
標準エラー出力 ev.Data がnull
終了イベント到来
プログラム終了

※終了時、最後にnullが送られてくる?実験用子プロセスのプログラムの作りが悪いだけかも?

参考

EnableRaisingEvents フラグ

プロセスが終了したときに、Exited イベントを発生させるかどうかのフラグ。

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.enableraisingevents?view=net-7.0

非同期外部プロセス起動で標準出力を受け取る際の注意点

このぺージのサンプルでは、CancellationTokenのCancel待ちをして、プロセス終了を待っていたが、今回はProcessをproc.WaitForExit();する形にしてみた。試した限り、どちらでも同じ挙動になっていた。

https://qiita.com/skitoy4321/items/10c47eea93e5c6145d48

コンソールアプリの、標準出力への出力を受け取る

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

別のコンソールアプリのexeを実行したときに、

戻り値ではなくて、コマンドプロンプトで実行したときに黒窓に出てくるアレを受け取りたい。

例えば、ipconfigのコマンドだと、下記のような文字列を受け取りたい。

やり方

ProcessStartInfoのRedirectStandardOutputをtrueにしたものを、

Process.Start()に渡して実行させ、そのプロセスのStandardOutputを見る。

下記が、サンプルコード。

こちらのコードほぼそのまんまです。ありがとうございます。

using System.Diagnostics;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 実行するファイル
            string command = @"TestProcess.exe";

            var psInfo = new ProcessStartInfo();

            psInfo.FileName = command;
            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;

            psInfo.RedirectStandardOutput = true; // 標準出力をリダイレクト
            psInfo.RedirectStandardError = true;  // 標準エラー出力をリダイレクト

            var p = Process.Start(psInfo);
            string output = p.StandardOutput.ReadToEnd();   // 標準出力の読み取り
            string errOutput = p.StandardError.ReadToEnd(); // 標準出力の読み取り

            Debug.WriteLine(DateTime.Now.ToString() + " " + output);
            Debug.WriteLine(DateTime.Now.ToString() + " " + errOutput);
        }
    }
}

※↑で呼んでいる「TestProcess.exe」は、こちらで作ったものと同じ。

非同期版はこちら

https://tera1707.com/entry/2023/08/16/220208

参考

コンソール・アプリケーションの出力を取り込むには?

https://atmarkit.itmedia.co.jp/ait/articles/0710/11/news123.html

別のexeを、自分のアプリ(exe)に取り込んで、好きな時に吐き出して実行する

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

自分が作ったプログラムのexeと同じ階層に、あらかじめ指定のファイルを配置しておきたい場合に、 下図のように、ソリューションエクスプローラーにファイルを追加して、そのプロパティで「出力ディレクトリにコピー」を「常にコピーする」にしておくと、ビルドしたときに、自動的に、exeと同じ階層に、そのファイルがコピーされてくれる。

この画像では、例としてpngファイルにしたが、 今回は、pngではなく、別のexeを抱え込んで、かつあらかじめ配置はせずに、

  • アプリ(exe)にその別のexeファイルを抱え込んでおいて、
  • 通常はその別のexeは見えないようにしておいて、
  • アプリの任意のタイミングでそのexeファイルを具現化?して、
  • そのexeファイルを実行する

ようなことをしたい。

前提

  • .NET6
  • コンソールアプリ使用

やり方

VisualStudioの「リソース」の機能を使った。

リソースを追加する

まず、プロジェクトを右クリックし、[追加] > [新しい項目]を押す。

出てきた選択画面で、「リソースファイル」を選ぶ。

そうすると、プロジェクトの下に「Resource1.resx」ができる。

ソリューションエクスプローラーの中の「Resource1.resx」をダブルクリックすると、リソースの画面が開く。 ここまでは、以前調べたローカライズのためのreswと同じやり方。

下記の画面が開いたら、アクセス修飾子「public」にする。

次に、「リソースの追加」を押し、「既存のファイルの追加」を押す。

今回は、「別のexe」として、ファイル比較でよく使うDF.exeでやってみるので、出てきた選択画面で、DF.exeを選ぶ。

そうすると、自動で「Resources」フォルダができて、その中に今選択したファイルが入る。

resx画面の方で追加したファイルをクリックして選択すると、下図のようなプロパティが表示される。
Typeが「System.Byte[]」になっている。

Resource1.Designer.csを開くと、追加したリソースがこんな感じで定義されている。
byte[]のリソースになっている。

これで、リソースの追加は終わり。

追加したリソースをコードから呼ぶ

下記のようなコードを書いた。

using System.Diagnostics;

namespace BinTorikomi
{
    internal class Program
    {
        private static string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

        static void Main(string[] args)
        {
            // リソースを取ってくる
            var exe = Resource1.DF;

            // 出力先のパスを決める
            var outputExePath = Path.Combine(desktopPath, "DF2.exe");

            // リソースを書き出す
            using (var fs = new FileStream(outputExePath, FileMode.Create))
            using (var sw = new BinaryWriter(fs))
            {
                sw.Write(exe);
            }

            // 書き出したファイルを実行する
            Process.Start(outputExePath);

            Console.ReadLine();
        }
    }
}

まず、これで、元のDF.exeを全部バイナリにしたバイト列を取れる。(型はbyte[]

var exe = Resource1.DF;

それを取れたら、あとは、DF.exeを復元するには、ただそのバイト列をファイルとして書き出せばよい。 (書き出し方の参考→ファイルに文字列/バイナリデータを読み書きする

で、書き出したら、Process.Startでそのファイルを実行する。

これで出来上がり。

参考

ファイルに文字列/バイナリデータを読み書きする

https://qiita.com/tera1707/items/65025aca202ef0535fa5

よく使うgitコマンド

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

普段tortoise gitを使っていて、gitコマンドはあまり意識せずに使えてしまってるが、 たまに細かいことをしようと思うときや、別のgitクライアントソフトを使ってる人からgitに関する質問をされると、 全然話が通じなくなってしまうので、ちょっとコマンドも覚えときたい。

git push origin ブランチ名

リモートに、指定のブランチをpushする。

git push origin ブランチ名 -f

リモートに、指定のブランチを強制的にpushする。

rebaseしたあとに、元のブランチと派生元が変わったときに、 そのままだとpush時にエラーがでるので、どうしてもpushしたいときは、強制的にpushする必要がある。

たとえば、ブランチ1をプルリクエストしたあとに、ブランチ2のプルリクがでて、先に2のほうがdevelopにマージされた。 その後、ブランチ1も引き続きプルリクしてるが、マージの前に先にブランチ2の変更を取り込みたい、というときに、 ブランチ1を最新のdevelopにrebaseする。 rebaseしたローカルのブランチは、リモートにpushする際にエラーになる(ブランチの派生元が変わってるから)ので、強制的にpushする。

そうすれば、ブランチ1のプルリクエストはそのまま使うことができる。

git reset <コミットハッシュ> --hard

今のブランチを、指定の(過去の)時点のコミットに巻き戻したいときに使う。

参考

  • ブランチが指定コミットに巻き戻る
  • ステージングエリアが巻き戻る
  • ワーキングディレクトリが巻き戻る

→要するに、全部巻き戻る。

手元のワーキングディレクトリにも、変更は残らず、全部指摘のコミット時点に戻ってる。 ※リモートのブランチには、この時点ではまだ残ってるので、「消えてもた!」と焦らなくてもOK。

git reset <コミットハッシュ> --soft

今のブランチを、指定の(過去の)時点のコミットに巻き戻したいときに使う。

ただし、hardとは戻り具合が違う。

  • ブランチが指定コミットに巻き戻らない
  • ステージングエリアが巻き戻らない
  • ワーキングディレクトリが巻き戻る

tortoise gitを使ってると、

  • 元のコミットから、指定のコミットまでの変更点が、手元のワーキングディレクトリに残る。
    • 赤マルのファイルが残る

という感じ。

tortoise gitを使ってる上では、「ステージングエリア」はあまり意識しない(ファイルをAddするときぐらいな)感じなので、ほぼほぼ「--hardとの違いは、手元ワーキングディレクトリに変更差分がのこるかどうか」な感じ。

git branch -m <現ブランチ名> <新ブランチ名>

今のブランチ名を変更したいときに使う。

インターフェース・継承周りの実験②

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

前回記事の続き。

コードと資料

コード

https://github.com/tera1707/InterfaceJikken

資料

https://github.com/tera1707/Siryou/blob/master/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%83%BB%E7%B6%99%E6%89%BF%E5%91%A8%E3%82%8A%E3%81%AE%E5%AE%9F%E9%A8%93.xlsx

前提

  • VisualStudio2022
  • .NET6

やること

このようなコードがある。

using System.Diagnostics;

namespace InterfaceJikken
{
    internal class Program
    {
        static void Main(string[] args)
        {
            {
                MyClass2 asClassInstance = new MyClass2();
                IMyInterface2 asInterface = new MyClass2();

                Debug.WriteLine("mc2");
                asClassInstance.Method1_1();                    // 1

                Debug.WriteLine("mc2 class");
                ((MyClass1)asClassInstance).Method1_1();        // 2
                ((MyClass2)asClassInstance).Method1_1();        // 3

                Debug.WriteLine("mc2 if");
                ((IMyInterface1)asClassInstance).Method1_1();    // 4
                ((IMyInterface2)asClassInstance).Method1_1();    // 5

                Debug.WriteLine("mi2");
                asInterface.Method1_1();                        // 6

                Debug.WriteLine("mi2 class");
                ((MyClass1)asInterface).Method1_1();            // 7
                ((MyClass2)asInterface).Method1_1();            // 8

                Debug.WriteLine("mi2 if");
                ((IMyInterface1)asInterface).Method1_1();        // 9
                ((IMyInterface2)asInterface).Method1_1();        // 10
            }
        }
    }

    internal interface IMyInterface1
    {
        void Method1_1();
    }
    internal interface IMyInterface2 : IMyInterface1
    {
        // 実質、MyInterface1と同じものを持ったinterface
    }
#if true // パターン1  親クラスのメソッドをnewする
    internal class MyClass1 : IMyInterface1
    {
        public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");  // ①
    }

    internal class MyClass2 : MyClass1, IMyInterface2
    {
        public new void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ②
    }
#endif

#if false // パターン2 親interfaceのメソッドとして明示的実装する
    internal class MyClass1 : IMyInterface1
    {
        public void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");  // ①
    }

    internal class MyClass2 : MyClass1, IMyInterface2
    {
        void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ②
    }
#endif

#if false // パターン3 overrideする
    internal class MyClass1 : IMyInterface1
    {
        public virtual void Method1_1() => Debug.WriteLine(" Method1_1 of MyClass1");  // ①
    }

    internal class MyClass2 : MyClass1, IMyInterface2
    {
        public override void  Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ②
    }
#endif
}

ポイントは、

  • interfaceが2つある。(IMyInterface1IMyInterface2
  • IMyInterface2IMyInterface1を継承している。
  • MyClass1は、IMyInterface1を実装する。
  • MyClass2は、MyClass1を継承し、かつIMyInterface2を実装する。

このコードには、#ifで分けた3つのパターンがあるが、それぞれそのMyClassとMyInterfaceに対して、

  • パターン1:親クラスのメソッドをnewする
  • パターン2:親interfaceのメソッドとして明示的実装する
  • パターン3:overrideする

ということをする。

実行すると、それぞれこういう出力になる。 (親(MyClass1)の方を通ったのを①、子(MyClass2)の方を通ったのを②としている)

この結果で気になったところ

以下で「※1」とか「※2」と言っているのは、上の表中のセルの中の※1や※2を指す。

そもそもMyClass2で明示的実装ができるのはIMyInterface2がIMyInterface1を継承しているから

上のコードでは、

internal class MyClass2 : MyClass1, IMyInterface2
{
    void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ②
}

となっていて、Method1_1()を、IMyInterface1のメソッドとして明示的に実装できてるが、

これを

internal class MyClass2 : MyClass1

とすると、下記のように怒られる。

つまり、

「interfaceを実装したクラス」を継承したクラスは、そのままでは親クラスが実装してるinterfaceを使って明示的にメソッドを実装できない。

明示的に実装できるのは、「今このクラスで実装すると書いたinterface」のメソッドだけだと思われる。

↑ここにあるinterfaceだけ、「明示的実装」に使える。

ただ、今回のコードだと、

下記で「今このクラスで実装すると書いたinterface」じゃないinterfaceを明示的実装に使ってるじゃないか、となるが、

これはIMyInterface2IMyInterface1を継承しているからだと思われる。
(次の項目を参照)

※1 IMyInterface1として明示的に実装したメソッドがIMyInterface2として呼べちゃうのはなぜ?

上のコードにおいては、

IMyInterface2は、IMyInterface1を継承しているので、実質、

internal interface IMyInterface2 : IMyInterface1
{
    // 実質、MyInterface1と同じものを持ったinterface
}

は、

internal interface IMyInterface2 
{
        void Method1_1();
}

と同じだから、

((IMyInterface2)asInterface).Method1_1(); 

という呼び方もできるのだと思われる。

※「同じだから」と書いたが、いろいろ試していると、同じでもなさそう。

interfaceの明示的実装の意味合い

interfaceの明示的な実装には、2つの意味があるように感じた。

■意味①

複数のインターフェースで同じメソッドを実装したときに、指定したほうのインターフェースとしてのそのメソッドを実装する、という意味。

■意味②

明示的に実装したそのメソッドは、クラスのインスタンス経由では呼び出せなくして、インターフェース経由でしか呼べなくする、という意味。

つまりは、下記ページにも書かれているように、

https://ufcpp.net/study/csharp/oo_interface.html?p=3#explicit-impl

ということか。

※2 明示的に実装すると、クラスインスタンスからは呼べなくなり「隠れる」はずが、※2で呼べるのはなぜ?

掲題の通り、なぜ呼べるのか?と、なぜ両方親クラスのメソッドが呼ばれるのか?

これがよくわからない。

internal class MyClass2 : MyClass1, IMyInterface2
{
    void IMyInterface1.Method1_1() => Debug.WriteLine(" Method1_1 of MyClass2");  // ②
}

MyClass2で明示的実装をしたからMyClass2のメソッドは「隠れ」て、それをMyClass2クラスインスタンスから呼ぶと、隠れてないMyClass1のメソッドが呼ばれるということか??

※3 親interfaceとして明示的に実装してるメソッドを、子のinterfaceから呼べるのはなぜ?

なぜ呼べるのか、どういう理屈なのか、さっぱりわからない。

パターン2の明示的実装で、

こう書くと、エラーなくビルドできる。 そのときに、cls2.Method1_1();のMethod1_1()でF12を押すと、MyClass1のほうのMethod1_1に飛ぶ。 →これは、パターン②の1と同じか,,,

また、if2.Method1_1();のほうののMethod1_1()でF12を押すと、IMyInterface1`の中ののMethod1_1に飛ぶ。 →パターン②の6と同じか...

→そうか、

パターン2の1は、

  • MyClass2の中で明示的に実装されたIMyInterface1のメソッドは、IMyInterface1のインスタンスから呼ばれるときには隠れない。
  • つまり、下図のように、今実装すると書いたinterfaceのメソッドを明示的に実装しないと、隠すことはできない。
    • 継承したクラスが持ってるメソッドを、子のクラスの中で明示的に実装しても、隠すことができない?

ということか?

パターン2の2,3は、

  • MyClass1インスタンスとして呼ばれたメソッドは、普通にMyClass1のメソッドが呼ばれるだけ
  • MyClass2インスタンスとして呼ばれたメソッドは、MyClass2としてのメソッドは「隠れてる」けど、継承したMyClass1のメソッドとして呼ばれてる

ってことか?

で、パターン2の6は、

インターフェースとして呼ばれるときは「隠れない」から、

  • MyClass2が持ってる、継承したMyClass1のメソッドが呼ばれてる

で、パターン2の7,8は、パターン2の2,3と同じか、、

「隠す」の発動条件

隠す、について、あってる自信はないが、

こういうことじゃないのか。

interfaceを実装したクラスを継承したクラスの中で、 親のメソッドを明示的に実装しても、隠すことはできない、ということか??

実際、この図のようにかけば、クラスのインスタンスからは呼べなくなる。

まとめ

本当に、わからないから試した、の記事になり、全然まとまらなかった。

書いたことが正しいかどうかもわからない。

今回、最初に書いたように、

  • interfaceが2つある。(IMyInterface1IMyInterface2
  • IMyInterface2IMyInterface1を継承している。
  • MyClass1は、IMyInterface1を実装する。
  • MyClass2は、MyClass1を継承し、かつIMyInterface2を実装する。

みたいなややこしいことをするから、複雑になってしまった。

interfaceの明示的実装は、参考に上げさせていただいたこの記事のように、

同じ名前のメンバーを持った複数のinterfaceを実装する、というケースに絞って使ってあげた方がよいのかもしれない。 (直感的ではなさ過ぎて、私には理解が難しかった。。。)

参考

ufcpp

インターフェイスの明示的実装の用途

https://ufcpp.net/study/csharp/oo_interface.html?p=4

こちらで書かれている「メソッドを隠す」ということについて、隠したい!と思う場面にまだであったことがないので、その使い方が役に立つ実感はないが、今回の実験で実際に「隠れる」ということは分かった。

隠したい場面に出会ったらやってみる。