20201126のC#に関する記事は11件です。

VisualStudio便利ショートカットキー(頻繁に使うものだけ)

Qiita初投稿。
3DデザイナーがUnityを覚えるためにC#を勉強して早一か月。
便利だなと思ったものをメモとして書いてみた。

VisualStudio便利ショートカットキー
Ctrl+x        行全て削除
Ctrl+d 行複製
Shift+↑、↓    行選択
Shift+Tab Tab戻し

C#
*/  文字 */ 行をまたいでコメントアウトが出来る
// コメントアウト

C#分かりやすいHP
https://xr-hub.com/archives/15201

今後メモとしてQiitaを使い、メモが皆の技術共有になれば良いなあ。
よろしくお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VisualStudio C# 便利ショートカットキー(頻繁に使うものだけ)

Qiita初投稿。
3DデザイナーがUnityを覚えるためにC#を勉強して早一か月。
便利だなと思ったものをメモとして書いてみた。

VisualStudio便利ショートカットキー
Ctrl+x        行全て削除
Ctrl+d 行複製
Shift+↑、↓    行選択
Shift+Tab Tab戻し

C#
*/  文字 */ 行をまたいでコメントアウトが出来る
// コメントアウト

C#分かりやすいHP
https://xr-hub.com/archives/15201

今後メモとしてQiitaを使い、メモが皆の技術共有になれば良いなあ。
よろしくお願いいたします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET 5 を使いたい理由6選(C#編)

C#9.0の進歩が目覚ましいので使いたい

.NET5 がリリースされました。C#も9.0となりました。C#9.0は.NET5以降でしか使えません。
.NET5 を使えるプロジェクトではどんどん新しい文法を活用しましょう。
C#9.0の新機能から、素晴らしいと思った部分をまとめてみました。

使いたい理由1 : record

class, struct に加えて record を用いて型定義することができます。
変更不可能な参照型とのことです。

    public record Person // classのかわりにrecord
    {
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }
    }

これをILSpyなどで覗いてみると以下のようなコードに展開されます。大方の予想通り class ですがリッチです。

    public class Person : IEquatable<Person>
    {
        protected virtual Type EqualityContract
        {
            [System.Runtime.CompilerServices.NullableContext(1)]
            [CompilerGenerated]
            get => typeof(Person);
        }
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("Person");
            stringBuilder.Append(" { ");
            if (PrintMembers(stringBuilder))
            {
                stringBuilder.Append(" ");
            }
            stringBuilder.Append("}");
            return stringBuilder.ToString();
        }

        protected virtual bool PrintMembers(StringBuilder builder)
        {
            builder.Append("Name");
            builder.Append(" = ");
            builder.Append((object?)Name);
            builder.Append(", ");
            builder.Append("BirthDate");
            builder.Append(" = ");
            builder.Append(BirthDate.ToString());
            return true;
        }

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator !=(Person? r1, Person? r2) => !(r1 == r2);

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator ==(Person? r1, Person? r2) => (object)r1 == r2 || (r1?.Equals(r2) ?? false);

        public override int GetHashCode() => (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name)) * -1521134295 + EqualityComparer<DateTime>.Default.GetHashCode(BirthDate);
        public override bool Equals(object? obj) => Equals(obj as Person);
        public virtual bool Equals(Person? other) => (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name) && EqualityComparer<DateTime>.Default.Equals(BirthDate, other!.BirthDate);
        public virtual Person<Clone>$() => new Person(this); // Clone系の何からしいが不明

        protected Person(Person original)
        {
            Name = original.Name;
            BirthDate = original.BirthDate;
        }
    }

ToStringGetHashCode、比較演算子やコピーコンストラクタなどが自動的に実装されます。
比較演算子も「クラスのインスタンス」ではなく「クラスの内容が」等しいかという実装は大変にありがたいものです。

使いたい理由2 : init

先ほどのPersonのコンストラクタを省略します。代わりに init という初期化中だけ使える Setter を用いてみますがこれが大変良い働きをします。

    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "まだ名はない", BirthDate = DateTime.Today };
            Console.WriteLine(person);
        }
    }

    public record Person
    {
        public string Name { get; init; }
        public DateTime BirthDate { get; init; }
    }

重要なのは、コンストラクタを抜けた後のwith式内まで使用可能であることです。
当然ながら record が不変であることも合わせて変更はできません。

   person.Name = "ねこ"; // コンパイルエラー

ところで、不変なのにSetterが使えるとはどういうことでしょうか。気になります。
ILを見てみましょう。
NameのSetterは内部的には set_Name となります。第1引数に value を受け取っています。

    .method public hidebysig specialname 
        instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Name (
            string 'value'
        ) cil managed 
    {
        // 省略
    }

modreqというのがポイントのようです。これは呼び出し元が無視してはならない制約という意味です。
外部初期化までは有効、つまり「IsExternalInit」という属性が制約として機能します。

使いたい理由3 : 型省略可能なnew()

new は型が明確である場合に型の記述を省略できるようになりました。プロパティやフィールドの初期化などで同じ型を2回書く必要はもうなくなりました。
ローカル変数でvarとこれのどちらを使うかは迷いどころです。

    Person person = new() { Name = "まだ名はない", BirthDate = DateTime.Today };

使いたい理由4 : パターンマッチングの強化

is のあとに and or not などを記述できるようになりました。

    // c is のあとは c に対する条件式
    public static bool IsLetterOrSpace(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or ' ';
    // 従来の場合 c を何度も書く必要があった
    public static bool IsLetterOrSpace(this char c) => c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || ' ';

同じものを記述する回数が減ることは基本的に良いことです。大いに活用したいと思います。

使いたい理由5 : staticラムダ式

メソッド内にこのように書くことができます。

    Func<double, double> square = static x => x * x;

意味が伝わりにくいかもしれませんが、ラムダ式はClosure(関数閉包)です。ラムダ式がローカル変数やthisを参照できるのは、関数オブジェクトとしてローカル変数やthisを内包しているからです。このキャプチャーと呼ばれる内包処理のために、通常のラムダ式はメソッド内で毎回 delegate を生成します。
しかし、thisもローカル変数も内包する必要のないラムダ式の場合、使用する都度生成する必要もなくなり、static で静的にラムダ式を保持するようになります。つまりその分はパフォーマンスが良いということになります。

しかしそれでは、このように書くのと何が違うのか?となります。

class Foo
{
    static Func<double, double> Square = x => x * x;
}

スコープを絞れるメリットがあります。ローカルメソッドのように、そのメソッド内部以外から見えないということは重要です。

使いたい理由6 : 拡張メソッドでもGetEnumeratorがあればforeachできる

GetEnumeratorを拡張メソッドで用意すれば、Rangeですらこのように拡張できてしまいます。これはとても強力です。

    class Program
    {
        static void Main(string[] args)
        {
            foreach (var index in 1..10)
                Console.WriteLine(index);
        }
    }
    public static class RangeExtensions
    {
        public static IEnumerable<int> GetEnumerable(this Range range)
        {
            for (int idx = range.Start.Value; idx <= range.End.Value; idx++)
                yield return idx;
        }
        public static IEnumerator<int> GetEnumerator(this Range range)
            => range.GetEnumerable().GetEnumerator();
    }

パターンマッチングと組み合わせて何かができそうな気がします。

その他の強化

unsafe unmanaged 関連の補強も興味深いですが、今回の記事では扱わないことにします。

謝辞

ここで扱った情報はMicrosoftのサイト( https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9 )の内容から、これはぜひ使いたいと思った機能を抽出し、自分なりに分析を加えたものです。
.NET と C# の発展に携わるすべてのエンジニアの皆様に感謝いたします。

ご案内

この記事は以下の連載記事の第2回目となります。
https://qiita.com/proprogrammer0/items/560ffaf99cdf828c8e52 「.NET 5 を使いたい理由6選」
第3回目記事はTwitterの同名アカウント( https://twitter.com/proprogrammer0 )上から案内予定です。ASP.NET CoreやEF Coreにも触れる予定です。よろしければご覧ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure の App Center でアプリの使用状況を分析する機能を製品に入れる場合の注意点

はじめに

本稿は、Microsoft Azure の App Center Analytics を用いて、アプリの使用状況を分析する機能を製品に入れる場合の注意点を記します。

App Center というのは、Microsoftが提供している「モバイルやデスクトップアプリを作成、テスト、リリース、監視するための開発者向け統合サービス」です。
App Cente についての詳細はこちら(公式)

今回は、App Center の機能のうち、「App Center Analytics」という分析情報の収集機能についての説明です。
この機能は、アプリの任意の操作をした時に、任意の情報をクラウドにイベントログとして登録して確認することができます。上記の公式ページには、以下のように説明されています。

App Center Analytics を使用して、使用パターン、ユーザーによる導入、その他のエンゲージメント メトリックを追跡します。カスタム イベントを使用して、最も重要なユーザーの行動に関する詳細な分析情報を収集できます

App Center Analytics の使い方

App Center には実装するための便利なSDKがあります。
利用できるプラットフォームは、Android、iOS、React Native、Xamarin、Unity、UWP、WPF/WinForms、macOSなど、多数あります。
SDKが利用可能なプラットフォームの詳細はこちら

本稿では、例として、WPFの場合で紹介します。

まず対象のプロジェクトに「Microsoft.AppCenter.Analytics」と「Microsoft.AppCenter.Crashes」をnugetからインストールしてから、以下のように、AppCenterの機能を有効化させれば、準備完了です。

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        AppCenter.Start("{App CenterのWebページで取得する Secret キー}", 
            typeof(Analytics), typeof(Crashes));
    }
}

