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

Visual Studio が Equals、GetHashCode を自動生成できるようになっていた

Visual Studio が Equals、GetHashCode を自動生成できるようになっていたので備忘のためにメモ。
==!= 演算子や IEquatable<T> も生成できる。

助かる。

情報源

Generate Equals and GetHashCode method overrides in Visual Studio

Visual Studio 2017 を対象とした 2 年以上前の情報なのでこのあたりから提供されていたのか。。

Visual Studio で Equals および GetHashCode メソッドのオーバーライドを生成する

日本語の情報もあるな。。が、書いてしまったんで。

手順

  1. 対象の クラス にカーソルを移動して、キーボードで Ctrl + . を入力する。このとき、クラス名を選択 (反転) 状態にしないよう注意。
  2. もしくは、クラス名の行を右クリックして クイック アクションとリファクタリング をクリックするでも可。
  3. コンテキストメニューの Equals および GetHashCode もしくは Equals(object) を生成するをクリックする。
  4. [メンバーの選択] ダイアログで、評価するメンバーを指定する。
  5. 必要に応じて IEquatable<T> を実装する、および 演算子を実装する をチェックする。
  6. [OK] ボタンをクリックする。

確認した環境

  • Windows 10 Home 64 bit バージョン 2004
  • Microsoft Visual Studio Community Edition 2019 Version 16.4.6
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#OpenCV と #csharp を使って、 #TETRIS の動画からミノの積み位置を抽出するツール

githubと改行ルールが違って二重管理になるため、ここにはgithubのREADME.mdのコピペを載せます。
(githubにあっても検索で探せないため。)
https://github.com/github895439/tetris_stacking_position_picker

README.md
本内容を利用した場合の一切の責任を私は負いません。

機能
テトリスの動画を解析して、ミノの積み位置を抽出します。
「 https://www.youtube.com/watch?v=kJohHun8ANI 」の左フィールドを抽出したものが https://github.com/github895439/tetris_stacking_position_picker/tree/master/log です。
(ログの「表示無し」をチェック入りの状態で40分弱かかりました。 github上で表示するとフォントの都合で崩れているが、RAWデータをダウンロードすれば崩れない。)
基本的に、画面のログとログファイル(実行ファイルの場所に出力)の内容は同じです。
ログファイルは1ファイル/クレジットです。
ログの内容は、

□:ミノ落ち前後で存在したままのブロック
■:ミノが落ちる前と比較して、増えたブロック(=落ちたミノ)
◆:ミノが落ちる前と比較して、減ったブロック

ライン消しがあった場合とその後近辺はライン消しの影響があり、判別が困難なため、間違った結果になっています。
対戦モードには対応していません。
同ミノ連続した場合も判別できないため、2ミノが一度に落ちたような結果になっています。
「f」単位は経過フレームで、タイムスタンプはそれを時間換算したものです。

バージョン
OS
OS 名: Microsoft Windows 10 Home
OS バージョン: 10.0.18362 N/A ビルド 18362
システムの種類: x64-based PC
開発
Visual Studio Community 2015(以降、VS)
OpenCVSharp4 v4.2.0.20200208(nuget)
OpenCVSharp4.runtime.win v4.2.0.20200208(nuget)
OpenCVSharp4.Windows v4.2.0.20200208(nuget)
ビルド
本ツールをダウンロード
https://github.com/github895439/tetris_stacking_position_picker

ダウンロードしたものを展開

展開したソリューションをVSでオープン

OpenCVSharpをインストール
VSのプロジェクトの右クリックメニューの「NuGetパッケージの管理」を選択する。
「opencvsharp4」を検索し、結果一覧の「OpenCVSharp4.Windows」をインストールする。
(依存関係により、バージョンに挙げた残り2つのOpenCVSharpも自動的にインストールされる。)
「インストール済み」タブにバージョンに挙げた3つのOpenCVSharpが表示される。

ソリューションのビルド

使い方
直接入力か、「動画ファイル選択」ボタンで動画ファイルを選択

下記の設定を入力(座標の特定は「?」ボタンにガイド有り)

NEXT判定座標
プレイ開始判定座標
プレイ終了判定座標
フィールド判定座標
「抽出実行」ボタン押下(※markdownのせいで番号がおかしく、手順の続き)
その他:

「動画ファイル概要」ボタン
動画ファイルの情報を表示します。

「動画ファイル変更」ボタン
情報表示や抽出を行った場合はデータを破棄する必要があるため、このボタンを押します。
このボタンを押すと、動画ファイルのパスや「動画ファイル概要」ボタンが復帰します。

「抽出中止」ボタン
抽出を途中で止めます。

「終了」ボタン
ツールを終了します。

備考
下記は設計メモです。
(「追記」以降がコーディングを始めてから追記した内容です。)
https://github.com/github895439/tetris_stacking_position_picker/blob/master/design_memo.txt
フレームの確認はaviutlを使用しています。
設定値を変えれば右のフィールドも抽出できるはず。 
動画ファイルを変えて、動画に基づいた設定値にすれば、他の動画からも抽出できるはず。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity超初心者】NullReferenceExceptionというエラーについて知る【初見殺し】

はじめに

amazonで高評価だったのとタイトルが気になり「Unityで神になる本」という本を結構前に買いました。
書籍内に大砲みたいなのをつくれるチャプターがあるのですが、
内容に沿って進めてうまくいかなかったので
どこがおかしかったのかわかるような気づきの記事になるように残したいと思います。

できあがったもの

できあがってうれしかったのでYoutube動画でうpしました
こんなかんじで動きます
https://www.youtube.com/watch?v=mhkqT4d-S40

この書籍の注意点

2015/6/26に発売した本です。載ってる情報が若干古いです。
(紹介されているアセットストアの無料アセットが有料になってたりしています)

つまずいたスクリプト

「Destruction.cs」というスクリプトを作成しました。
「Destruction」は直訳すると「破壊」です。
大砲の玉をとばして車を破壊する処理についてのスクリプトなのでこのような命名になってます。

コメントは勉強不足な自分が読み返して何をしているのかわかるようにつけているので、慣れている人がソースを見れば処理の内容がわかるものについても記述されている状態だと思います。

・エラーメッセージ
NullReferenceException: Object reference not set to an instance of an object
Destruction.Start () (at Assets/Destruction.cs:47)

Destruction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//P.228 玉がボログルマに着弾するとボログルマがだんだん壊れていくスクリプト

public class Destruction : MonoBehaviour
{
    // 玉がぶつかった回数を記憶する int hitCount
    int hitCount = 0;

    // Start後にオブジェクト自身のRigidbodyを参照して情報を格納するためのrb
    public Rigidbody rb;

    // GameObject型のDamageLevels、GameObjectのもの(ボログルマ3形態)のみ配列に入れる
    public GameObject[] DamageLevels;

    // 煙エフェクト Unity側でSmokeをドロップする
    public GameObject SmokePt;

    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for(var n = 0; n < 3; n++)
        {
            // 1段階壊れたオブジェクトから後+1ずつ呼び出す、3段階目まで
            // 非アクティブ(ディアクティベート)状態の子オブジェクトも取得できるtransform.Find
            /*(子オブジェクトの参照先がみつからない、原因分からず)NullReferenceException: Object reference not set to an 
            instance of an object
            Destruction.Start () (at Assets/Destruction.cs:35)*/
            DamageLevels[n] = transform.Find("damage_level1" + (n + 1)).gameObject;
        }
    }

    // BulletBehaviour.csから送られてきたら反応するAdd_Damageメソッド
    void Add_Damage()
    {
        // 玉がぶつかるたびに煙のsmをinstantiate(インスタンス化する、シーン中に表示させる)
        GameObject sm = Instantiate(SmokePt, transform.position, transform.rotation);
        // smoke出現後はボログルマを親とする
        sm.transform.parent = transform;
        // hitcount2以上はreturn、玉からAdd_Damageが呼ばれるとメソッドの処理は終わり
        if (hitCount > 2)
            return;
        // ボログルマのダメージが0の状態はSmokeを非表示
        DamageLevels[hitCount].SetActive(false);

        // インクリメント hitCountという整数の値に1を足すこと
        hitCount++;

        // ボログルマのダメージ1以上の状態はSmokeを表示
        DamageLevels[hitCount].SetActive(true);
    }

}

ログに表示

Debug.Log(DamageLevels);

たぶんうまくいってるデバッグログ.JPG

