20200803のC#に関する記事は5件です。

Blazor WebAssembly で input タグの値をリアルタイムに双方向バインディングしたい

Blazor WebAssembly

Blazor WebAssemblyはWebAssemblyによって.NET環境がブラウザに用意されC#で書いたコードがそのまま動いちゃうすごいやつです。そしてきちんとSPAフレームワークになっているのですがDOMとモデルの双方向バインディングで少しハマったので記録として残しておきます。

ASP.NET Core Blazor の概要
image.png

Blazor WebAssemblyは、.NETとC#などを用いてWebブラウザ上で実行可能なWebアプリケーションを開発できるフレームワークおよび実行系です。
Blazor WebAssembly 3.2.0はBlazor WebAssemblyとしてフル機能が実装され、本番運用に対応したバージョンです。これによりBlazor WebAssemblyは正式版としてリリースされたことになります。

publickeyより引用

要件

下記のようなinputタグの値を変えたら、リアルタイムでh2タグのインナーテキストも変更させることが要件です。

<h2>@name</h2>
<input type="text" />

NG実装

双方向バンディングの@bindを使ってみます。

NG.razor
<h2>@name</h2>
<input type="text" @bind="name" />

@code {
    string name = "";
}

上記では双方向にバインドできていますが、フォーカスが離れたり、エンターを押したりしないと反映されません

OK実装①

@bind-valueで双方向バインディングしつつ、@bind-value:eventでインプットイベント拾います。

OK1.razor
<h2>@name</h2>
<input type="text" @bind-value="name" @bind-value:event="oninput" />

@code {
    string name = "";
}

フォーカスアウトやエンター入力で発生する@bind異なり、テキストボックスの値が変更されたときにイベントが発生します。
単純な実装でコード量が少ないです。

OK実装②

@oninputで入力時に実行されるメソッドをバンディングします。
バインドしたメソッド内でnameの値を書き換えて画面に反映します。
値の変更だけでなく、何かしら処理をしたい場合に使います。

OK2.razor
<h2>@name</h2>
<input type="text" value="@name" @oninput="HandleInput" />

@code {
    string name = "";

    void HandleInput(ChangeEventArgs  e)
    {
        // メソッド内でなにか処理をできる
        name = e.Value.ToString();
    }
}

おまけ

@bindは下記のように@onchangeを使ったコードに内部的に変換されるらしく、
1つの要素に対して@bind@onchangeを同時に使用することはできないようです。

実際のコード
<input @bind="CurrentValue" />
内部的なコード
<input type="text" value="@CurrentValue"
    @onchange="@((ChangeEventArgs __e) => CurrentValue = __e.Value.ToString())" />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Standardプロジェクトを.NET FrameworkのテストプロジェクトでUTを作成した場合に認識されないときの対応方法

はじめに

情報がなく、かなり困ったので備忘録として残す。

現象

「.NET Standard 2.0」を対象のフレームワークとして指定したプロジェクトを「.NET Framework 4.6.2」を対象のフレームワークとしたテストプロジェクトでUTを実装していた。途中までは問題なく動作していたが、実現したい機能の都合上、.NET Standard 2.0でMicrosoft.Win32.Registryのnugetパッケージをインストールしたところ、テストプロジェクトで実装したUTがひとつも認識されなくなってしまった。(白抜きのひし形に水色の"!"が入った記号が表示される)

解決策

このStackOverFlowの回答をヒントに、以下の通り対応したら動作した。

  • MSTest.TestFramework, MSTest.TestAdapterを最新にする
  • Microsoft.Net.Test.SDKをnugetでインストールする

おわりに

裏は取れていないが、おそらくWin32APIを利用するようなAPIを持つnugetパッケージをインストールした時に起きると思われる。
Windows OS専用の仕組みを利用するnugetパッケージを利用する場合は注意が必要。

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

C#で汎用性の高いシンプルな乱数テーブルの実装・解説

概要

色々な種類のアイテムや敵をランダムで出現させたい、かつ、
確率だけ異なるパターンを複数用意させたい……。
そんなときにお手軽に使える、乱数テーブルを実装したので、詳しくご紹介します。
実装環境は、Unity C#です。

解説

今回実装するプログラムが最も有効活用出来るのは、
わかりやすい例でいうと、マリオカートのアイテムボックスのような例ですね。

マリオカートでは、8人でのレースの場合、1位~8位の、計8パターンが存在し、順位が低くなるほどレアなアイテムの出現確率が上がります。