上記についての詳細は以下の公式ページを参照ください。
Get Started with WPF/WinForms

あとは、任意のタイミングで、任意の情報をイベントログとして登録する実装をします。
以下のように、Analytics.TrackEventメソッドで、イベントの名前を指定してイベントログが登録できます。必要があれば、Dictionaryを用いてプロパティごとの値も登録できます。

Analytics.TrackEvent("イベントの名前", new Dictionary<string, string> {
    { "プロパティ名1", "プロパティ値1" },
    { "プロパティ名2", "プロパティ値2"}
});

上記についての詳細は以下の公式ページを参照ください。
App Center Analytics(Windows)

ここまでの実装ができていれば、下図のように、App CenterのWebサイトから、イベントの名前ごとに、実行回数や実行ユーザー数が確認できます。
image.png

また、実装方法については、Microsoftの かずきさん の以下の記事が、とてもわかり易いと思います。
Visual Studio App Center の WPF 対応機能を使ってみよう

オフラインの場合の対応

App Center へのイベントログ登録時に、アプリがオフラインの場合、SDKが自動的にログを保存し、オンラインに戻ったらログを送信してくれると、以下のFAQに書いてありました。
ネットワーク接続がない場合、SDKが最大10 MB(デフォルト)のログをローカルストレージに保存しているとのことです。
Analytics FAQ(公式)

無償利用できる範囲

本稿で紹介している App Center Analytics は、Freeプラン(無料)で利用できます。特に利用制限もなく、イベントログを登録できます。
また、サポートに問い合わせて確認したところ、「Freeプランで、法人が商用利用する場合でも、制限はない」とのことでした。素晴らしいですね。
App Center の価格(公式)

個人情報保護の観点

App Center Analytics は、イベントログを登録する際に、自動的にユーザーのOSバージョンなどの情報も収集しています。
ということで、GDPRの対応について、確認しました。

EUでは、EU域内の個人データ保護を規定する法として、1995年から現在に至るまで適用されている「EUデータ保護指令(Data Protection Directive 95)」に代わり、2016年4月に制定された「GDPR(General Data Protection Regulation:一般データ保護規則)」が2018年5月25日に施行されました。
GDPRは個人データやプライバシーの保護に関して、EUデータ保護指令より厳格に規定します。

GDPRの詳細はこちら

こちらの記事を参考にすると、GDPRでは以下の情報が「個人情報」とみなされて、厳格な取り扱いが必要になります。

  • 氏名
  • 識別番号
  • 所在地データ
  • メールアドレス
  • クレジットカード情報
  • パスポート情報
  • 身体的、生理学的、遺伝子的、精神的、経済的、文化的、社会的固有性に関する要因
  • オンライン識別子(IPアドレス、クッキー)

ということで、GDPRでの「個人情報」に該当する情報を、App Center Analytics が収集しているのかどうか、Microsoftのサポートに確認したところ、GDPRでの「個人情報」に該当する情報は、デフォルトでは収集していないとのことでした。

ちなみに、イベントログとして登録した情報に、デフォルトでどんな情報が入っているのかは、以下のページに書いてあります。
App Center Export(公式)

以下に App Center が GDPR に準拠するための取り組み方針が書いてあります。
Visual Studio App Center and GDPR

なお、GDPRでの「個人情報」を収集する場合の、ユーザー同意画面の文章案については、以下のサイトで公開されています。各企業が真似しても良さそうなニュアンスで書かれていますので、必要な場合はこちらを参考にすると良いと思います。
知らなかったでは済まされない世界各国の個人情報保護。従業員情報も対象に! - KDDI

まとめ

App Center Analytics を製品に入れる場合の注意点などを紹介しました。

ちなみに私は、普段はエンジニアリングマネージャーとして、チームの皆で楽しく開発する施策を色々実施しています。詳しくは以下を参照ください。
1年以上かけて生産性倍増+成長し続けるチームになった施策を全部公開

Twitterでも開発に役立つ情報を発信しています → @kojimadev

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#の基礎] インスタンスの動的生成

準備

生成対象のクラス定義(namespace は都合により ConsoleApp2 )

namespace ConsoleApp2
{
    public interface IFoo
    {
        int Id { get; set; }
        string Name { get; set; }

        void DoSomething();
    }
    public class Foo : IFoo
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public void DoSomething()
        {
            Console.WriteLine($"{nameof(Foo)} do something" );
        }
    }
}

パターン1 リフレクション

LINQ 登場以前に一般的だった手法。LINQ(というか Linq.Expression)を自分のものにするのはそこそこ手間がかかるので未だに現役だったりする。

基本的には 対象の型をロードして、コンストラクタ探して作る。
System.Activator は型が分かっていればコンストラクタ呼出しを代行する。

        static void Pattern1()
        {
            var type = Type.GetType("ConsoleApp2.Foo");
            var instance = Activator.CreateInstance(type) as IFoo;
            instance.DoSomething();
        }

受け取ったインスタンスを IFoo にキャストしている。これにより欲しい処理が呼び出せる。

Type.GetType() 呼出しで文字列を使っているので、同じインターフェースを実装した別のクラスに置き換えてもそれなりの動作をする。

パターン2 Linq.Expression

沼にようこそ。

リフレクションのパターンと同様に型の取得やコンストラクタの取得を実行するのは同じだが、決定的に違うのは最終的に元のコンストラクタ呼出しと同じものが出来上がる事。

        static void Pattern2()
        {
            var type = Type.GetType("ConsoleApp2.Foo");
            var constructor = type.GetConstructor(Type.EmptyTypes);
            var expression = Expression.Lambda<Func<IFoo>>(Expression.New(constructor)).Compile();
            var instance = expression();
            instance.DoSomething();
        }

expression() の部分がコンストラクタ呼出し。この呼び出しは事実上以下と等しい。

var instance = new ConsoleApp2.Foo();

あるいは

            Func<IFoo> expression = () => new ConsoleApp2.Foo();
            var instance = expression();