画像のログを確認したところ3つ分のボログルマオブジェクトを認識しているっぽい

Unity側のヒエラルキー内

赤枠が対象の1段階壊れたボログルマです
最初はdamage_level1をシーン上に表示してある状態で、
それ以降の状態のオブジェクトはディアクティベート(シーン上から非表示状態)にしています。
つまったとき、ここのオブジェクト名称とスクリプト内35行目の「"damage_level1"」を何度も見返して
名前が間違ってないか見てました。
4章のヒエラルキー.JPG

本をよく読み返したら

for文の部分は同じような構文を繰り返す場合に、ソースコードの見通しがよくなるという意味での参考でした、
短縮しないでひとつずつ配列を用意しても同様のエラーが出るか下記のように修正して試したら
なぜかちゃんと動いちゃいました。

Destruction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//P.228 玉がボログルマに着弾するとボログルマがだんだん壊れていくスクリプト

public class Destruction : MonoBehaviour
{
    // 玉がぶつかった回数を記憶する int hitCount
    int hitCount = 0;

    // Start後にオブジェクト自身のRigidbodyを参照して情報を格納するためのrb
    public Rigidbody rb;

    // GameObject型のDamageLevels、GameObjectのもののみ配列に入れる
    public GameObject[] DamageLevels;

    // 煙エフェクト Unity側でSmokeをドロップする
    public GameObject SmokePt;

    // ビルトイン配列を定義 配列数を指定して子オブジェクトをtransform.Findで探す、damage_levelのGameObject
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        DamageLevels = new GameObject[3];
        DamageLevels[0] = transform.Find("damage_level1").gameObject;
        DamageLevels[1] = transform.Find("damage_level2").gameObject;
        DamageLevels[2] = transform.Find("damage_level3").gameObject;
    }

    /*
    // For文を使うともう少しすっきりする
    // (子オブジェクトの参照先がみつからない、原因分からず)NullReferenceException: Object reference not set to an instance of an object
       Destruction.Start () (at Assets/Destruction.cs:47)
    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for(var n = 0; n < 3; n++)
        {
            // 1段階壊れたオブジェクトから後+1ずつ呼び出す、3段階目まで
            // 非アクティブ(ディアクティベート)状態の子オブジェクトも取得できるtransform.Find
            DamageLevels[n] = transform.Find("damage_level1" + (n + 1)).gameObject;
        }
    }
    */

    // BulletBehaviour.csから送られてきたら反応するAdd_Damageメソッド
    void Add_Damage()
    {
        // 玉がぶつかるたびに煙のsmをinstantiate(インスタンス化する、シーン中に表示させる)
        GameObject sm = Instantiate(SmokePt, transform.position, transform.rotation);
        // smoke出現後はボログルマを親とする
        sm.transform.parent = transform;
        // hitcount2以上はreturn、玉からAdd_Damageが呼ばれるとメソッドの処理は終わり
        if (hitCount > 2)
            return;
        // ボログルマのダメージが0の状態はSmokeを非表示
        DamageLevels[hitCount].SetActive(false);

        // インクリメント hitCountという整数の値に1を足すこと
        hitCount++;

        // ボログルマのダメージ1以上の状態はSmokeを表示
        DamageLevels[hitCount].SetActive(true);
    }

}

ググる

過去に同じ書籍を進めて質問していたであろう人の質問とそれに対しての回答や、記事を読みました。

情報をざっくり整理すると
・Transform.Findで取得できるのは、アクティブ・非アクティブに関わらず子オブジェクトのみ

つまり、やろうとしていることは間違ってないことがわかりました。
書籍の参考がおかしいことになってるなと思いStartメソッドをを下記のように改修したら
ちゃんと動くようになりました。

Destruction.cs
    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for (var n = 0; n < 3; n++)
        {
            //アクティブなオブジェクト"damaged_transporter_01"をまず見つけ出し、その子オブジェクトを探索して"damage_level1"を見つける
            DamageLevels[n] = GameObject.Find("damaged_transporter_01").transform.Find("damage_level1").gameObject;
        }
    }

おわりに

去年の11月に購入した書籍なんですが、今回のように詰まってばかりなため
いつ読み終えることができるか心配なところです。
ただじっくり進めているおかげか今のところ挫折せずに楽しみながら進められています。

