20210907のC#に関する記事は6件です。

Gtk3アプリ イベントハンドラー自動生成ツール 改良

Gtk3アプリ イベントハンドラー自動生成ツール 改良 Release 改良点1 シグナル GtkWidgetやGtkContainerに対応 GtkWidgetやGtkContainerや未対応だった箇所もイベントハンドラーを生成できるようにしました。 改良点2 Riderのエクスプローラーの選択したgladeだけ書き出すようにしました。 フォルダを選択した場合その下にあるすべてのgaldeファイルを書き出します。 改良点3 Gladeアプリに備わっているインテリセンス機能を優先する GladeアプリにはOnを入力すると自動的にメソッド名が自動生成があります。そちらを優先することにしました。 本アプリにもあった自動でメソッド名を生成する機能を廃止しました。 改良点3 コマンドの記述を改良 Riderのツールに登録するコマンドを改良しました。 $ gladeGenerator -projectName $SolutionName$ -fileDir $FilePath$ -saveDir $SolutionDir$ 使用方法のおさらい gtkSharpのテンプレートをインストールする Gladeをインストールする RiderやVScodeにツールを登録する コマンドも登録します。 新規プロジェクトを開く GladeファイルをGUIパーツを追加する 仮 Buttonを追加した場合 IDをWindowとコントロールに記入する。メソッド名を記入する Onを押すとインテリセンスがきく ターミナルもしくはRiderの外部ツールから実行する イベント文が自動生成されたファイルが生成される 続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#におけるインターフェースと拡張メソッドを用いたバリエーション定義のガイドライン

はじめに C#のインターフェイスで様々なバリエーションを定義したい場合に拡張メソッドを用いてバリエーションの定義をすると利点が多いため、そのガイドラインを記します。 インターフェースと拡張メソッドを用いたバリエーション 公式の拡張メソッドのガイドライン Loggerでの例 Configurationでの例 インターフェースと拡張メソッドを用いたバリエーション インターフェースには、プリミティブなメソッドのみを定義し、そのバリエーションは拡張メソッドとして定義すると良い。 利点 インターフェース実装者は最低限のインターフェースのみ実装すれば良い。 単体テストでもバリエーション部分のメソッドのモック実装が不要となる。 さらにメソッドを増やす場合も、インターフェースが変わらないのでインターフェース実装をしている他ユーザへの影響がない。 フレームワーク、ライブラリ提供側は、インターフェースはプリミティブ化して、バリエーションを拡張メソッド化するアプローチを取ると良い。 公式の拡張メソッドのガイドライン 上記はマイクロソフトの拡張メソッドのガイドラインです。 どのような場合に拡張メソッドを利用すべきかが記されています。以下に例を記します。 拡張メソッドをむやみに定義することは避けてください。これは自分が所有していない型に対して特に当てはまります。 型のソース コードを所有している場合は、代わりに通常のインスタンス メソッドを使用することを検討してください。 所有していないときにメソッドを追加する場合は、細心の注意を払ってください。 拡張メソッドを自由に使用すると、それらのメソッドを使用するように設計されていなかった種類の API が乱雑になる可能性があります。 Loggerでの例 Microsof.Extensions.Loggingで同様のアプローチを取っています。 // インターフェイスでプリミティブなログのメソッドを定義する public interface ILogger { void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter); bool IsEnabled(LogLevel logLevel); IDisposable BeginScope<TState>(TState state); } // ログのバリエーションを拡張メソッドで定義する public static class LoggerExtensions { public static void LogDebug(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args) { logger.Log(LogLevel.Debug, eventId, exception, message, args); } ... public static void LogError(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args) { logger.Log(LogLevel.Error, eventId, exception, message, args); } ... } Configurationでの例 // インターフェイスでプリミティブなConfigurationのメソッドを定義する public interface IConfiguration { IConfigurationSection GetSection(string key); IEnumerable<IConfigurationSection> GetChildren(); IChangeToken GetReloadToken(); } // Configurationのバリエーションを拡張メソッドで定義する public static class ConfigurationExtensions { public static bool Exists(this IConfigurationSection section) { if (section == null) { return false; } return section.Value != null || section.GetChildren().Any(); } public static string GetConnectionString(this IConfiguration configuration, string name) { return configuration?.GetSection("ConnectionStrings")?[name]; } ... }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

拡張メソッドの利用アプローチ