この呼び出しが1回だけならリフレクションパターンと大して差はないが、SQLクエリーからの結果行オブジェクト生成等だと圧倒的性能差が付く。

「パラメータがあると使えない」とか言う人はいると思うけど、Expression.New() のオーバーロード調べてみると良い。当然それなりに複雑になるけれど。

Pattern1 と同様、生成するクラスの名前を別の型にすることが可能。

パターン3 Factory

インスタンス生成を外部のクラスに隠蔽する。
コンストラクタ呼出しでもよいし、上二つのやり方を使っても良いし、他いろいろ使っても良い。
とにかくクライアントコードの中にインスタンス生成のための知識を持ち込まないことが重要。

factory クラスの実装

    public class FooFactory
    {
        public IFoo GetFoo()
        {
            return new Foo();
        }
    }

クライアントコード

        static void Pattern3()
        {
            var factory = new FooFactory();
            var instance = factory.GetFoo();
        }

クライアントコードでは Factory についての知識と、 IFoo の知識があれば良い。
Factory が何かパラメータを使って IFoo を実装した別のインスタンスを返すようにしても良い。

Pattern1、Pattern2 とは違い、呼び出す生成メソッドが実装クラスのコンストラクタを知っていなければならない
(もちろん Pattern1,Pattern2 と組み合わせて動的に生成しても良い.... あ、動的生成って言ってたけどこのパターンは基本的には静的な生成だった)

パターン4 Dependency Injection

各種 DI Container を使うパターン。コンテナによってはロードするアセンブリ自体を動的にロードして、読み込まれる型をごっそり置き換え…などという事も可能になる。

ここでは Autofac 4.6.2 (だいぶ古いけど)を使ってみる

まずはコンテナの構成

using Autofac;
        static IContainer GetContainer()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Foo>()
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope()
                ;
            return builder.Build();
        }

コンテナに登録されたインスタンスの取得

        static void Pattern4()
        {
            var container = GetContainer();
            using(var scope = container.BeginLifetimeScope())
            {
                var instance = scope.Resolve<IFoo>();
                instance.DoSomething();

            }
        }

ここではサンプルコードの単純化のためにコンテナに登録する型を固定で書いているけれど、特定属性の付けられたクラスや特定 namespace のクラスをまとめて登録する、なんてこともできる。

その他

実行時のちょっとしたロジックの切り分け(一般ユーザーと特別会員の切り分けとか)だと Factory が活躍できる。
構成自体でロジックを切り替えたい場合(顧客ごとのカスタマイズコードの適用とか)なら DI がおすすめ。
(もちろんリフレクションを駆使しても良い。ただしそういう苦労をいろいろ肩代わりしてくれるのが DI)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebView2の固定バージョン配布モードの話

はじめに

 11/20にMicrosoftが.NET向けのWebView2を正式公開しました。これによりWPFやWinformといったアプリで、Chromiumを採用した新しいMicrosoft Edgeベースのブラウザーコントロールを利用できることになりました。
 下記のマイクロソフトのドキュメントの中では、ブラウザーコントロールがバックエンドで使うWebViewランタイムの2つの配布方法について解説されています。この記事では2つの配布パターンの内、固定バージョンの配布モードでアプリのパッケージを作成して動作を確認します。

WebViewランタイムの配布モード

 WebView2はバックエンドにEdgeと同じWebブラウザーのランタイムを利用するのですが、すべての環境にEdgeがインストールされているとは限らない&様々なバージョンがインストールされている可能性があります。WebView2コントロールでは再配布可能なWebViewランタイムをアプリのパッケージに含めて、そのランタイムを利用することで安定して利用するために下記の2パターンの配布方法が用意されています。

  • エバーグリーン配布モード
  • 固定バージョンの配布モード

 エバーグリーン配布モードは、あらかじめWebViewランタイムがインストールされている、もしくはアプリ起動時にWebViewランタイムをインストールできる環境向けの配布モードです。実行環境にインストールされた最新のWebViewランタイムを使用するため、

  • アプリのパッケージサイズを節約できる
  • PlayReadyの対象になるので起動時間を短縮できる

といったメリットがあります。

 今後はWindowsのリリースに含まれる予定のようですが、現時点ではWebViewランタイムは自動的に配信されていないので、エバーグリーン配布モードを利用する場合は下記手順に従ってWebViewランタイムをインストールする必要があります。

固定バージョンの配布モード

 固定バージョンの配布モードは、WebView2の配置時にWebViewランタイムのパスを指定してWebView2を初期化する方法です。エバーグリーン配布モードが実行環境に1つだけ存在する共有のWebViewランタイムを利用するのに対し、WebView2初期化時に指定した特定バージョンのWebViewランタイムを利用できるため

  • 開発環境と実行環境のブレが少なくなる
  • WebViewランタイムを個別にインストールする必要がなくなる
  • WebViewランタイムの配置時に管理者への昇格が不要

といったメリットがありますが、

  • アプリのパッケージサイズが肥大化する
  • WebViewランタイムの脆弱性が放置される可能性がある
  • デフォルトではPlayReadyの対象にならない

 といったデメリットがあり、ランタイムの管理コストがエバーグリーン配布モードよりも高くなります。ただ、厳密に指定されたバージョンのWebViewランタイムを利用できることは大きなメリットなので、個人的にはとても魅力的に見えます。

固定のパスに展開したWebViewランタイムを参照するようにアプリを構成してみる。

 ここの手順に従い、Microsoft Edge WebView2のダウンロードページから実行環境ごとのWebViewランタイムをダウンロードしてCabファイルをC:¥tempなどに展開します。参照元のドキュメントにもありますが、フォルダー構造が重要なのでエクスプローラーの機能で展開すると動作しないので注意が必要です。
image.png
 まずは単純にダウンロードしたランタイムを参照してプログラムが起動するかを確認していきましょう。
 WPFでのWebView2の概要に従いWPFプロジェクトを作成し、NugetからMicrosoft.Web.WebView2パッケージをプロジェクトにインストールします。

App.xamlおよびMainWindow.xamlにWebView2用のネームスペースを追加し、

App.xamlおよびMainWindow.xaml
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"

App.xamlにWebViewランタイムのパスを設定するプロパティーをリソースに追加します。

App.xaml
<Application x:Class="BuildeeApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <wv2:CoreWebView2CreationProperties 
            x:Key="FixedWebView2CreationProperties" 
            BrowserExecutableFolder="C:\Temp\Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\" />
    </Application.Resources>
</Application>

次にWebView2コントロールをページに追加し、上で作ったリソースを参照します。

MainWindow.xaml
        <wv2:WebView2 CreationProperties="{StaticResource FixedWebView2CreationProperties}"
                      Source="https://karuakun.wordpress.com" />

xml:MainWindow.xamlはこのようになります。

MainWindow.xaml
<Window x:Class="SampleApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SampleApp"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <wv2:CoreWebView2CreationProperties 
            x:Key="FixedWebView2CreationProperties" 
            BrowserExecutableFolder="C:\Temp\Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\" />
    </Window.Resources>

    <Grid>
        <wv2:WebView2 CreationProperties="{StaticResource FixedWebView2CreationProperties}"
                      Source="https://karuakun.wordpress.com" />
    </Grid>
</Window>

実行すると、WebView2コントール内にSourceに指定したURLのページが表示されていることが確認できます。
image.png

アプリにWebViewランタイムをパッケージングする

 このままでは、個別にC:¥tempにWebViewランタイムをダウンロードして展開しないと動かないので、アプリのパッケージにWebViewランタイムを含めていきます。とりあえず展開したWebViewランタイムをプロジェクトにドロップしてこんな状態にします。
image.png
 このフォルダー配下の全ファイル(フォルダーの中も)の出力ディレクトリにコピープロパティーを新しい場合はコピーするにします(GUIで操作するのがめんどくさい場合は、文末の「おまけ 出力ディレクトリにコピーの設定」の設定をプロジェクトファイルに追加してあげればたぶんOKです)。これでビルド時にWebViewランタイムがパッケージに含まれるようになります。