この記事を書きながら親オブジェクトを参照し子オブジェクトを探索するというやり方に気づけたので、
気づきの記事にするという目標は達成できました。

これにゲームの要素を取り入れるとしたら玉の段数を10発までにして
90秒の間にボログルマを何台破壊できるかとかでしょうか、

これなら1ダウンロード10円くらいで販売できそうですかね

参考サイト

▼Find関数について
http://kimama-up.net/unity-find/

▼Find系関数で注意すべきこと4つ
http://mediamonster.blog.fc2.com/blog-entry-7.html

▼transform.Findでオブジェクトを取得できない
https://teratail.com/questions/53384

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

Lambda式が使える汎用ValueConverterを作ったが、いまいちだった話

WPFで値をBindingする際、そのままの値を使うのではなく
定数を加算、2倍、逆数など 簡単な演算をしたい事が度々あります。
毎回それ用にプロパティを用意したり、専用ValueConverterを作るのも面倒なので
Lambda式で処理を渡せる汎用のValueConverterを作ってみました。

こちらに同じような記事があったのですが
Converterを書くのが面倒だというはなし
IronPythonか~と思い、自分でも作ることに。

結果としては上手くいかず、今回は専用のValueConverterを作ることにしたのですが、
使える場面もあるかなと思い、ここに残す事にしました。

上手く行かなかった事

Microsoft.CodeAnalysis.CSharp.Scriptingを使ってるのですが
EvaluateAsyncの初回起動が遅いためか、アプリそのものの起動時間がかかるようになってしまいました
(今回のケースでは起動時からTextBoxに初期値が入っていてConverterが走ります)。
Bindingの際にIsAsync=Trueとすると起動時間は短縮できるものの、
FallbackValueには固定値しか設定できないため、値が変わる度にちらついてしまいます。
Converter完了まで前回の値を表示しておく事ができれば良いのですが…

実装

Lambda式をConverterParameterで文字列として受け取り
Microsoft.CodeAnalysis.CSharp.Scriptingで処理しています。

Converter.cs
public class LambdaConverter : IValueConverter
{
    public Object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var func = CSharpScript.EvaluateAsync<Func<dynamic, dynamic>>($"(Func<dynamic, dynamic>)({parameter as String})",
            ScriptOptions.Default
            .WithImports("System")
            .WithReferences(typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly)
            ).Result;
        return func(value);
    }

    public Object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ConverterParameterで処理内容をLambda式で定義します。

View.xaml
<TextBox x:Name="InputData" Width="100"
         Text="{Binding InputData, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged, FallbackValue=0}"/>
<TextBlock Margin="10,0"
           Text="{Binding InputData, Converter={StaticResource LambdaConverter}, ConverterParameter='e => e + 1'}"/>
 <TextBlock
           Text="{Binding InputData, Converter={StaticResource LambdaConverter}, ConverterParameter='e => 1 / e'}"/>

こんな感じ
example.gif

起動さえすれば、実行中の速度は気にならないんですけどね~

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

P/Invokeと各種String作成ベンチマーク

C# DllExportでWindowスタイルなAPIをC#風に扱う - Qiita という記事を見かけたので、コメントついでに関連ネタです。

Win32APIで結果が文字列の場合、呼び出し側が確保した領域のポインタを渡してそこに書き込んでもらうという手段が一般的です。(代表例:GetWindowTextやGetTempPath)

一方、C#の文字列(System.String)は基本的に不変であり、そのメモリ領域に書き込みを行ってはなりません。(参考 → String クラス (System) )

そこで、C#からP/Invokeで書き換えが必要なAPIを呼び出すときは書き換え可能な領域を確保して渡し、そこからstringを作成するという手順が取られます。それぞれのパフォーマンスを比較してみました。

ソースコード

実行可能なサンプルはこちらに上げました。
https://github.com/KageShiron/StringPinvokePeformance

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetTempPathW")]
static extern int GetTempPath1(uint nBufferLength, StringBuilder sb);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetTempPathW")]
static extern int GetTempPath2(uint nBufferLength, ref char sb);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetTempPathW")]
static extern unsafe int GetTempPath3(uint nBufferLength, char* sb);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetTempPathW")]
static extern unsafe int GetTempPath4(uint nBufferLength, string sb);