拡張メソッドの利用アプローチ はじめに 拡張メソッドを利用したアプローチについて学ぶ機会があったため、学びを書き残しておきます。 インターフェースと拡張メソッドを用いたバリエーション 拡張メソッドのガイドライン Loggerでの例 Configurationでの例 インターフェースと拡張メソッドを用いたバリエーション インターフェースには、プリミティブなメソッドのみを定義し、そのバリエーションは拡張メソッドとして定義すると良い。 利点 インターフェース実装者は最低限のインターフェースのみ実装すれば良い。 単体テストでもバリエーション部分のメソッドのモック実装が不要となる。 さらにメソッドを増やす場合も、インターフェースが変わらないのでインターフェース実装をしている他ユーザへの影響がない。 フレームワーク、ライブラリ提供側は、インターフェースはプリミティブ化して、バリエーションを拡張メソッド化するアプローチを取ると良い。 拡張メソッドのガイドライン Loggerでの例 Microsof.Extensions.Loggingで同様のアプローチを取っています。 public interface ILogger { // プリミティブなログのメソッド void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter); bool IsEnabled(LogLevel logLevel); IDisposable BeginScope<TState>(TState state); } public static class LoggerExtensions { // ログのバリエーションメソッド public static void LogDebug(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args) { logger.Log(LogLevel.Debug, eventId, exception, message, args); } ... public static void LogError(this ILogger logger, EventId eventId, Exception? exception, string? message, params object?[] args) { logger.Log(LogLevel.Error, eventId, exception, message, args); } ... } Configurationでの例 public interface IConfiguration { // プリミティブなConfigurationのメソッド IConfigurationSection GetSection(string key); IEnumerable<IConfigurationSection> GetChildren(); IChangeToken GetReloadToken(); } public static class ConfigurationExtensions { // Configurationのバリエーションメソッド public static bool Exists(this IConfigurationSection section) { if (section == null) { return false; } return section.Value != null || section.GetChildren().Any(); } public static string GetConnectionString(this IConfiguration configuration, string name) { return configuration?.GetSection("ConnectionStrings")?[name]; } ... }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UniRxの使い方を間違い、フリーズを発生させてしまった

はじめに 初歩的なミスをしてしまったので、再び発生させないようにするためにも備忘録を残します。 制作しているアプリで一定時間メインスレッドが止まり表示が更新されないフリーズが特定条件で発生していました。 原因を調査するためにプロファイラーを見ていたところ、GarbageCollectionで大きな山が? GCが発生した箇所 プロファイラーの詳細を見ると、ObservableDestroyTrigger.OnDestroyで発生していました。 コードを調べてみると、IObservableのSubscribe時に呼び出すAddTo()が原因でした。 問題となったコード 実際のコードは載せられないので、近い挙動をするコードになります。 using System; using UniRx; using UnityEngine; public class Sample : MonoBehaviour { private Subject<Unit> _apiSubject = new Subject<Unit>(); private Subject<Unit> _subject = new Subject<Unit>(); void Start() { // ApiReceive時の想定 _apiSubject.StartWith(Unit.Default).Subscribe(_ => { UpdateSubject(); }).AddTo(this); // Apiの複数回投げたときの想定 for (int i = 0; i < 26000; i++) { _apiSubject.OnNext(Unit.Default); } } void UpdateSubject() { _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } void Update() { if (Input.GetKeyDown(KeyCode.D)) { Destroy(gameObject); } } } 実際のコードが、Apiであるデータのレスポンスが返ってきたときに対象の処理を再購読するというものでしたので、上記のようなコードになっています。 (実際のコードではこれほど大量にNextを発行していませんが、実際起った挙動を再現するためにこのようにしています) ちなみにこちらのコードでGameObjectがDestroyされたときに、2.52GBのGC Allocが働いています。 なぜAddToが必要か 購読とオブジェクトを紐付けないと、オブジェクトが破棄されても購読が残り処理が走ってしまいます。 今回のコードは同じクラス内で生成したSubjectに対して購読しているので、AddTo()がなくても問題ありませんが、 クラスを跨いだときに問題が出るのでAddToを呼び出す癖をつけておいた方が良いです。 実際の挙動 フリーズが分かり易いように、アニメーションしているキューブを表示しています。 Dキーを押した時に、GameObjectがDestroyされるときにフリーズが発生しています。 何が問題だったか こちらの処理に問題があります。 void UpdateSubject() { _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } 再び購読する際に、前回購読していたものを破棄していないのでどんどん購読が積まれて行ってしまいます。 正しいコード private IDisposable _disposable = null; void UpdateSubject() { _disposable?.Dispose(); _disposable = _subject.Subscribe(_ => { Debug.Log("DoSomething"); }).AddTo(this); } 再購読時、上記のように前回のものを破棄してから購読させます。 ?はnull条件演算子で、オブジェクトがnullの場合は関数が呼ばれません。 以下と同じ処理になります。 if (_disposable != null) { _disposable.Dispose(); } 最後に 普段、再購読するときは前回購読したものを破棄すると気をつけてはいましたが、今回はミスで正しくないコードになっていました。 ちょっとしたミスが分かりづらい不具合を生んでしまいます。 今回のミスのおかげで、GCが大量に走ってしまうパターンを発見することができました。 今回紹介した内容は初歩的なミスですが、これからはAddToに同じオブジェクトを大量に指定しないように気をつけなければいけませんね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

asset化しない生のファイル読み込み

var a = await Re.Get<T>(name);//T: Texture2D or string using UnityUtil; //... async void Start() { Re.basepath=Application.streamingAssetsPath; //.../Assets/StreamingAssets //quad var tex =await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<Renderer>().material = tex.ToMat(); //rawimage var tex = await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<RawImage>().texture = tex;// as Texture; gameObject.GetComponent<RectTransform>().sizeDelta=new Vector2(tex.width,tex.height); //image var tex = await Re.Get<Texture2D>("stand.png"); gameObject.GetComponent<Image>().sprite = tex.ToSprite(); //text var str= await Re.Get<string>("test.txt"); Debug.Log(str); Debug.Log("end"); } namespace UnityUtil { using System.Collections.Generic; using UnityEngine; using System.IO; using System.Threading.Tasks; public static class Re { /*usage using UnityUtil; Re.basepath=Application.streamingAssetsPath; var a =await Re.Get<string>("license.txt"); var b= await Re.Get<Texture2D>("zed1.jpg"); */ public static Dictionary<string, object> cash = new Dictionary<string, object>(); public static string basepath = ".\\"; public static async Task<T> Get<T>(string name) { name = name.Trim(); if (!cash.ContainsKey(name)) { var o = await Load(name); cash.Add(name, o); } // return (T)cash[name]; } static async Task<object> Load(string name) { var ret = new System.Object(); var path = Path.Combine(basepath, name); if (isText(name)) { var str = File.ReadAllText(path); ret = str; } else { var tex = new Texture2D(1, 1); var bytes = File.ReadAllBytes(path); tex.LoadImage(bytes); ret = tex; } return ret; // bool isText(string name) => name.IndexOf(".txt") != -1; } public static Material ToMat(this Texture2D tex,string shadername="Transparent/Diffuse"){ Material mat = new Material(Shader.Find(shadername)); mat.SetTexture("_MainTex", tex); return mat; } public static Sprite ToSprite(this Texture2D tex) { return Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), Vector2.zero); } } }//namespace
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IFormFileをUnitTestしたい

実体験を記事化しています。一人で情報システム部門の仕事をしている人向け、かな? 業務アプリを作っていると、CSVファイル(概ねSJISのファイル)をアップロードして、そのファイルをゴニョゴニョした後、結果を画面に表示する。 といった処理を作ることはよくあります。 このとき、コントローラ側のコードはこのようになっているはずです。 SomeController.cs public IActionResult SomeAction(IFormFile formFile) { using var reader = new StreamReader(formFile.OpenReadStream(), Encoding.GetEncoding("shift_jis")); ... ... return View(); } このコントローラの単体テストを作る際に色々と検索すると Mock<IFormFile> を使ったサンプルをよく見かけます。 で、そのサンプルをサクッとコピペしてテストを実行するとうまくいきませんでした。 そこで、IFormFile をMockしないで SomeControllerTests.cs [TestMethod] public void SomeActionTest() { var fileName = "TestData.csv"; var path = Path.Combine(TestContext.TestDeploymentDir, fileName); var contents = File.ReadAllText(path); var bytes = Encoding.GetEncoding("shift_jis").GetBytes(contents); var stream = new MemoryStream(bytes); var formFile = new FormFile(stream, 0, bytes.Length, fileName, fileName) { Headers = new HeaderDictionary(), ContentType = "text/csv" }; ... ... var controller = new SomeController(); var actionResult = controller.SomeAction(formFile); } このように FormFile を自分で new することで、ちゃんと単体テストができるようになりました。 Mockに拘って手数を伸ばすよりも、こうやってハードコーディング(ちょっと違う)してしまうのも一つの手段だと、私は思います。 むしろ、「ファイルアップロードして読み込む」という処理を共通化することを考えた方がいいと思います。 処理を共通化することで「A画面では動くんだけど、B画面では動かない」なんていうよくある不具合に出会う確率が下がります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む