image.png
App.xaml.csなどでアプリケーションリソースの属性値をビルド時に配置したディレクトリにしてあげれば設定は完了です。

App.xaml.cs
namespace SampleApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var executeDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var runtimePath = Path.Combine(executeDirectory, "Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64");
            var fixedWebView2CreationProperties = (CoreWebView2CreationProperties)Current.Resources["FixedWebView2CreationProperties"];
            fixedWebView2CreationProperties.BrowserExecutableFolder = runtimePath;

        }
    }

プロジェクトディレクトリで、次のコマンドを実行し単一実行形式のパッケージを作成します。

dotnet publish -r win-x64 /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true

じっ、実行ファイルのサイズが458MBになりましたね(汗。まぁ、WebViewランタイムを展開した時点で297MBとかありますからしょうがない、、、ですよね。サイズが気になるならエバーグリーン配布モードを使いましょう。

image.png
7zで圧縮したら140MBまで小さくなりました。
image.png

まとめ

これまでWebBrowserコントロールがIEベースだったために起きていた問題が少なくなると良いですね。
WebViewランタイムのパッケージ方法はなんとなく別の方法がある気がします。
ご存じの方がいらっしゃったら教えてください。

おまけ 出力ディレクトリにコピーの設定

SampleApp.csproj
  <ItemGroup>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\87.0.664.47.manifest">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\concrt140.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\d3dcompiler_47.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\dual_engine_adapter_x64.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\EBWebView\x64\EmbeddedBrowserWebView.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\EBWebView\x86\EmbeddedBrowserWebView.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\eventlog_provider.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Extensions\external_extensions.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\icudtl.dat">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\learning_tools.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\libEGL.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\libGLESv2.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\libsmartscreen.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\af.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\am.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ar.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\as.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\az.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\be.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\bg.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\bn-IN.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\bn.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\bs.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ca-Es-VALENCIA.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ca.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\chr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\cs.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\cy.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\da.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\de.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\de.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\es.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\fr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\it.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\ja.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\ko.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\pt-BR.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\ru.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\zh-CN.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\devtools\zh-TW.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\el.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\en-GB.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\en-US.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\es-419.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\es.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\et.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\eu.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\fa.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\fi.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\fil.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\fr-CA.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\fr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ga.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\gd.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\gl.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\gu.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\he.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\hi.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\hr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\hu.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\hy.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\id.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\is.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\it.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ja.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ka.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\kk.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\km.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\kn.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ko.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\kok.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ky.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\lb.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\lo.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\lt.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\lv.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\mi.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\mk.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ml.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\mn.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\mr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ms.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\mt.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\nb.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ne.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\nl.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\nn.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\or.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\pa.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\pl.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\prs.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\pt-BR.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\pt-PT.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\qu.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ro.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ru.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sd.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\si.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sk.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sl.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sq.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sr-Cyrl-BA.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sr-Latn-RS.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sv.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\sw.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ta.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\te.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\th.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\tk.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\tr.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\tt.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ug.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\uk.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\ur.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\uz-Latn.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\vi.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\zh-CN.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Locales\zh-TW.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MEIPreload\manifest.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MEIPreload\preloaded_data.pb">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\microsoft_apis.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\microsoft_shell_integration.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\mip_core.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\mip_protection_sdk.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MLModels\autofill_labeling.onnx">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MLModels\autofill_labeling_email.onnx">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MLModels\autofill_labeling_features.txt">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MLModels\autofill_labeling_features_email.txt">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\MLModels\nexturl.onnx">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\mojo_core.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedge.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedge.dll.sig">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedgewebview2.exe">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedgewebview2.exe.sig">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedge_100_percent.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedge_200_percent.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msedge_elf.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\msvcp140.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\nacl_irt_x86_64.nexe">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Notifications\SoftLandingAssetDark.gif">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Notifications\SoftLandingAssetLight.gif">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\notification_helper.exe">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\oneauth.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\oneds.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\onnxruntime.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\onramp.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\PlayReadyCdm\_platform_specific\win_x64\playreadycdm.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\resources.pak">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\show_third_party_software_licenses.bat">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\swiftshader\libEGL.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\swiftshader\libGLESv2.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\telclient.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Advertising">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Analytics">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\CompatExceptions">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Content">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Cryptomining">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Entities">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Fingerprinting">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\LICENSE">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\manifest.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Other">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\Trust Protection Lists\Social">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\v8_context_snapshot.bin">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\vccorlib140.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\vcruntime140.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\vcruntime140_1.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\wdag.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\WidevineCdm\manifest.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\WidevineCdm\_platform_specific\win_x64\widevinecdm.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\WidevineCdm\_platform_specific\win_x64\widevinecdm.dll.sig">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="Microsoft.WebView2.FixedVersionRuntime.87.0.664.47.x64\wns_push_client.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#の基礎] インターフェースの話 (2) 抽象クラスとの違い

  • インターフェース: 実装クラスに複数適用可能、実装本体を記述できない(*)
  • 抽象クラス: 実装クラスに複数適用できない、実装本体を記述できる

*) 最近はデフォルト実装を記述できる言語もある。C#では どうだろう 8.0 (.NET Core 3.0) から利用可能。

どちらも利用者クラスにおいて実装インターフェースや抽象クラスと置き換え可能(リスコフの置換原則)。なので適当なFactory で生成したオブジェクトを定義したインターフェースや抽象クラス扱いできる。

インターフェース は以下が許される

public interface IFoo{
  void DoFoo();
}
public interface IBar{
  void DoBar();
}

public class FooService: IFoo, IBar{
  public void DoFoo(){
  }
  public void DoBar(){
  }
}

一方抽象クラスはは以下が許されない

public abstract class AbstractFoo{
  public abstract  void DoFoo();
}
public abstract class AbstractBar{
  public abstract void DoBar();
}

public class FooService: AbstractFoo, AbstractBar{
  public void DoFoo(){
  }
  public void DoBar(){
  }
}

使い分け

複数インターフェースを実装する局面も IDisposable を付けるとか意外はほとんどないし、実際抽象クラスだけでどうにかなる場合も多いので明確な使い分け指針があるわけでもない。

ただDI使う場合Containerへの登録等でインターフェースを定義する必要がある場合が多いかも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#の基礎] インターフェースの話

インターフェース(Interface) にまつわる話。

インターフェースの基本

こういうインターフェースを作ると

public interfae IFoo{
    string Greet(string to);
}

これは許されないが

public class FooImplementation : IFoo {
   string Greet(string to, int i){
       return "hello";
  }
}

これは許される。

public class FooTypeA : IFoo {
   string Greet(string to){
       return $"hello {to}";
  }
}
public class FooTypeB : IFoo {
   string Greet(string to){
       return $"こんにちは、{to} 様";
  }
}

つまり "インターフェースで宣言したメソッドは実装クラスでその通りに実装されなければならない" という事。

良く知られた例としては IEnumerable<T> があり、いつも使う List&let;FooEntity> とかを foreach 出来るのも、string を foreach して1文字ずつ解析したりできるのも 実装クラスが必ずそう言う処理を実装しているから。

同じインターフェースに対する複数の実装

先の例の FooTypeA, FooTypeB は共に IFoo インターフェースを実装している。
これで何が嬉しいのかって言うのがイマイチ理解しづらいみたい。

サンプル

class Program{
  public static void Main(string[] args){
    IFoo foo = GetFoo(args[0]);
    if(foo != null){
      Console.Write(foo.Greet("Mr. Boo");
    }
  }
  public static IFoo GetFoo(string fooType){
      if(fooType  "A") return new FooTypeA();
      else if(fooType  "B") return new FooTypeB();
      else return null;
  }
}

ここではコマンドライン引数で受け取ったタイプによって生成する実装クラス を切り替えて、メソッドGreet()を呼び出す。
void Mainを書く人はIFoo の実装クラスについてあまり詳細な事を知らなくても使える。
(ただし、自分の欲しい結果が何かは把握していないと困る)

つまり