例えば、バナナ・こうら・キノコ・スター、それぞれ4種類のアイテムが、1~8位で異なる確率で出現するとして、確率を百分率で表にすると、以下のようになります。

バナナ こうら キノコ スター
1位 60 30 10 0
2位 40 40 20 0
3位 20 50 30 0
4位 0 60 40 0
5位 0 40 40 20
6位 0 20 40 40
7位 0 0 40 60
8位 0 0 20 80

このようなテーブルを実際にプログラムとして実装する場合、通常の配列では1~8位それぞれの順位ごとの出現確率を8個分用意しなければなりません。
そこで今回は「ジャグ配列」という、配列を複数重ね合わせられる機能を使います。

乱数のアルゴリズム

今回は8つのアイテムの出現確率を、状況によって3つの確率パターンに変化させて出現させます。
使用するジャグ配列は、以下の通りになります。

private int[][] itemTable = new int[][]
{
    new int[] { 4, 4, 4, 4, 4, 4, 4, 4},
    new int[] { 0, 0, 0, 0, 4, 4, 4, 4},
    new int[] { 8, 7, 6, 5, 4, 3, 2, 1}
};

まず初めに、
private int[][] itemTable = new int[][]
このコードですが、通常の配列と異なり、かっこが2つ付いていることがわかります。
これがジャグ配列の宣言となります。

そして、itemTableという配列の中で、
new int[] { 4, 4, 4, 4, 4, 4, 4, 4},
new int[] { 0, 0, 0, 0, 4, 4, 4, 4},
new int[] { 8, 7, 6, 5, 4, 3, 2, 1}
これらの3つの配列を宣言しています。
さらにこれら3つの配列には、それぞれ8つの値を持っていることがわかります。
この値こそが、実際に乱数テーブルとしての役割を持つ値となります。

まず配列の1番目の中身の値、
new int[] { 4, 4, 4, 4, 4, 4, 4, 4},
では、4の値が8つ並んでいます。
(以降、この4に該当する値のことを「範囲値」と呼びます)

ランダムなアイテムを1つだけ選ぶ、という機能を実装する場合は「1~範囲値の合計(今回の場合は4*8=32)」の中からランダムな数値を1つ選び、その値がどの範囲値に属するかを調べます。
その計算方法についてですが、例えば範囲値の1番目が4となっています。これは1~4の範囲の値を取る、ということです。
次の範囲値も4なので、前回の範囲値(4)を足して、その次は5~8の範囲の値を取ります。
このように範囲値を足していくことで、乱数が何番目の範囲値に属するかを計算します。

範囲値の確率は「範囲値/合計値」で今回は全ての値が4なので、1~8番目の範囲値のどれに属するか、という確率は全て4/32となり、約分すると1/8となります。
確率的には
new int[] { 1, 1, 1, 1, 1, 1, 1, 1},
と同じですね。

配列の2番目では、
new int[] { 0, 0, 0, 0, 4, 4, 4, 4},
となっています。
配列の1番目と異なり、1~4番目の範囲値が0なので、乱数がこれらの範囲値に属することはありません。
このように選択したくない範囲値に対しては0を入れて、状況によって絶対に選択させない、といったことが可能です。
5~8番目の範囲値の確率はそれぞれ4/16となり、約分すると1/4となります。

配列の3番目は、
new int[] { 8, 7, 6, 5, 4, 3, 2, 1}
となっています。
範囲値の最初が8で、そこから-1ずつ範囲値が小さくなっていき、範囲値の最後は1となっています。
範囲値の合計値は1/2*8*(8+1)=36で、範囲値の確率は、8/36 ~ 1/36となります。
分母が36だと、ちょっとわかりづらいですね。
百分率で確率を表すと、以下の通りになります。

8/36 = 22.222...
7/36 = 19.444...
6/36 = 16.666...
5/36 = 13.888...
4/36 = 11.111...
3/36 = 8.333...
2/36 = 5.555...
1/36 = 2.777..

コードと実装方法

private int[][] itemTable = new int[][]
{
    new int[] { 4, 4, 4, 4, 4, 4, 4, 4},
    new int[] { 0, 0, 0, 0, 4, 4, 4, 4},
    new int[] { 8, 7, 6, 5, 4, 3, 2, 1}
};