public string StringBuilder()
{
    StringBuilder sb = new StringBuilder(261);
    GetTempPath1(260, sb);
    return sb.ToString();

}
public string StackAllocSpan()
{
    Span<char> buff = stackalloc char[261];
    GetTempPath2(260, ref buff.GetPinnableReference());
    return new string(buff);
}
public string StackAllocSpanToString()
{
    Span<char> buff = stackalloc char[261];
    GetTempPath2(260, ref buff.GetPinnableReference());
    return buff.ToString();
}
public unsafe string StackAllocPointer()
{
    char* buff = stackalloc char[261];
    GetTempPath3(260, buff);
    return new string(buff);
}
public string StackAllocCreate()
{
    return string.Create(260, 0,
        (b, _) => { GetTempPath2(260, ref b.GetPinnableReference()); }
        );
}
public string DangerousNewString()
{
    // Dangerous
    string buff = new string('\0', 260);
    GetTempPath4(260, buff);
    return buff;
}

ベンチマーク結果

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-2600K CPU 3.40GHz (Sandy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100
  [Host]   : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
  ShortRun : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
StringBuilder 625.0 ns 354.39 ns 19.43 ns 0.1659 - - 696 B
DangerousNewString 352.5 ns 89.41 ns 4.90 ns 0.1297 - - 544 B
StringCreate 349.4 ns 30.39 ns 1.67 ns 0.1297 - - 544 B
StackAllocSpan 388.4 ns 40.02 ns 2.19 ns 0.1297 - - 544 B
StackAllocSpanToString 392.8 ns 65.17 ns 3.57 ns 0.1297 - - 544 B
StackAllocPointer 338.0 ns 24.29 ns 1.33 ns 0.0229 - - 96 B

※解説のしやすさのために表の順番を入れ替えています

解説

StringBuilder

まず、StringBuilderを使う方法はググるとよく出てきますが、StringBuilder自体をヒープに確保する必要があるため論外に近いです。スタックに乗らないほど巨大なバッファが必要な場合には使える・・・といいたいところですが、その場合はおそらくArrayPoolからchar[]バッファをもらってくるのが正攻法でしょう。

DangerousNewString

名前の通り、危険なやり方
new stringしたメモリ領域に直接書き込んでいます。無駄は無いのですが、new stringしたメモリ領域は本来不変なので別の状況では予期しない動作やクラッシュを招く可能性があるはずです。安全性を投げ捨てた割に、速度的にもメリットがでませんでした。

StringCreate

String.Createは.NET Core 2.1から追加された新しいメソッドです。こちらはstringを作成し、そのバッファの中身をコールバックで直に書き換えられるという最近の.NETの方向性を示すかのようなメソッドです。うまく使うと非常に効率的に文字列を生成できます。ただ、今回の場合はあまり刺さる使い方になっておらず、ラムダ式を書くのが面倒な割に他と大差ありません。

ラムダ式を使う分余分なヒープ確保があるかと思いましたが、その心配は無用のようです。

StackAllocSpan/StackAllocSpanToString

やはり早くて楽ちんなのがスタックメモリを確保するstackallocを使う方法です。スタックメモリはヒープと違い解放が非常に低コストです。さらにC# 7.2からunsafeを使うことなくSpanで受け取れるようになりました。

GetPinnableReferenceで参照を取得できるのも扱いやすいですね。new stringとToStringも比較してみましたが、どちらも処理は同じようです。メモリ確保が566Bytesなのは、ToStringした際にNULL文字も含まれてしまったためです。

StackAllocPointer

古来のC#から使えた方法です。unsafeを付ける必要があり、コンパイルオプションでも/unsafeが必要です。C#ではnew stringにchar*を受け取るオーバーロードがあるので、実は手軽に使用可能です。

まとめ

  • stackallocをchar*で直接使う方法がnull文字の分のヒープ確保がいらない分お得っぽい
  • メモリのブロックコピーはCPUに専用命令がある等で意外と低コスト
  • バッファサイズが巨大になる(=メモリコピーコストが増大する)場合にはString.Createも勝ち目が出てくるかも
  • StringBuilderは論外!(stackallocを差し置いて使う理由があれば教えて下さい)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む