  • メソッドの形式が同じなので、インターフェースの定義を知っていれば使える
  • 同じインターフェースを実装した複数のオブジェクトについて、同じメソッドを確実に呼び出すことができる(IDisposableなど = 常にusing{}できるとか)
  • 何かの都合で処理内容を切り替えたい場合に使える

という事になる。

3番目の件、if や switch でどうにかすることもできるし、コンパイル定数で制御することもできるけども、テスト大変。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#の基礎] インターフェースの話 (1) 基本的な事

インターフェース(Interface) にまつわる話。

インターフェースの基本

こういうインターフェースを作ると

public interfae IFoo{
    string Greet(string to);
}

これは許されないが

public class FooImplementation : IFoo {
   string Greet(string to, int i){
       return "hello";
  }
}

これは許される。

public class FooTypeA : IFoo {
   string Greet(string to){
       return $"hello {to}";
  }
}
public class FooTypeB : IFoo {
   string Greet(string to){
       return $"こんにちは、{to} 様";
  }
}

つまり "インターフェースで宣言したメソッドは実装クラスでその通りに実装されなければならない" という事。

良く知られた例としては IEnumerable<T> があり、いつも使う List&let;FooEntity> とかを foreach 出来るのも、string を foreach して1文字ずつ解析したりできるのも 実装クラスが必ずそう言う処理を実装しているから。

同じインターフェースに対する複数の実装

先の例の FooTypeA, FooTypeB は共に IFoo インターフェースを実装している。
これで何が嬉しいのかって言うのがイマイチ理解しづらいみたい。

サンプル

class Program{
  public static void Main(string[] args){
    IFoo foo = GetFoo(args[0]);
    if(foo != null){
      Console.Write(foo.Greet("Mr. Boo");
    }
  }
  public static IFoo GetFoo(string fooType){
      if(fooType  "A") return new FooTypeA();
      else if(fooType  "B") return new FooTypeB();
      else return null;
  }
}

ここではコマンドライン引数で受け取ったタイプによって生成する実装クラス を切り替えて、メソッドGreet()を呼び出す。
void Mainを書く人はIFoo の実装クラスについてあまり詳細な事を知らなくても使える。
(ただし、自分の欲しい結果が何かは把握していないと困る)

つまり

  • メソッドの形式が同じなので、インターフェースの定義を知っていれば使える
  • 同じインターフェースを実装した複数のオブジェクトについて、同じメソッドを確実に呼び出すことができる(IDisposableなど = 常にusing{}できるとか)
  • 何かの都合で処理内容を切り替えたい場合に使える

という事になる。

3番目の件、if や switch でどうにかすることもできるし、コンパイル定数で制御することもできるけども、テスト大変。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 1.0 と C# 9.0 で同じプログラムを書いてみよう

この記事は C# Advent Calendar 2020 の 2 日目の記事です。1 日目は @RyotaMurohoshi さんの C# 9.0で加わったC# Source Generatorと、それで作ったValueObjectGeneratorの紹介 でした。

私の記事では、.NET Framework 1.0 の頃の C# 1.0 と今の .NET 5 時代の C# 9.0 で同じお題をもとにプログラムを書いてみて比べてみようと思います。これを書くにあたって事前に xin9le さん岩永さんに色々見てもらいました!感謝!

ではやってみましょう!
記事を書く前の感覚では LINQ の有無と async/await の有無が大きいだろうな…と思ってます。

プロジェクトの設定

Windows 10 に .NET Framework 1.1 SDK を入れようと思えば入れることが出来るみたいなのですが、サポートが切れた SDK を入れる勇気がなかったのでサポートされている .NET Framework 4.5 系のプロジェクトで LangVersion 1 を設定したものを .NET Framework 1.0 の頃の気持ちで書いていこうと思います。なので無意識に .NET Framework 1.X の時代には無かった便利クラスを使ってしまっている可能性がありますが、気持ちとしては使わないようにしています。

C# 9.0 は .NET 5 のプロジェクトにします。

end と入力するまで文字列を受け取って最後に入力した文字列から表示する

こんな感じのものをイメージしています。

> aaaa
> bbbb
> cccc
> end
> cccc # ここから下が出力
> bbbb
> aaaa

C# 1.0

C# 1.0 の頃にはジェネリクスはありません。当時は全部 object 型で格納する System.Collections.ArrayList を使います。ただ、それじゃぁあんまりなので System.Collections.Specialized 名前空間に型指定されたコレクションがいくつかあります。今回のような文字列は、よく使われるので StringCollection クラスがあります。それを使って書いてみましょう。

using System;
using System.Collections.Specialized;

namespace CSharp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // var がない
            StringCollection inputs = new StringCollection(); // 型指定されたコレクション
            string line;
            while((line = Console.ReadLine()) != "end")
            {
                inputs.Add(line);
            }

            // 末尾からインデックス指定でループ
            for (int i = inputs.Count - 1; i >= 0; i--)
            {
                Console.WriteLine(inputs[i]);
            }
        }
    }
}

うん…まぁこんなもんかな。

C# 9.0

やはり LINQ が追加されたあたりで追加された機能が強い…。あと、こういう小さなコンソールアプリにおいてはトップレベルステートメントが強い。

using System;
using System.Collections.Generic;
using System.Linq;

// 無限シーケンス
static IEnumerable<string> readLines()
{
    while (true) { yield return Console.ReadLine(); }
}

// end まで受け取って反転
foreach (var line in readLines().TakeWhile(x => x != "end").Reverse())
{
    Console.WriteLine(line);
}

ローカル関数で Console.ReadLine() を無限シーケンスにして、あとは LINQ で TakeWhile で end まで取得して反転して foreach でループを回して表示しています。LINQ 以降は色々なものを IEnumerable<T> にして LINQ で処理するという考え方でプログラムが組めるようになりました。今回も標準入力を IEnumerable<string> として扱って LINQ で処理することで簡単にかけてますね。

独自型の型指定されたコレクション

このお題で出てきた StringCollection のような型指定されたコレクションですが自作のクラスに関してはもちろん型指定されたクラスは用意されていません。なので自作します。自作といってもコレクションの機能を全部作りこむのは大変なので、System.Collections.CollectionBase というクラスがあって、基本的にはこれを継承して作ります。

このクラスの定義部分は以下のような感じになっています。型指定されたコレクション用ってちゃんと書いてありますね。

//
// 概要:
//     Provides the abstract base class for a strongly typed collection.
public abstract class CollectionBase : ICollection, IEnumerable, IList

このクラスを継承して、特定のクラス用のコレクションを自作します。とてもめんどくさいです。

// 何か自前クラス
class Item { }

// 自前クラス用の独自コレクション
class ItemCollection : CollectionBase
{
    public ItemCollection()
    {
    }

    public ItemCollection(int capacity) : base(capacity)
    {
    }

    public int Add(Item item)
    {
        return InnerList.Add(item);
    }

    public Item this[int index]
    {
        get
        {
            return (Item)InnerList[index];
        }
        set
        {
            InnerList[index] = value;
        }
    }

    // 必要なメソッドを各種定義していく
}

使う側は普通に使うだけですが、定義する人は辛いです。

image.png

余談ですが、OSS の .NET 用の IDE の SharpDevelop では、型指定されたコレクションを生成するためのアイテムテンプレートがあったりした記憶があります。もう15年近く前の話なので、うろ覚えになりますが…。アイテムテンプレートが準備されるくらいにはめんどくさい作業でした。今は List<T> って書いたら完了なのは神がかってますね。