private int TableGenerate(int tableNumber)
{
    // 検索用の配列を代入
    var _itemTable = itemTable[tableNumber - 1];

    // 判定値を計算
    var totalNumber = 0;
    var searchTable = new int[_itemTable.Length];
    for (int i = 0; _itemTable.Length > i; i++)
    {
        totalNumber += _itemTable[i];
        searchTable[i] = totalNumber;
    }

    // 乱数を計算
    var randomNumber = Random.Range(1, totalNumber + 1);

    // 乱数がどの範囲値に属するか検索
    for (int i = 0; searchTable.Length > i; i++)
    {
        if (searchTable[i] >= randomNumber)
        {
            // 乱数が、「判定値よりも小さい」ならば結果を返す
            return i + 1;
        }
    }

    //randomNumberがsearchTableの最後の値より大きいことはありえないのでここに到達することはない
    return 0;
}

使い方はとても簡単で、最初(1番目)の配列のテーブルを使用したい場合は、
var itemValue = TableGenerate(1);
と書くだけで、itemValueに配列の1番目を使用した乱数の結果(1~8のどれか)が返されます。
状況合わせて自由に2番目や3番目のテーブルに切り替えることもできます。

乱数のテスト

試しに3つの配列をそれぞれ8000・4000・8000(有効な範囲値の数*1000)回数分、乱数を生成して、それぞれの範囲値の値が返された回数を計算してみました。

1番目の配列 2番目の配列 3番目の配列
1 979 - 1727
2 1029 - 1573
3 1005 - 1360
4 971 - 1149
5 994 983 901
6 1027 1003 656
7 999 1017 402
8 996 997 232

平均出現回数は「試行回数 / 範囲値の数」なので、3つとも平均出現回数は1000となります。
実際の結果も、平均がだいたい1000に違い数値で、しっかりと確率通りになっていますね。

あとはこの結果の値をSwitch文なりで分岐し、それぞれ異なるアイテムをインスタンスとして生成すればOKです。

また、配列テーブルを変更するだけで、確率を自分好みに変更することができます。

お疲れさまでした:bow:

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

C#のテストコードをサクッと試す時はdotnet-script環境を整えておくと便利

C#で開発時、ちょっとしたテストコードをスクリプト言語のようにコンパイルを挟まず実行できる環境を用意しておくと良いと考えています。C#はコンパイルして実行するまでに、そこそこ手間がかかりますし。

ググるとC#インタラクティブ1、csi2という手段も出てきます。
ただし今回は以下の条件から、dotnet-scriptに採用します。
https://github.com/filipw/dotnet-script

  • 開発環境はmacOS
  • IDEに依存しない

.NET Core 2.1SDKのインストール

本家のマニュアルに従ってdotnet-scriptの環境を構築していきますが、実行させるにあたって.NET Core 2.1SDKは必須です。以下のサイトからダウンロードしてインストールしておきます。
https://dotnet.microsoft.com/download/dotnet-core
※C#8.0を使用する場合は.NET Core 3.1SDKをインストールします。ちなみに.NET Core3.1SDKだけではdotnet-scriptは動きません。

dotnet-scriptのインストール

dotnet toolでインストール

プラットフォーム問わず、以下のコマンドでdotnet-scriptをインストールできます。

$ dotnet tool install -g dotnet-script

curlでインストール

macOSの場合は以下のcurlコマンドでもインストールできます。

$ curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | bash
# もしパーミッション周りでエラーを吐く場合は以下のコマンドを実行してください
$ curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | sudo bash

(補足)エディタはVisualStudioCodeを使用

使用するエディタは何でも良いですが、僕はサクッと環境を構築できるVisualStudioCodeを使いました。

  • MicrosoftのC#
  • LeopotamのC# FixFormat

この2つをExtensionsをインストールしておきます。
コード補完が利くようになりShift + Option + Fでソース整形されるようになります。

これで環境構築は完了です。

Hello world!する

$ dotnet script init

initコマンドを実行すると、以下のテンプレートファイルが生成されます。3

├── main.csx
└── omnisharp.json

以下のコマンドを実行するとHello world!と出力されます。

$ dotnet script main.csx

パスの扱い

指定したパスの扱いについて調査していきます。
以下のファイル構成をサンプルに調査を進めます。

├── Sample1.csx(dotnet-script実行ファイル)
├── test01.txt
└── test02.txt

試しに以下のソースコードを実行してみます。

var files = Directory.GetFiles(".");
foreach(var filePath in files)
{
    Console.WriteLine(filePath);    
}

以下のログ出力の通り、実行ファイル(Sample.csx)を起点にした相対パスのようです。

./test01.txt
./test02.txt
./Sample1.csx