特定のメッセージを受け取ったらイベントを発行するクラス

文字列を追加する AddMessage というメソッドを持ったクラスで "こんにちは" か "おはようございます" か "こんばんは" が追加されたら Greet という名前のイベントを発火するようなクラスを作って。イベント引数は、その時追加されたメッセージを Message プロパティで取得できるような感じで

C# 1.0

デリゲートの取り回しもちょっとめんどくさいし、デリゲートを自分で定義するのが当たり前の世界だった。

using System;

namespace CSharp1
{
    class Program
    {
        static void Main(string[] args)
        {
            MessageCollector messageCollector = new MessageCollector();
            // 確かシグネチャーが同じでもデリゲートとメソッドで自動的に変換してくれなかったと思う…
            // ラムダ式もないので、必ず別メソッドで定義しないといけない。
            messageCollector.Greet += new GreetEventHandler(MessageCollector_Greet);

            messageCollector.AddMessage("Hello");
            messageCollector.AddMessage("world");
            messageCollector.AddMessage("こんにちは"); // こんにちは って言ったね! と表示される
        }


        static void MessageCollector_Greet(object sender, GreetEventArgs args)
        {
            // string.Format が正義
            Console.WriteLine(string.Format("{0} って言ったね!", args.Message));
        }
    }

    class GreetEventArgs : EventArgs
    {
        // プロパティの定義がつらい
        private string _message;
        public string Message
        {
            get { return _message; }
        }

        public GreetEventArgs(string message)
        {
            _message = message;
        }
    }

    // ジェネリクスが無かったので汎用的なイベントハンドラーの EventHandler<T> なんてない。
    // もちろん Action<T> や Func<T, R> のようなデリゲートも定義されてないので必要があれば全部自分で定義しないといけない
    delegate void GreetEventHandler(object sender, GreetEventArgs args);

    class MessageCollector
    {
        public event GreetEventHandler Greet;

        public void AddMessage(string message)
        {
            switch (message)
            {
                case "おはようございます":
                case "こんにちは":
                case "こんばんは":
                    // ?. 演算子はないので null チェックして呼ばないといけない。
                    if (Greet != null)
                    {
                        Greet(this, new GreetEventArgs(message));
                    }
                    break;
                default:
                    // noop
                    break;
            }
        }
    }
}

C# 9.0

細かいところで便利になってる。冗長な表現が結構消えてる。デリゲートの定義やラムダ式やらなんやら。

using System;

var messageCollector = new MessageCollector();
// ラムダ式楽。でも -= で登録解除しようとしたら、別途メソッド定義しておかないといけない。
messageCollector.Greet += (_, args) => Console.WriteLine($"{args.Message} って言ったね!");
messageCollector.AddMessage("Hello");
messageCollector.AddMessage("world");
messageCollector.AddMessage("こんにちは"); // こんにちは って言ったね! と表示される


class GreetEventArgs : EventArgs
{

    public GreetEventArgs(string message)
    {
        Message = message;
    }

    // プロパティすっきり
    public string Message { get; }
}

class MessageCollector
{
    // ジェネリクスあるの楽だよね
    public event EventHandler<GreetEventArgs> Greet;

    public void AddMessage(string message)
    {
        switch (message)
        {
            case "おはようございます":
            case "こんにちは":
            case "こんばんは":
                // ?. もあるし、 target typed new も使ってすっきり
                Greet?.Invoke(this, new(message));
                break;
            default:
                // noop
                break;
        }
    }
}

昔はなんでもきちんと冗長に書いてた

これを書いてて思ったのは昔は何でもきちんと書いてたなぁということです。
恐らく、この冗長な書き味のせいで当時からライトにかけたスクリプト言語の流行につながっていって、C# や Java のような非スクリプト言語も型を保ったままスクリプト言語ライクにかけるようになる機能が拡充していって今のような感じになったんだろうなぁと思ってます。

非同期でインターネットからテキストのダウンロード

非同期で https://example.com の HTML をダウンロードする感じです。
プログラムはダウンロードが終わって結果が表示されるまでは終了したらダメです。例外が出たら例外のメッセージを出す感じで。

C# 1.0

async/await なんてなかった。BeginXXXX で始まるメソッドにコールバックを渡して呼ぶ。コールバックでは EndXXXX というメソッドを呼んで結果を取得するという感じです。

Main メソッドは非同期処理が終わるのを待機しないといけないので…セマフォ使いました。久しぶりに使ったよ!
セマフォと、BeginXXXX メソッドを呼んだオブジェクトをコールバックに渡さないといけないけど、コールバックに渡せるステートはオブジェクト1つだけなので、持ちまわりたいものをまとめたクラスも定義しないといけない。
クラスのプロパティには、今みたいな public string Hoge { get; set; } のように簡単に書く方法はなく、必ずフィールドを定義して、それを自前でラップするようなプロパティを定義してあげないといけませんでした。今改めて書くとめんどくさいね!

HTTP レスポンスが取れたら、今度は Body を読む処理を非同期でやらないといけない…ワンモアコールバック…。ここでもコールバックにセマフォと、バッファーと Stream を渡したいので、それらをまとめるクラスを定義して…辛い。

ということで、以下のようなコードになりました。

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

namespace CSharp1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (Semaphore semaphore = new Semaphore(0, 1))
            {
                // 非同期で読み込む処理を開始して
                WebRequest req = WebRequest.Create("https://example.com");
                req.BeginGetResponse(new AsyncCallback(BeginGetResponseCallback), new GetResponseState(req, semaphore));
                // 終わるのを信じて待つ。信じてるので、非同期処理の先で開放漏れがあると終わらないプログラムになる。
                semaphore.WaitOne();
            }
        }

        static void BeginGetResponseCallback(IAsyncResult asyncResult)
        {
            GetResponseState state = (GetResponseState)asyncResult.AsyncState;
            try
            {
                WebResponse res = state.WebRequest.EndGetResponse(asyncResult);
                // 本来なら超巨大ページみたいなのに当たった時用に適当な大きさのバッファーを作っておいて
                // ループぐるぐるしたほうがいいんだろうけど、非同期処理でそれをやる事を考えると辛かったので
                // ギブアップした。一括読み込み!
                byte[] buffer = new byte[res.ContentLength];
                Stream stream = res.GetResponseStream();
                stream.BeginRead(
                    buffer,
                    0,
                    buffer.Length,
                    new AsyncCallback(BeginReadCallback), 
                    new ReadState(stream, buffer, state.Semaphore));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                state.Semaphore.Release();
            }
        }

        static void BeginReadCallback(IAsyncResult asyncResult)
        {
            ReadState state = (ReadState)asyncResult.AsyncState;
            try
            {
                int length = state.Stream.EndRead(asyncResult);
                if (length != state.Buffer.Length)
                {
                    Console.WriteLine("何かデータうまく読めなかった");
                    state.Semaphore.Release();
                    return;
                }

                // UTF-8 決め打ちにしました…
                Console.WriteLine(Encoding.UTF8.GetString(state.Buffer));
                state.Semaphore.Release();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                state.Semaphore.Release();
            }
        }
    }

    // BeginGetResponse のコールバックに渡したい情報 (今ならこんなクラスはレコードでよさそう)
    class GetResponseState
    {
        private WebRequest _webRequest;
        public WebRequest WebRequest
        {
            get
            {
                return _webRequest;
            }
        }

        private Semaphore _semaphore;
        public Semaphore Semaphore
        {
            get
            {
                return _semaphore;
            }
        }

        public GetResponseState(WebRequest req, Semaphore sem)
        {
            _webRequest = req;
            _semaphore = sem;
        }
    }

    // BeginRead のコールバックに渡したい情報
    class ReadState
    {
        private Stream _stream;
        public Stream Stream
        {
            get
            {
                return _stream;
            }
        }

        private byte[] _buffer;
        public byte[] Buffer
        {
            get
            {
                return _buffer;
            }
        }

        private Semaphore _semaphore;

        public Semaphore Semaphore
        {
            get
            {
                return _semaphore;
            }
        }
        public ReadState(Stream stream, byte[] buffer, Semaphore semaphore)
        {
            _stream = stream;
            _buffer = buffer;
            _semaphore = semaphore;
        }
    }
}