コマンドライン引数の取得方法

$ dotnet-script Sample1.csx arg1 arg2 arg3

上記のようにコマンドライン引数にarg1, arg2, arg3と3つの値をセットしてスクリプト側で受け取る方法です。

Console.WriteLine(Args[0]);
Console.WriteLine(Args[1]);
Console.WriteLine(Args[2]);

このようにArgsに詰め込まれているので、上記のようなコードで受け取ることが出来ます。
ちなみにArgsは IList<string>ですので、要素数を取得したい場合は、LengthではなくCountを使用します。

コマンドライン引数が必須のスクリプトの場合、以下のようにArgsがnullになる場合があり、以下のようなエラーが出力されます。

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

このようなnullチェックを事前に走らせた方が良いでしょう。

if (Args == null && Args.Count <= 0){
   // コマンドライン引数はありません
   return;
}

ファイルのリネーム処理サンプル

最後に指定ディレクトリ内の、.txtファイル.csファイルにファイルリネームするサンプルを作ってみました。

RenameTxt2Cs.csxの一部
var dirPath = Args[0];
var files = Directory.GetFiles(dirPath, "*.txt");
foreach(var filePath in files)
{
    var dirPath = Path.GetDirectoryName(filePath);
    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
    var newPath = $"{dirPath}/{fileNameWithoutExtension}.cs";
    File.Move(filePath, newPath);
}

全ソースはコチラ

以下のように引数にディレクトリパスを指定してdotnet-scriptを実行します。

$ dotnet-script RenameTxt2Cs.csx 任意のフォルダパス(絶対パス or 相対パス)

最後に

dotnet-scriptを使うとC#をインタプリタ型言語のようにライトにC#を実行できるようになります。
本記事では触れていませんがREPLでも動作します。
テストコードを書いてサクッと挙動をチェックする時などに使えますのでC#を扱うエンジニアはdotnet-scriptが動く環境を作っておくと開発効率が上る可能性はあるなと思いました。

トラブルシューティング

.NET Core2.1が未インストールエラー

dotnet-scriptコマンド実行時に以下のエラーが出た場合は.NET Core2.1が未インストールです。
インストールすると解決します。
バイナリはコチラからダウンロードできます。

It was not possible to find any compatible framework version
The framework 'Microsoft.NETCore.App', version '2.1.0' was not found.
  - The following frameworks were found:
      3.1.6 at [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

You can resolve the problem by installing the specified framework and/or SDK.

The specified framework can be found at:
  - https://aka.ms/dotnet-core-applaunch?framework=Microsoft.NETCore.App&framework_version=2.1.0&arch=x64&rid=osx.10.15-x64

dotnet scriptdotnet-scriptコマンド

dotnet toolでインストールした場合はdotnet script、curlでインストールした場合はdotnet-scriptを使用します。少しハマりました。

環境

  • dotnet-script v0.53.0
  • macOS 10.15.5

  1. C#インタラクティブはVisualStudio依存 

  2. csiはMac向けにリリースされていないため、本記事では扱わない(参考:https://www.buildinsider.net/language/csharpscript/01

  3. 既にcsxファイルが存在する場合は生成されません 

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

C#のテストコードをサクッと試す時はdotnet-scriptとVSCodeの環境を整えておくと便利

C#で開発時、ちょっとしたテストコードをスクリプト言語のようにコンパイルを挟まず実行できる環境を用意しておくと良いと考えています。C#はコンパイルして実行するまでに、そこそこ手間がかかりますし。

ググるとC#インタラクティブ1、csi2という手段も出てきます。
ただし今回は以下の条件から、dotnet-scriptに採用します。
https://github.com/filipw/dotnet-script

  • 開発環境はmacOS
  • IDEに依存しない

.NET Core 2.1SDKのインストール

本家のマニュアルに従ってdotnet-scriptの環境を構築していきますが、実行させるにあたって.NET Core 2.1SDKは必須です。以下のサイトからダウンロードしてインストールしておきます。
https://dotnet.microsoft.com/download/dotnet-core
※C#8.0を使用する場合は.NET Core 3.1SDKをインストールします。ちなみに.NET Core3.1SDKだけではdotnet-scriptは動きません。

dotnet-scriptのインストール

dotnet toolでインストール

プラットフォーム問わず、以下のコマンドでdotnet-scriptをインストールできます。

$ dotnet tool install -g dotnet-script

curlでインストール

macOSの場合は以下のcurlコマンドでもインストールできます。

$ curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | bash
# もしパーミッション周りでエラーを吐く場合は以下のコマンドを実行してください
$ curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | sudo bash

(補足)エディタはVSCodeを使用

使用するエディタは何でも良いですが、僕はサクッと環境を構築できるVisualStudioCode(以下:VSCode)を使いました。

  • MicrosoftのC#
  • LeopotamのC# FixFormat

この2つをExtensionsをインストールしておきます。
コード補完が利くようになりShift + Option + Fでソース整形されるようになります。

これで環境構築は完了です。

Hello world!する

$ dotnet script init

initコマンドを実行すると、以下のテンプレートファイルが生成されます。3

├── main.csx
└── omnisharp.json

以下のコマンドを実行するとHello world!と出力されます。

$ dotnet script main.csx

パスの扱い

指定したパスの扱いについて調査していきます。
以下のファイル構成をサンプルに調査を進めます。

├── Sample1.csx(dotnet-script実行ファイル)
├── test01.txt
└── test02.txt

試しに以下のソースコードを実行してみます。

var files = Directory.GetFiles(".");
foreach(var filePath in files)
{
    Console.WriteLine(filePath);    
}

以下のログ出力の通り、実行ファイル(Sample.csx)を起点にした相対パスのようです。

./test01.txt
./test02.txt
./Sample1.csx

コマンドライン引数の取得方法

$ dotnet-script Sample1.csx arg1 arg2 arg3

上記のようにコマンドライン引数にarg1, arg2, arg3と3つの値をセットしてスクリプト側で受け取る方法です。

Console.WriteLine(Args[0]);
Console.WriteLine(Args[1]);
Console.WriteLine(Args[2]);

このようにArgsに詰め込まれているので、上記のようなコードで受け取ることが出来ます。
ちなみにArgsは IList<string>ですので、要素数を取得したい場合は、LengthではなくCountを使用します。

コマンドライン引数が必須のスクリプトの場合、以下のようにArgsがnullになる場合があり、以下のようなエラーが出力されます。

System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

このようなnullチェックを事前に走らせた方が良いでしょう。

if (Args == null && Args.Count <= 0){
   // コマンドライン引数はありません
   return;
}

ファイルのリネーム処理サンプル

サンプルを1つ作ってみます。

以下は指定ディレクトリ内の、.txtファイル.csファイルにファイルリネームするというものです。

RenameTxt2Cs.csxの一部
var dirPath = Args[0];
var files = Directory.GetFiles(dirPath, "*.txt");
foreach(var filePath in files)
{
    var dirPath = Path.GetDirectoryName(filePath);
    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
    var newPath = $"{dirPath}/{fileNameWithoutExtension}.cs";
    File.Move(filePath, newPath);
}

全ソースはコチラ

以下のように引数にディレクトリパスを指定してdotnet-scriptを実行します。

$ dotnet-script RenameTxt2Cs.csx 任意のフォルダパス(絶対パス or 相対パス)

最後に

dotnet-scriptを使うとC#をインタプリタ型言語のようにライトにC#を実行できるようになります。
本記事では触れていませんがREPLでも動作します。
テストコードを書いてサクッと挙動をチェックする時などに使えますのでC#を扱うエンジニアはdotnet-scriptが動く環境を作っておくと開発効率が上る可能性はあるなと思いました。

トラブルシューティング

.NET Core2.1が未インストールエラー

dotnet-scriptコマンド実行時に以下のエラーが出た場合は.NET Core2.1が未インストールです。
インストールすると解決します。
バイナリはコチラからダウンロードできます。

It was not possible to find any compatible framework version
The framework 'Microsoft.NETCore.App', version '2.1.0' was not found.
  - The following frameworks were found:
      3.1.6 at [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

You can resolve the problem by installing the specified framework and/or SDK.

The specified framework can be found at:
  - https://aka.ms/dotnet-core-applaunch?framework=Microsoft.NETCore.App&framework_version=2.1.0&arch=x64&rid=osx.10.15-x64

dotnet scriptdotnet-scriptコマンド

dotnet toolでインストールした場合はdotnet script、curlでインストールした場合はdotnet-scriptを使用します。少しハマりました。

環境

  • dotnet-script v0.53.0
  • macOS 10.15.5

  1. C#インタラクティブはVisualStudio依存 

  2. csiはMac向けにリリースされていないため、本記事では扱わない(参考:https://www.buildinsider.net/language/csharpscript/01

  3. 既にcsxファイルが存在する場合は生成されません 

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