長い…、実行すると example.com の HTML が標準出力に表示されます。

C# 9.0

async/await 神かよ…

using System;
using System.Net.Http;

var client = new HttpClient();
try
{
    // 神かよ…
    var body = await client.GetStringAsync("https://example.com");
    Console.WriteLine(body);
}
catch(Exception ex)
{
    Console.WriteLine(ex.Message);
}

async/await で同期処理と同じように非同期処理が書けるのは神がかってますね。あと HttpClient クラスが便利。C# 9 では、JSON を送受信することを想定したメソッドとかも追加されていて、より使いやすくなってます。

async/await に至るまで

.NET Framework 1.0 が出た当初は、コアが複数あるパソコンは一般人が使うものとしてはレアで、モンスターマシンを組むような人の中でも稀に物理的に CPU が 2 個ついてるものがあるかもしれないとかくらいだった気がします。
なので、今回のような IO 待ちに非同期処理を行うのは当時も効果はあったと思うのですが、得られるメリットに対してコードが凄い大変なので、そこまで非同期処理がカジュアルに書かれる感じではなかった印象です。

.NET framework 2.0 で重たい計算処理をするような処理を簡単に書くための BackgroundWorker というクラスが提供されましたが、これも IO 待ちの場合は最適な選択なのかどうかは微妙な気がする。

その後 Task などが追加されメソッドチェーンで非同期処理を書けるようになったあとに async/await が追加されて今のような形になりました。今じゃぁカジュアルに非同期処理かけていいね!

テキストファイルに書かれた URL のリストからサイトのタイトルタグの行を取得

こちらのお題ですが、出来れば GUI アプリケーションから呼んだ時に IO 待ちで UI がブロックされるようなことは避けてほしいという考慮を求められているという感じで行こうと思います。ここで書くのはコンソールアプリですが。

ついでに、全サイトを読み込んでから結果を通知ではなく、なるべく 1 サイトのソースをダウンロードするたびに結果が取れるようにしてほしいという感じです。Downloader というクラスにこの機能を実装していきます。

C# 1.0

C# 1.0 で呼び出し元スレッドをブロックしないようにしようと思ったら別スレッドで処理する方法をとっちゃうと思います。例えそれが IO 待ちで計算が重たいとかではないとしても…。1 つ前の非同期でのダウンロードを C# 1.0 で書いてて心が折れたので妥協します。

ということで以下のような感じになりました。

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

namespace CSharp1
{
    class Program
    {
        // このスコープにセマフォもってくるの負けた気がする
        private static Semaphore semaphore = new Semaphore(0, 1);
        static void Main(string[] args)
        {
            // TLS1.2
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (semaphore)
            {
                // 進捗報告と完了報告はイベントで受け取る
                Downloader d = new Downloader();
                d.TitleTagDetected += new TitleTagDetectedEventHandler(Downloader_TitleTagDetected);
                d.Completed += new EventHandler(Downloader_Completed);
                d.Start();

                // イベントハンドラーの先でReleaseが呼ばれることを信じて待つ
                semaphore.WaitOne();
            }
        }

        private static void Downloader_Completed(object sender, EventArgs e)
        {
            // 完了したのでリリース
            semaphore.Release();
        }

        private static void Downloader_TitleTagDetected(object sender, TitleTagDetectedEventArgs args)
        {
            // 進捗を表示
            Console.WriteLine(args.Url);
            Console.WriteLine(args.Title);
        }
    }

    class TitleTagDetectedEventArgs : EventArgs
    {
        private string _url;
        public string Url { get { return _url; } }

        private string _title;
        public string Title { get { return _title; } }

        public TitleTagDetectedEventArgs(string url, string title)
        {
            _url = url;
            _title = title;
        }
    }

    delegate void TitleTagDetectedEventHandler(object sender, TitleTagDetectedEventArgs args);

    class Downloader
    {
        public event TitleTagDetectedEventHandler TitleTagDetected;
        public event EventHandler Completed;

        public void Start()
        {
            // スレッドプールで処理をやって呼び出し元をブロックしない作戦
            ThreadPool.QueueUserWorkItem(new WaitCallback(StartImpl));
        }

        private void StartImpl(object state)
        {
            // ファイル名は urllist.txt 決め打ちでとりあえず。
            using (StreamReader sr = new StreamReader("urllist.txt"))
            using (WebClient client = new WebClient())
            {
                // 同期処理のオンパレード
                client.Encoding = Encoding.UTF8;
                for (string url; (url = sr.ReadLine()) != null;)
                {
                    string data = client.DownloadString(url);
                    foreach (string line in data.Split('\n'))
                    {
                        // 雑に対応
                        if (line.Contains("<title>"))
                        {
                            // title タグがあったらイベント発行
                            TitleTagDetectedEventHandler h = TitleTagDetected;
                            if (h != null)
                            {
                                h(this, new TitleTagDetectedEventArgs(url, line));
                            }
                        }
                    }
                }
            }

            // 終了時も通知
            EventHandler completedHandlers = Completed;
            if (completedHandlers != null)
            {
                completedHandlers(this, EventArgs.Empty);
            }
        }
    }
}

urllist.txt は以下のような内容のテキストです。

urllist.txt
https://example.com
https://ufcpp.net
https://github.com

実行してみましょう。

https://example.com
    <title>Example Domain</title>
https://ufcpp.net
        <title>++C++; // 未確認飛行 C</title>
https://github.com
  <title>GitHub: Where the world builds software ・ GitHub</title>

ちゃんと取れてますね。

C# 9.0

こういう非同期処理で複数の値を返すようなものは非同期イテレーターが活きてくる。あと、ちゃんと IO 待ちは IO 待ちでスレッドを無駄にブロックはしない。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;

// await foreach 便利
await foreach (var (title, url) in Downloader.GetTitlesAsync())
{
    Console.WriteLine(url);
    Console.WriteLine(title);
}

class Downloader
{
    private static HttpClient _client = new();

    // 複数個の値を非同期で返すのもお手の物
    public static async IAsyncEnumerable<(string title, string url)> GetTitlesAsync()
    {
        var urlList = await File.ReadAllLinesAsync("urllist.txt");
        foreach (var url in urlList)
        {
            var body = await _client.GetStringAsync(url);
            // 雑に title タグのある行を取得
            var title = body.Split('\n').FirstOrDefault(x => x.Contains("<title>"));
            if (title != null)
            {
                // title タグの行と url を返す
                yield return (title, url);
            }
        }
    }
}

やっぱ非同期処理系は圧倒的に便利になってますね。

入力 2 つを整数かどうか判定して、さらに偶数かどうかも判定してメッセージを出しわける

ユーザーが入力した 2 つの文字列を、それぞれ整数かどうか判定し条件に応じて以下のようにメッセージを出しわける。

条件 メッセージ
両方整数でかつ両方偶数 両方偶数!!入力した値は XX と XX ですね!
両方整数 入力した値は XX と XX ですね!
どちらかが整数 おしいね…
どちらも整数ではない まだまだだね

今回は非常にシンプルだけど、イメージとしては特定のデータが来た時にルールによって振り分けるありがちな処理です。

C# 1.0

愚直に書いてみた。おしいね…ルートは共通化できたかもしれないけどいいや。

using System;

namespace CSharp1
{
    class Program
    {
        // int.TryParse は 1.0 の頃にはなかった
        static bool TryParse(string input, out int result)
        {
            try
            {
                result = int.Parse(input);
                return true;
            }
            catch
            {
                result = 0;
                return false;
            }
        }

        static void Main(string[] args)
        {
            string input1 = Console.ReadLine();
            string input2 = Console.ReadLine();
            int parsedValue1;
            int parsedValue2;
            bool isInput1Valid = TryParse(input1, out parsedValue1);
            bool isInput2Valid = TryParse(input2, out parsedValue2);

            // 素直に if で分岐
            if (isInput1Valid)
            {
                if (isInput2Valid)
                {
                    if (parsedValue1 % 2 == 0 && parsedValue2 % 2 == 0)
                    {
                        Console.WriteLine(string.Format("両方偶数!!入力した値は {0} と {1} ですね!", parsedValue1, parsedValue2));
                    }
                    else
                    {
                        Console.WriteLine(string.Format("入力した値は {0} と {1} ですね!", parsedValue1, parsedValue2));
                    }
                }
                else
                {
                    Console.WriteLine("おしいね…");
                }
            }
            else
            {
                if (isInput2Valid)
                {
                    Console.WriteLine("おしいね…");
                }
                else
                {
                    Console.WriteLine("まだまだだね");
                }

            }
        }
    }
}

C# 9.0

switch 式でパターンマッチが使えるようになったおかげで、事前に判断に必要な情報を整理しておいて、switch 式で分岐が簡単に出来るようになってるのが強い。

ちなみに下のコードは、自分が書いたコードを岩永さんに手直ししてもらいました。多謝!

using System;

var x1 = parseOrNull(Console.ReadLine());
var x2 = parseOrNull(Console.ReadLine());

var message = (x1, x2) switch
{
    (int v1, int v2) when isEven(v1) && isEven(v2) => $"両方偶数!!入力した値は {v1}{v2} ですね!",
    (int v1, int v2) => $"入力した値は {v1}{v2} ですね!",
    (int, null) or (null, int) => "おしいね…",
    (null, null) => "まだまだだね", // 網羅性チェックのためにあえて null, null。条件漏れ防いでる
};

Console.WriteLine(message);

bool isEven(int value) => (value % 2) == 0;

// input が NRT 対応で string?
// ひそかに target-typed 条件演算子で value : null が成り立ってる
int? parseOrNull(string? input) => int.TryParse(input, out var value) ? value : null;

if 文になくて switch 式にある特徴としてきちんと条件が網羅されているかどうか、抜け漏れがないかというのをコンパイラがチェックしてくれるというものがあります。
上のプログラムでも、最後を _ => "まだまだだね" にせずに明示的に (null, null) => "まだまだだね" にすることで、ちゃんと条件に抜け漏れがないということをコンパイラにチェックしてもらってます(岩永さん談)。

なるほどね switch 式便利。

まとめ

ということで、昔を思い出すために C# 1.0 の頃と C# 9.0 の頃とで同じお題をもとにいくつか処理を書いてみました。
ぱっと書いた感想としては、ジェネリクス、LINQ, async/await あたりが結構大きな変化だったのかなと感じました。

他にも細かな ?. 演算子や、今回は登場していない ????= などのような null の時によくやる処理を短く書けるようにしてくれていたり、タプル(値型のほうのタプル)も疑似的に戻り値を複数にしたりとか出来たりなどなど、沢山ありますが今回 C# 1.0 の機能だけに絞ってコードを書いてみて、暫くは書きたくないかな…と思うくらいには不便でした。

ということで 12/2 のアドベントカレンダーは以上になります。ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET(C#)でデータ暗号化(主にAES)を利用する時のメモ

.NETを使った暗号化の実装について気になることがあったので、メモ。

気になったこと

オンラインドキュメントの日本語訳が無茶苦茶だったので、英語から引用です。
要するに、AesCryptoServiceProviderAesManagedとかは普通は使わないでよくて、Aesを使うとよい、というように読めます。ネットのサイトを色々見てると作り方が色々のようで、どのクラスを使うのか迷ったのだけれど。どうも見ていると、基本はAes.Create()でインスタンスを作れば設定値は推奨のものが自動で入るっぽいですね(そんな風に読めた)。

In most cases, you don't need to directly reference an algorithm implementation class, such as AesCryptoServiceProvider. The methods and properties you typically need are on the base algorithm class, such as Aes.

確かに、MSサイトの例を見ると、Aes aes = Aes.Create()という例が多いですね。ちなみに、このCreateのFactoryメソッドの引数にはアルゴリズム(AES、AesManaged、AesCryptoServiceProvider)が指定できるので、何の型が返ってくるのか調べてみました。

検証コード、出力結果は以下です。

検証プログラム
using System;
using System.Security.Cryptography;

Aes def = Aes.Create();
Aes aes = Aes.Create("AES");
Aes aesManaged = Aes.Create("AesManaged");
Aes aesCSP = Aes.Create("AesCryptoServiceProvider");

AesManaged managed = new AesManaged();
AesCryptoServiceProvider CSP = new AesCryptoServiceProvider();

Console.WriteLine(def.GetType().Name);
Console.WriteLine(aes.GetType().Name);
Console.WriteLine(aesManaged.GetType().Name);
Console.WriteLine(aesCSP.GetType().Name);
Console.WriteLine(managed.GetType().Name);
Console.WriteLine(CSP.GetType().Name);
出力結果
AesImplementation
AesCryptoServiceProvider
AesManaged
AesCryptoServiceProvider
AesManaged
AesCryptoServiceProvider

testAES.PNG

"AES"の場合はAesImplementationなので、AesManagedとは別のクラスが使用されているようですね。下にも書いていますが、AesCryptoServiceProviderはWindowsの実装を呼び出すためのラッパーです。

Managedが付くやつはFIPS認定されていない

アルゴリズムの名称にMagagedが付くものは文字通り完全なマネージドとして記述されているものなのですが、MSのドキュメントによるとFIPS認定されていません。FIPS (Federal Information Processing Standards)は、アメリカの政府調達に関する標準です(FIPS 140-2については暗号化モジュールが満たすべき要件について定義している)。要するに、FIPS認定がないものは第三者のお墨付きがないから、それ以外を使った方が無難ということでしょうか(むしろ詳しい方がいれば教えてほしい)。

if (CryptoConfig.AllowOnlyFipsAlgorithms) {
    if (LocalAppContextSwitches.UseLegacyFipsThrow) {
        throw new InvalidOperationException(SR.GetString(SR.Cryptography_NonCompliantFIPSAlgorithm));
    }

AesCryptoServiceProviderは、Windowsの機能(CAPI)のラッパーのようです。このソースを見ると、AllowOnlyFipsAlgorithmsは見ていないようです。FIPS認証されているということでいいんですかね?

Rijndael

.NETのRijndael (とそれを継承したRijndaelManaged)は、SymmetricAlgorithmクラスを継承していますが、Aesも同様にSymmetricAlgorithmを継承しています。

RijndaelはAESと比べ、ブロック長が256bitまでの32bitの倍数で選べるが(鍵長についても同様)、AESは鍵長が128bit、192bit、256bitの3種類のみ。あと、ブロック長が128bitしか選べない。

要するに、AESとは別クラス。どちらが良いか…は何ともいえないけれど、AESは秘密鍵暗号方式としては標準と言ってよいほど色んなプログラムで実装されていて、今のところ危殆化とかそういう話題は見ない(今のところ)。むしろ、あれば大騒ぎになるはず。広く使われているので。

補足:WindowsにFIPS認定の使用を強制させる方法

MSの記事で、こんなのを見つけた(英語ですが)。

上で書いたFIPS認定の使用を強制させることができるらしいが、グループポリシーの編集で可能になるらしい。ただ、変更した後にどうなるかが怖いので、検証はしていません。

半ばメモなので、Qiitaの記事っぽくないかもしれませんが、ご了承下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む