- 投稿日:2020-04-05T21:45:37+09:00
ASP.NET Core Blazor WebAssembly で構成ファイルを扱う
ドキュメントを軽くあさってみたら Blazor WebAssembly で、既に構成ファイルの扱いまでサポートしているんですね。
ASP.NET Core Blazor hosting model configuration
ということで試してみました。
Blazor WebAssembly のプロジェクトを作成しておきます。
NuGet のパッケージの管理で Blazor まわりのライブラリが 3.2 の Preview 3 以降であることを確認します。
では、構成ファイルを置いてい置きましょう。Blazor WebAssembly では wwwroot の下に構成ファイルを置くみたいです。こんな感じで。
それぞれの中身はこのようにしてみました。
appsettings.json{ "message": "Hello from appsettings.json" }appsettings.Development.json{ "envMessage": "Hello from appsettings.Development.json" }appsettings.Production.json{ "envMessage": "Hello from appsettings.Production.json" }そして、Program.cs で以下のようにして IConfiguration から適当なクラスにマッピングして DI 出来るように下準備をしてやります。
Program.csusing System; using System.Collections.Generic; using System.Threading.Tasks; using System.Text; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; namespace BlazorApp3 { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("app"); builder.Services.AddBaseAddressHttpClient(); // IConfiguration から AppSettings クラスにして DI できるようにしておく builder.Services.AddSingleton(p => p.GetRequiredService<IConfiguration>().Get<AppSettings>()); await builder.Build().RunAsync(); } } // appsettings.json の値を入れるよう public class AppSettings { public string Message { get; set; } public string EnvMessage { get; set; } } }あとは、普通に DI するなりして使いましょう。こんな感じで
Index.razor@page "/" @inject AppSettings _appSettings <h1>Hello, world!</h1> Welcome to your new app. <p>@_appSettings.Message</p> <p>@_appSettings.EnvMessage</p>因みに、Program.cs で Configure メソッドで DI コンテナに登録して
IOptions<T>
で受けるのは出来ませんでした。Configure をするには、IConfiguration のインスタンスが必要で、それをしようとしたらbuilder.Build().Configuration
で取得しないといけないのですが、builder.Build()
は 2 回やるとappsettings.json
とかを読み込むための Stream を 2 回読むことになっているっぽくて、そこで例外が出てるように見えます。(深追いはしていないBlazor のアプリを実行してみると、ちゃんと思った通りのメッセージが出ます。開発者ツールで見てみると appsettings.json と appsettings.Development.json を、サーバーから取得していることがわかります。
Production の方は、何処かにデプロイされると読み込まれるらしいです。試しに、Azure ストレージアカウントの Blob の静的サイトのホスト機能を使って作ったサイトに置いてみました。発行をして発行先フォルダーの wwwroot フォルダーの下を全部 Blob の $web コンテナーにコピーします。
今度はちゃんと Hello from appsettings.Production.json というメッセージが出てますね。
まとめ
ちゃんと動くね。ということで、ドキュメントにも書かれていますが、このような仕組みなので URL に appsettings.json と打ち込むと生 JSON が手に入っちゃうので、間違っても秘密な情報は書かないように気をつけましょう。
- 投稿日:2020-04-05T21:36:18+09:00
.NetCore ではPrivateObjectが無いのでその代替案
はじめに
.NET Coreも3.* になったし、そろそろ.NET Frameworkで作ったプロジェクトも置き換えようと始めてみたのですが、単体テストにPrivateObjectがないことに気がつきました。
あれ? それじゃあプライベートメソッドのテストが出来ないじゃないですか? どういうことですか!?.NET Core と .NET Standard での単体テストのベスト プラクティス
https://docs.microsoft.com/ja-jp/dotnet/core/testing/unit-testing-best-practicesパブリック メソッドの単体テストを行うことでプライベート メソッドを検証する
ほとんどの場合、プライベート メソッドをテストする必要はありません。 プライベート メソッドは実装の詳細です。 プライベート メソッドは独立して存在することはない、と考えることができます。ご、ごめんなさい!!
確かにそうですね。プライベートメソッドのテストを書きながら「面倒だなぁ。でもやっておいた方がなんか安心するしなぁ」という感じで作ってました。あと単純にテストのパターン数が減らせるので。1
とはいえ、既に作っちゃったわけで、これからは心を入れ替えて Publicだけでやっていくんで、既存部分だけは何とかならないかなと思うわけです。
一時的な措置ということでリフレクションを使って代替案としたいと思ったのが始まりです。置き換え案(初期案)
初期に考えたものです。課題点があります。
説明はコメントとして書いておきました。PrivateObjectの代替クラス(初期案)using System; using System.Linq; using System.Reflection; /// <summary> /// .Net Coreで消えた PrivateObjectの代替クラス。 /// プライベートメソッドのテストに使用する /// </summary> public class PrivateObject { private readonly object _instance; private readonly Type _testTargetType; /// <summary> /// コンストラクタの引数が存在しないパターン /// </summary> /// <param name="testTargetClass">テスト対象のクラス</param> public PrivateObject(object testTargetClass) { _testTargetType = testTargetClass.GetType(); // テスト対象のタイプを取得 var c = _testTargetType.GetConstructor(Type.EmptyTypes); // コンストラクタ情報を取得して _instance = c.Invoke(null); // インスタンス化(コンストラクタの引数なし) SetAllPropertiesToInstance(testTargetClass); // プロパティ類を全部コピー } /// <summary> /// コンストラクタの引数が存在するパターンはこちら。 /// 引数は可変長になっているからいくつ引数があっても大丈夫。 /// </summary> /// <param name="testTargetClass"></param> /// <param name="constArg"></param> public PrivateObject(object testTargetClass, params object[] constArg) { _testTargetType = testTargetClass.GetType(); // テスト対象のタイプを取得 // コンストラクタ情の引数の型情報を取得する var ctors = _testTargetType.GetConstructors(); var ctor = ctors[0]; var t = ctor.GetParameters().Select(a => a.ParameterType).ToArray(); var c = _testTargetType.GetConstructor(t); // コンストラクタ情報を引数を含めて取得して _instance = c.Invoke(constArg); // 引数込みでインスタンス化 SetAllPropertiesToInstance(testTargetClass); // プロパティ類を全部コピー } /// <summary> /// インスタンスに全てのプロパティ情報をセットする /// </summary> /// <param name="testTargetClass"></param> private void SetAllPropertiesToInstance(object testTargetClass) { foreach (var t in _testTargetType.GetProperties()) { SetPropertyToInstance(testTargetClass, t.Name); } } /// <summary> /// インスタンスにプロパティ情報をセットする /// </summary> /// <param name="obj"></param> /// <param name="PropertyName"></param> private void SetPropertyToInstance(object obj, string PropertyName) { var prop = _testTargetType.GetProperty(PropertyName); // プロパティ名からプロパティ情報を取得する var value = prop.GetValue(obj); // プロパティ値を抜き出して、 prop.SetValue(_instance, value); // セットする } /// <summary> /// プライベートメソッドを実行する /// </summary> /// <param name="methodName"></param> /// <param name="arg"></param> /// <returns></returns> public object Invoke(string methodName, params object[] arg) { // メソッド名からメソッド情報を取得する。 var method = _testTargetType.GetMethod(methodName, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance); try { return method.Invoke(_instance, arg); // メソッドを実行する } catch (Exception e) { // エラーが発生したら「ここ」のエラー情報じゃなくて発生元のエラーを投げる throw e.InnerException; } } }使用方法(テスト対象のコンストラクタに引数がない場合)// Act var privObj = new PrivateObject(testTargetClass); var result = privObj.Invoke("PrivateMethod", arg1, arg2);使用方法(テスト対象のコンストラクタに引数ががある場合)// Act var privObj = new PrivateObject(testTargetClass, constArg1, constArg2); var result = privObj.Invoke("PrivateMethod", arg1, arg2);課題点
お気づきのように、テスト対象のコンストラクタに引数がない場合は良いのですが、ある場合はPrivateObjectのインスタンス化時に、その引数も渡してあげる必要がある状態となっています。
それを無くすためにはテストオブジェクト対象の内容をそのままコピーすることが出来ればいいわけで、原理的には出来るはずだと思うのですが(.NET Frameworkなら出来ていたわけで)、生憎と今の私の拙いリフレクション知識ではこれが精一杯です。これだけでも大分軽減できたのでまずは十分です。置き換え案2(課題点解決)
色々調べたところ、InvokeMemberを使えば解決できるということが分かりました。
PrivateObjectの代替クラス(上記の課題点を解決)using System; using System.Reflection; /// <summary> /// .Net Coreで消えた PrivateObjectの代替クラス。 /// プライベートメソッドのテストに使用する /// </summary> public class PrivateObject { private readonly object _obj; public PrivateObject(object obj) { _obj = obj; } public object Invoke(string methodName, params object[] args) { var type = _obj.GetType(); var bindingFlags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance; try { return type.InvokeMember(methodName, bindingFlags, null, _obj, args); } catch (Exception e) { throw e.InnerException; } } }シンプルで良し!
もちろん元々のを完全に再現しているわけではないので、色々と足りないところはありますが、まぁ十分かなと。
こういうメタプログラミングも学んでいかないといけないですね。参考
- C#リフレクションTIPS 55連発
- ConstructorInfoを使ってコンストラクタを呼び出し、インスタンスを作成する
- Name of the constructor arguments in c#
- Type.InvokeMemberを使ってメンバの呼び出しを行う
publicで3分岐、privateで4分岐があったとしたら、privateのテストもやれば3+4の足し算で済むけど、publicのみだと、3×4の乗算になるので。 ↩
- 投稿日:2020-04-05T21:36:18+09:00
NetCore ではPrivateObjectが無いのでその代替案
はじめに
.NET Coreも3.* になったし、そろそろ.NET Frameworkで作ったプロジェクトも置き換えようと始めてみたのですが、単体テストにPrivateObjectがないことに気がつきました。
あれ? それじゃあプライベートメソッドのテストが出来ないじゃないですか? どういうことですか!?.NET Core と .NET Standard での単体テストのベスト プラクティス
https://docs.microsoft.com/ja-jp/dotnet/core/testing/unit-testing-best-practicesパブリック メソッドの単体テストを行うことでプライベート メソッドを検証する
ほとんどの場合、プライベート メソッドをテストする必要はありません。 プライベート メソッドは実装の詳細です。 プライベート メソッドは独立して存在することはない、と考えることができます。ご、ごめんなさい!!
確かにそうですね。プライベートメソッドのテストを書きながら「面倒だなぁ。でもやっておいた方がなんか安心するしなぁ」という感じで作ってました。あと単純にテストのパターン数が減らせるので。1
とはいえ、既に作っちゃったわけで、これからは心を入れ替えて Publicだけでやっていくんで、既存部分だけは何とかならないかなと思うわけです。
一時的な措置ということでリフレクションを使って代替案としたいと思ったのが始まりです。置き換え案
説明はコメントとして書いておきました。
PrivateObjectの代替クラスusing System; using System.Linq; using System.Reflection; /// <summary> /// .Net Coreで消えた PrivateObjectの代替クラス。 /// プライベートメソッドのテストに使用する /// </summary> public class PrivateObject { private readonly object _instance; private readonly Type _testTargetType; /// <summary> /// コンストラクタの引数が存在しないパターン /// </summary> /// <param name="testTargetClass">テスト対象のクラス</param> public PrivateObject(object testTargetClass) { _testTargetType = testTargetClass.GetType(); // テスト対象のタイプを取得 var c = _testTargetType.GetConstructor(Type.EmptyTypes); // コンストラクタ情報を取得して _instance = c.Invoke(null); // インスタンス化(コンストラクタの引数なし) SetAllPropertiesToInstance(testTargetClass); // プロパティ類を全部コピー } /// <summary> /// コンストラクタの引数が存在するパターンはこちら。 /// 引数は可変長になっているからいくつ引数があっても大丈夫。 /// </summary> /// <param name="testTargetClass"></param> /// <param name="constArg"></param> public PrivateObject(object testTargetClass, params object[] constArg) { _testTargetType = testTargetClass.GetType(); // テスト対象のタイプを取得 // コンストラクタ情の引数の型情報を取得する var ctors = _testTargetType.GetConstructors(); var ctor = ctors[0]; var t = ctor.GetParameters().Select(a => a.ParameterType).ToArray(); var c = _testTargetType.GetConstructor(t); // コンストラクタ情報を引数を含めて取得して _instance = c.Invoke(constArg); // 引数込みでインスタンス化 SetAllPropertiesToInstance(testTargetClass); // プロパティ類を全部コピー } /// <summary> /// インスタンスに全てのプロパティ情報をセットする /// </summary> /// <param name="testTargetClass"></param> private void SetAllPropertiesToInstance(object testTargetClass) { foreach (var t in _testTargetType.GetProperties()) { SetPropertyToInstance(testTargetClass, t.Name); } } /// <summary> /// インスタンスにプロパティ情報をセットする /// </summary> /// <param name="obj"></param> /// <param name="PropertyName"></param> private void SetPropertyToInstance(object obj, string PropertyName) { var prop = _testTargetType.GetProperty(PropertyName); // プロパティ名からプロパティ情報を取得する var value = prop.GetValue(obj); // プロパティ値を抜き出して、 prop.SetValue(_instance, value); // セットする } /// <summary> /// プライベートメソッドを実行する /// </summary> /// <param name="methodName"></param> /// <param name="arg"></param> /// <returns></returns> public object Invoke(string methodName, params object[] arg) { // メソッド名からメソッド情報を取得する。 var method = _testTargetType.GetMethod(methodName, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance); try { return method.Invoke(_instance, arg); // メソッドを実行する } catch (Exception e) { // エラーが発生したら「ここ」のエラー情報じゃなくて発生元のエラーを投げる throw e.InnerException; } } }使用方法(テスト対象のコンストラクタに引数がない場合)// Act var privObj = new PrivateObject(testTargetClass); var result = privObj.Invoke("PrivateMethod", arg1, arg2);使用方法(テスト対象のコンストラクタに引数ががある場合)// Act var privObj = new PrivateObject(testTargetClass, constArg1, constArg2); var result = privObj.Invoke("PrivateMethod", arg1, arg2);課題点
お気づきのように、テスト対象のコンストラクタに引数がない場合は良いのですが、ある場合はPrivateObjectのインスタンス化時に、その引数も渡してあげる必要がある状態となっています。
それを無くすためにはテストオブジェクト対象の内容をそのままコピーすることが出来ればいいわけで、原理的には出来るはずだと思うのですが(.NET Frameworkなら出来ていたわけで)、生憎と今の私の拙いリフレクション知識ではこれが精一杯です。これだけでも大分軽減できたのでまずは十分です。良い方法がありましたら是非ともコメントよろしくお願い致します!!
参考
- C#リフレクションTIPS 55連発
- ConstructorInfoを使ってコンストラクタを呼び出し、インスタンスを作成する
- Name of the constructor arguments in c#
publicで3分岐、privateで4分岐があったとしたら、privateのテストもやれば3+4の足し算で済むけど、publicのみだと、3×4の乗算になるので。 ↩
- 投稿日:2020-04-05T19:38:10+09:00
ASP .Net Core3.1 起動プロファイルと設定ファイルについて
はじめに
「.Net Core 3.1について調べてみよう」その2です。
Visual studio 2019でASP .Net Coreのプロジェクトを作成するとlaunchSettings.jsonとappsettings.jsonがプロジェクトに追加されています。これらのファイルの使い方が気になったので調べてみました。launchSettings.json
Visual Studio等の開発環境で、アプリケーションを起動する際の環境とパラメータを指定するために使用されます。
launchSettings.json{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:5002", "sslPort": 44314 } }, "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "weatherforecast", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Weatherforecast": { "commandName": "Project", "launchBrowser": true, "launchUrl": "weatherforecast", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5001" }, "newProfile1": { "commandName": "Executable", "executablePath": "C:\\weatherforecast\\bin\\Debug\\netcoreapp3.1\\weatherforecast.exe" } }commnadNameによって以下のように分けられます。
commandName 起動方法 IISExpress IISExpress IIS IIS Project 実行ファイル Executable 指定された実行ファイル dotnet runで実行した場合は、Projectの設定で実行され、Projectの設定が複数ある場合、ファイルの最初に定義されているものが実行されます。
appsettings.json
.Net Frameworkのアプリケーション設定ファイル(exe.config)の代わりとなるファイルです。
appsettings.json{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }このファイルは実行時のASPNETCORE_ENVIRONMENT環境変数によって切り替えることが可能です。
appsettings.ASPNETCORE_ENVIRONMENT.jsonファイルがあれば、appsettings.jsonの代わりに使用されます。
例えば、ASPNETCORE_ENVIRONMENT=Developmentの場合、
appsettings.Development.jsonファイルがあれば、このファイルが使用されます。appsettings.json{ "test":{ "abc":1 } }追加した"test"をコードから読み込みます。
WeatherForecast.csprivate readonly IConfiguration configuration_; public WeatherForecastController(IConfiguration configuration) { configuration_ = configuration; } [HttpGet] public IEnumerable<WeatherForecast> Get() { //値を読み込む var v = configuration_.GetValue<int>("test:abc"); }また、直接アクセスせずにクラスにアサインすることもできます。
Test.cspublic class Test { public int abc { get; set; } }Startup.cspublic void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.Configure<Test>(Configuration.GetSection("test")); }WeatherForecast.csprivate readonly IOptions<Test> testConfiguration_; public WeatherForecastController(IOptions<Test> testConfiguration) { testConfiguration_ = testConfiguration; } [HttpGet] public IEnumerable<WeatherForecast> Get() { //値を読み込む var v = testConfiguration_.Value.abc; }最後に
launchSettings.jsonは、publish(発行)には含まれないので開発専用の設定ですね。
Kestrelを使用する場合、運用時の設定は、appsettings.Production.jsonに行い、Startup.csでこの設定をKestrelServerOptionsクラスにアサインすればいいようです。
この設定は、json:launchSettings.jsonのapplicationUrlよりも優先されます。そのため、appsettings.jsonに定義してしまうとapplicationUrlの設定を上書きしてしまいます。appsettings.Production.json{ "Kestrel": { "Endpoints": { "Http": { "Url": "http://localhost:8080" } } } }Startup.cspublic void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.Configure<KestrelServerOptions>(Configuration.GetSection("Kestrel")); }appsettings.jsonの設定を読み取る際に、IConfigrationの
GetValue<type>("key")
を使用すると、そのたびにファイルアクセスが発生するという記事を見かけたので調べてみましたが、アプリケーション起動時に1度だけ読み込んこまれてメモリ上に保存されていました。
- 投稿日:2020-04-05T18:52:21+09:00
C# NtQuerySystemInformation関数でSystemProcessInformationを取得するコード
C# (.NET Core 3.1)で
NtQuerySystemInformation
関数を用いてSystemProcessInformation
の情報を取得するコードの覚書です。動作確認環境はMicrosoft Visual Studio Community 2019 Version 16.5.2です。途中で以下の操作も行っています。
DllImport
による遅延バインディングMarshalAs(UnmanagedType.ByValArray, SizeConst = ...)
Marshal.PtrToStructure
による固定長配列を含む構造体の扱いMarshal.PtrToStringUni
によるポインタから文字列の作成System.Diagnostics.DebuggerDisplay
によるデバッガーへの情報表示GCHandle
とMarshal.PtrToStructure
によるCスタイル可変長配列の読み込みProgram.csusing System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; namespace ConsoleApp1 { class Program { private static class NativeMethods { [DllImport("ntdll.dll", SetLastError = false, ExactSpelling = true)] public static extern int NtQuerySystemInformation( int SystemInformationClass, byte[] SystemInformation, uint SystemInformationLength, out uint ReturnLength); } private enum SYSTEM_INFORMATION_CLASS : int { // SystemBasicInformation = 0, // SystemPerformanceInformation = 2, // SystemTimeOfDayInformation = 3, SystemProcessInformation = 5, // SystemProcessorPerformanceInformation = 8, // SystemInterruptInformation = 23, // SystemExceptionInformation = 33, // SystemRegistryQuotaInformation = 37, // SystemLookasideInformation = 45, // SystemCodeIntegrityInformation = 103, // SystemPolicyInformation = 134, } [StructLayout(LayoutKind.Sequential)] private struct UNICODE_STRING { public ushort Length; public ushort MaximumLength; public IntPtr Buffer; } [StructLayout(LayoutKind.Sequential)] private struct SYSTEM_PROCESS_INFORMATION { public uint NextEntryOffset; public uint NumberOfThreads; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)] public byte[] Reserved1; public UNICODE_STRING ImageName; public int BasePriority; public IntPtr UniqueProcessId; public IntPtr Reserved2; public uint HandleCount; public uint SessionId; public IntPtr Reserved3; public UIntPtr PeakVirtualSize; public UIntPtr VirtualSize; public uint Reserved4; public UIntPtr PeakWorkingSetSize; public UIntPtr WorkingSetSize; public IntPtr Reserved5; public UIntPtr QuotaPagedPoolUsage; public IntPtr Reserved6; public UIntPtr QuotaNonPagedPoolUsage; public UIntPtr PagefileUsage; public UIntPtr PeakPagefileUsage; public UIntPtr PrivatePageCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public long[] Reserved7; } /// <summary> /// SYSTEM_PROCESS_INFORMATION構造体のデータを保持するクラスです。 /// </summary> [DebuggerDisplay("{ImageName} ({UniqueProcessId})")] private class SystemProcessInformation { public readonly uint NumberOfThreads; public readonly string ImageName; public readonly int BasePriority; public readonly IntPtr UniqueProcessId; public readonly uint HandleCount; public readonly uint SessionId; public readonly UIntPtr PeakVirtualSize; public readonly UIntPtr VirtualSize; public readonly UIntPtr PeakWorkingSetSize; public readonly UIntPtr WorkingSetSize; public readonly UIntPtr QuotaPagedPoolUsage; public readonly UIntPtr QuotaNonPagedPoolUsage; public readonly UIntPtr PagefileUsage; public readonly UIntPtr PeakPagefileUsage; public readonly UIntPtr PrivatePageCount; public SystemProcessInformation() { } public SystemProcessInformation(ref SYSTEM_PROCESS_INFORMATION info) { NumberOfThreads = info.NumberOfThreads; ImageName = Marshal.PtrToStringUni(info.ImageName.Buffer); BasePriority = info.BasePriority; UniqueProcessId = info.UniqueProcessId; HandleCount = info.HandleCount; SessionId = info.SessionId; PeakVirtualSize = info.PeakVirtualSize; VirtualSize = info.VirtualSize; PeakWorkingSetSize = info.PeakWorkingSetSize; WorkingSetSize = info.WorkingSetSize; QuotaPagedPoolUsage = info.QuotaPagedPoolUsage; QuotaNonPagedPoolUsage = info.QuotaNonPagedPoolUsage; PagefileUsage = info.PagefileUsage; PeakPagefileUsage = info.PeakPagefileUsage; PrivatePageCount = info.PrivatePageCount; } } static void Main() { const int STATUS_INFO_LENGTH_MISMATCH = unchecked((int)0xC0000004); // SystemProcessInformationのすべての情報を取得します。 var buffer = Array.Empty<byte>(); for (; ; ) { var status = NativeMethods.NtQuerySystemInformation( (int)SYSTEM_INFORMATION_CLASS.SystemProcessInformation, buffer, (uint)buffer.LongLength, out var bufferLength); if (status != STATUS_INFO_LENGTH_MISMATCH) { if (status == 0) break; throw new NTStatusException(status); } Array.Resize(ref buffer, (int)bufferLength); } // SystemProcessInformationの情報を解析します。 var infos = new List<SystemProcessInformation>(); var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { var address = bufferHandle.AddrOfPinnedObject(); for (; ; ) { // GCHandleの解放後はSYSTEM_PROCESS_INFORMATIONのImageNameの値が無効となるため、 // 文字列をメモリから変数に保持するクラスへ変換します。 var info = Marshal.PtrToStructure<SYSTEM_PROCESS_INFORMATION>(address); infos.Add(new SystemProcessInformation(ref info)); if (info.NextEntryOffset == 0) break; address = IntPtr.Add(address, (int)info.NextEntryOffset); } } finally { bufferHandle.Free(); } // TODO: ここでSystemProcessInformationの内容を処理します。 foreach (var info in infos) { Console.WriteLine($"{info.ImageName}, {info.UniqueProcessId}"); } } } }NTStatusUtility.csusing System; using System.Runtime.InteropServices; namespace ConsoleApp1 { public static partial class NTStatusUtility { public static bool IsSuccess(int status) => status >= 0; public static bool IsInformation(int status) => (status >> 30) == 1; public static bool IsWarning(int status) => (status >> 30) == 2; public static bool IsError(int status) => (status >> 30) == 3; public static int HResultFromNTStatus(int status) { const int FACILITY_NT_BIT = 0x10000000; return status | FACILITY_NT_BIT; } } public sealed class NTStatusException : COMException { public NTStatusException() : base() { status = 0; } public NTStatusException(int status) : base(null, NTStatusUtility.HResultFromNTStatus(status)) { status = status; } public NTStatusException(string message, int status) : base(message, NTStatusUtility.HResultFromNTStatus(status)) { status = status; } public NTStatusException(string message, Exception inner) : base(message, inner) { status = 0; } public NTStatusException(string message) : base(message) { status = 0; } private int status; public int Status => status; } }
- 投稿日:2020-04-05T18:32:05+09:00
今更ながらWPFに置き換えてみる(10)
ボケ問題再び
セッティング項目を展開表示する際の小さな三角形。
元のForms版では 16×16ピクセルのBMPを2つ作ってON/OFFでスイッチしてpictureBoxに表示してました。画面解像度比が1の場合は画面ピクセルが画像の画素と一致するので、当然ボケない。今回解像度に応じてコントロールの物理ピクセルサイズが自動で変わるため、元画像が16ピクセルの固定サイズで作成してあると拡大時にどうしてもぼけて表示されます(以前のnotifyIconの時と同じ問題)。
じゃ、どうすればいいの?ってことになりますが思いつくのは論理サイズ16×16ピクセルのcanvasを作成してそこにDrawするくらいしか思いつきません。試しに実装してIMAGEとCANVASを画面上で並べてみると
左がBITMAPをimagesource変換してImageに張り付けたもの。右がCanvasを作成してPolygonで描画したもの。パっと見わからないので拡大。はい当然20*20ピクセルになってますね。
ほかの個所でも同じことをするのでcanvasと状態を引数にして分離
private void DrawArrowBox(Canvas aCanvas , int sw) { aCanvas.Children.Clear(); //外枠 Rectangle myRectangle = new Rectangle(); SolidColorBrush mySolidColorBrush = new SolidColorBrush(); mySolidColorBrush.Color = Color.FromArgb(255, 150, 150, 150); myRectangle.Fill = mySolidColorBrush; myRectangle.StrokeThickness = 1 / 1.25; //ここは解像度に変更 myRectangle.Stroke = Brushes.White; myRectangle.Width = aCanvas.Width; myRectangle.Height = aCanvas.Height; aCanvas.Children.Add(myRectangle); //三角形 Polygon myPolygon = new Polygon(); SolidColorBrush myPolyColorBrush = new SolidColorBrush(); if (sw == 1) { System.Windows.Point Point1 = new System.Windows.Point(aCanvas.Width * 2 / 10, aCanvas.Height * 1 / 3); System.Windows.Point Point2 = new System.Windows.Point(aCanvas.Width * 8 / 10, aCanvas.Height * 1 / 3); System.Windows.Point Point3 = new System.Windows.Point(aCanvas.Width * 1 / 2, aCanvas.Height * 2 / 3); System.Windows.Point Point4 = new System.Windows.Point(aCanvas.Width * 2 / 10, aCanvas.Height * 1 / 3); PointCollection polygonPoints = new PointCollection(); polygonPoints.Add(Point1); polygonPoints.Add(Point2); polygonPoints.Add(Point3); polygonPoints.Add(Point4); myPolygon.Points = polygonPoints; myPolyColorBrush.Color = Color.FromArgb(255, 150, 150, 150); } else { System.Windows.Point Point1 = new System.Windows.Point(CanvasSettings.Width * 1 / 3, CanvasSettings.Height - CanvasSettings.Height * 8 / 10); System.Windows.Point Point2 = new System.Windows.Point(CanvasSettings.Width * 1 / 3, CanvasSettings.Height - CanvasSettings.Height * 2 / 10); System.Windows.Point Point3 = new System.Windows.Point(CanvasSettings.Width - CanvasSettings.Width * 1 / 3, CanvasSettings.Height * 1 / 2); System.Windows.Point Point4 = new System.Windows.Point(CanvasSettings.Width * 1 / 3, CanvasSettings.Height - CanvasSettings.Height * 8 / 10); PointCollection polygonPoints = new PointCollection(); polygonPoints.Add(Point1); polygonPoints.Add(Point2); polygonPoints.Add(Point3); polygonPoints.Add(Point4); myPolygon.Points = polygonPoints; myPolyColorBrush.Color = Color.FromArgb(255, 255, 255, 255); } myPolygon.Stroke = Brushes.White; myPolygon.StrokeThickness = 1 / 1.25; //ここは解像度に変更 myPolygon.Fill = myPolyColorBrush; aCanvas.Children.Add(myPolygon); }当初元のCanvasを1回描画して、on/off切り替えの際はrotatetransformさせればいいかな、と思ってましたが、回転の中心点の指定方法がいまいちわからず。
結局poly内のヌキを変えるんで再描画してしまえと、記述としてはしつこいものになっています。設定画面切り替え
以前悩んでいた設定部の表示非表示ですが、StackPanelを構成の上位に配置して、その下にそれぞれのコントロールグループでGridを並べ、visibilityで表示非表示を切り替え。StackPanelにしておくことで中間のGridをCollapsedにした場合、流し込まれて下のGridが上がってくれるので、面倒な位置操作は不要。
メインのWindowのHeightに対象のGridのHeightを加減することで希望の表現ができました。
- 投稿日:2020-04-05T17:20:14+09:00
ReactivePropertyをMessagePackとJSONにシリアライズ/デシリアライズする
概要
ReactivePropertyをMessagePackCSharpでMessagePack形式とJSON形式にシリアライズ・デシリアライズする方法を紹介します。
ReactivePropertyはViewModel層で使用されることが多いライブラリですが、Model層に使用しても問題ありません。
アプリケーションの状態をファイルに保存したいときなどに、Modelのクラスをまるごと保存できると便利です。
しかしReactivePropertyは標準ではそのまま保存できません。
そこで、今回はModel層にReactivePropertyがあり、それをシリアライズ/デシリアライズする方法を紹介します。解説
シリアライズ方法を説明する前に、使用するライブラリの紹介を簡単にしておきます。
ReactiveProperty
ReactivePropertyはWPFやUWPでの、MVVM + リアクティブプログラミングの組み合わせを快適にするためのライブラリです。
詳しくは以下を参照ください。
https://blog.okazuki.jp/entry/2015/12/05/221154MessagePack
MessagePackは、効率の良いバイナリ形式のオブジェクト・シリアライズ フォーマットです。JSONの置き換えとして使うことができ、様々なプログラミング言語をまたいでデータを交換することが可能です。しかも、JSONよりも速くてコンパクトです。
詳しくは以下を参照ください。
https://msgpack.org/ja.htmlMessagePack-CSharp
上記MessagePackをC#で扱うためのライブラリです。
MessagePackだけではなく、JSONにも対応しています。
同じ作者(nueueccさん)のUtf8Jsonというライブラリもありますが、ReactivePropertyを使うのであれば、対応した拡張パッケージがあるぶん、こちらの方が便利です。詳しくは以下を参照ください。
http://neue.cc/2017/03/13_550.htmlシリアライズ/デシリアライズ方法
nugetで以下のライブラリを取得する。
- ReactiveProperty
- MessagePack
- MessagePack.ReactiveProperty
- System.Reactive.Compatibility
ReactivePropertyのResolverを作成して、登録します。
デフォルトに登録せず、シリアライズ/デシリアライズ時にオプションとして指定することもできます。var resolver = MessagePack.Resolvers.CompositeResolver.Create( ReactivePropertyResolver.Instance, MessagePack.Resolvers.ContractlessStandardResolver.Instance, MessagePack.Resolvers.StandardResolver.Instance ); MessagePackSerializer.DefaultOptions = MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver);ReactivePropertyを作成して、シリアライズします。
var rpText = new ReactivePropertySlim<string>("ABC"); //JSON形式にシリアライズ string jsonRpText = MessagePack.MessagePackSerializer.SerializeToJson(rpText); Console.WriteLine($"JSON:{jsonRpText}"); //JSON:[3,"ABC"] //MessagePack形式にシリアライズ byte[] mPackRpText = MessagePack.MessagePackSerializer.Serialize(rpText); Console.WriteLine($"MessagePack:{String.Join(" ", mPackRpText.Select(x => x.ToString("X2")))}"); //MessagePack:92 03 A3 41 42 43なお、シリアライズ結果内の"3"は
ReactivePropertyMode
です。
イベントハンドラなどはシリアライズのしようがありませんので、含まれていません。デシリアライズではJSON形式は一度MessagePack形式を経由してからデシリアライズします。
byte[] bytesRpText = MessagePack.MessagePackSerializer.ConvertFromJson(jsonRpText); ReactivePropertySlim<string> desiRpText = MessagePack.MessagePackSerializer.Deserialize<ReactivePropertySlim<string>>(bytesRpText); Console.WriteLine($"desiRpText = {desiRpText}"); //desiRpText = ABCなお、ReactivePropertyだけでなく、ReactivePropertySlimやIReactivePropertyでもシリアライズできます。ReadOnlyReactivePropertyはできません。
もちろんReactiveProperty単体ではなく、これを複数内包したクラスごとシリアライズすることもできます。注意点
{get;}
だとシリアライズできないReactivePropertyを使用する場合、
.Value
は変更されますが、ReactiveProperty自体を変更することはまずないので、プロパティのアクセッサは{get;}
で十分なことが多いです。
しかし、シリアライズ/デシリアライズする場合は{ get; set; }
にする必要があります。競合エラー
ReactivePropertyのVer5以降とMessagePack.ReactivePropertyを導入していると、
System.Reactiveのバージョン間の整合性の問題で、以下のエラーが発生します。エラー CS0121 次のメソッドまたはプロパティ間で呼び出しが不適切です: 'System.Reactive.Linq.Observable.Select<TSource, TResult>(System.IObservable<TSource>, System.Func<TSource, TResult>)' と 'System.Reactive.Linq.Observable.Select<TSource, TResult>(System.IObservable<TSource>, System.Func<TSource, TResult>)'System.Reactive.Compatibilityをnugetで導入すると解決します。
デモアプリ
クラスをまるごとシリアライズ/デシリアライズできるデモアプリを作成してみました。
動作画面
姓と名の入力欄があり、変更するとフルネームが代わります。
[Serialize]ボタンを押下すると、下にシリアライズされたMessagePack(の16進数表示)とJSONの結果が表示されます。
下のJSONの結果を一部書き換えて("Anakin"?"Luke")から、[Desirialize]ボタンを押下すると、上のReactivePropertyの結果が変わります。
デモアプリコード
全体はGithubにおいておきます。
https://github.com/soi013/ReactivePropertySerializeDemoシリアライズ対象となるクラスは以下です。
public class RpNames { public ReactiveProperty<string> NameRp { get; set; } = new ReactiveProperty<string>("Anakin"); //ReactivePropertyでもReactivePropertySlimでもできる。 public ReactivePropertySlim<string> NameRps { get; set; } = new ReactivePropertySlim<string>("Skywalker"); //ReadOnlyはSerializeできない。 [IgnoreMember] public ReadOnlyReactivePropertySlim<string> NameRorps { get; set; } public RpNames() { //姓と名の変更を購読して、フルネームにする NameRorps = Observable .CombineLatest(NameRp, NameRps, (x, y) => $"{x}={y}") .ToReadOnlyReactivePropertySlim(); } }シリアライズを行うMainWindowViewModelが以下です。
public class MainWindowViewModel : INotifyPropertyChanged { //メモリリークを防ぐためのダミー実装 public event PropertyChangedEventHandler PropertyChanged; public RpNames Names { get; } = new RpNames(); public ReactiveProperty<string> MessagePackSerializedNames { get; } = new ReactiveProperty<string>(); public ReactiveProperty<string> JsonSerializedNames { get; } = new ReactiveProperty<string>(); public ReactiveCommand SerializeCommand { get; } public ReactiveCommand DesirializeCommand { get; } public MainWindowViewModel() { SerializeCommand = Names.NameRorps .Select(x => x?.Length >= 3) .ToReactiveCommand() .WithSubscribe(() => Serialize()); DesirializeCommand = JsonSerializedNames .Select(x => x?.Length > 5) .ToReactiveCommand() .WithSubscribe(() => Desirialize()); //ReactiveProperty用を含んだResolverのセットをデフォルトに設定しておく var resolver = MessagePack.Resolvers.CompositeResolver.Create( ReactivePropertyResolver.Instance, MessagePack.Resolvers.ContractlessStandardResolver.Instance, MessagePack.Resolvers.StandardResolver.Instance ); MessagePackSerializer.DefaultOptions = MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); } private void Serialize() { var messagePackNames = MessagePackSerializer.Serialize(Names); this.MessagePackSerializedNames.Value = String.Join(" ", messagePackNames.Select(x => $"{x:X2}")); this.JsonSerializedNames.Value = MessagePackSerializer.SerializeToJson(Names); } private void Desirialize() { //JSON側からデシリアライズ var mPack = MessagePack.MessagePackSerializer.ConvertFromJson(JsonSerializedNames.Value); var deserializedRPNames = MessagePackSerializer.Deserialize<RpNames>(mPack); this.Names.NameRp.Value = deserializedRPNames.NameRp.Value; this.Names.NameRps.Value = deserializedRPNames.NameRps.Value; } }デモではViewModel内のクラスをシリアライズして、再び表示していますが、通常はModel層のクラスをファイルなりデータベースなりに保存する形になると思います。
表示するViewは以下です。
MainWindow.xaml<Window x:Class="ReactivePropertySerializeDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ReactivePropertySerializeDemo" Title="ReactiveProperty Serialize Demo" Width="800" Height="450" TextElement.FontSize="24"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <StackPanel> <TextBox Margin="5" Text="{Binding Names.NameRp.Value}" /> <TextBox Margin="5" Text="{Binding Names.NameRps.Value}" /> <TextBlock Margin="5" Text="{Binding Names.NameRorps.Value}" /> <StackPanel Orientation="Horizontal"> <Button Margin="10" Command="{Binding SerializeCommand}" Content="Serialize" /> <Button Margin="10" Command="{Binding DesirializeCommand}" Content="Desirialize" /> </StackPanel> <TextBlock Text="MessagePack Serialized" /> <TextBox Margin="5,0,5,10" IsReadOnly="True" Text="{Binding MessagePackSerializedNames.Value}" TextWrapping="Wrap" /> <TextBlock Text="Json Serialized" /> <TextBox Margin="5,0,5,10" Text="{Binding JsonSerializedNames.Value}" TextWrapping="Wrap" /> </StackPanel> </Window>環境
VisualStudio2019
.NET Core 3.1
C#8
ReactiveProperty 6.2.0
MessagePack 2.1.90
MessagePack.ReactiveProperty 2.1.90
System.Reactive.Compatibility 4.4.1謝辞
困っていたところを@okazuki さんに助けていただきました。
https://twitter.com/okazuki/status/1246384566756986881
ありがとうございます。
- 投稿日:2020-04-05T17:02:05+09:00
【ASP.NET】IIS Application Initializationによる初回アクセスの高速化
はじめに
ASP.NETでWeb APIを使用したアプリケーションがあり、4秒以内で結果を戻さないと呼び出し先でタイムアウトエラーになる処理となっています。
最初にアクセスユーザーが必ずエラーの犠牲になっていて、それ以降の人はエラーにならずに処理が行われます。
これを改善したい。Application Initializationの有効化
ASP.NETは、初回アクセスが遅いことで有名です、これは初回のページの呼び出し時にアプリケーションの初期処理が実施されるからです。
Microsoftは、起動時にIISがアプリケーションを呼び出すことで、最初のユーザーがアクセスした時の体感速度を改善する仕組み「Application Initialization Module」を用意しました。
Application Initialization Moduleは、IIS7.5では外部モジュールとして提供されていたが、IIS8以降ではIIS標準機能となっています。ただしデフォルトでは有効化されていない機能なので、Application Initialization を有効化する必要があります。Windows Server 2016 ではサーバーマネージャを起動して「サーバーの役割と追加」から「Application Initialization」にチェックを付けてインストールします。
IIS標準機能なので最初から有効化されていると勘違いしていて、後述する設定をしたのに何も変わらないと思ったら有効化が必要でした。
設定
アプリケーションプール
IISで対象のアプリケーションプールの詳細設定ダイアログを開いて設定を行います。
[開始モード]を[OnDemand]から[AlwaysRunning] に変更します。
「AlwaysRunning」にすることでアプリケーションプールを常に起動した状態にすることができます。
下記2点の設定は運用に合わせて設定してください。
参照:アプリプールのリサイクル後にasp.net mvc webappをウォームアップするにはどうすればよいですか?
- アイドルタイムアウトを無効にする
- 定期的なリサイクルを無効にする
Webサイト
IISでサイトの対象のWebサイトの詳細設定ダイアログを開いて設定を行います。
[有効かされたプリロード]を[True]に変更します。
初期ページ
初期処理時にどのファイルを呼び出すかを設定します。
Webアプリケーションの Web.config を編集します。system.webServer に次のようなタグを追加します。web.config<system.webServer> ... <applicationInitialization doAppInitAfterRestart="false" skipManagedModules="false" remapManagedRequestsTo="Loading.html" /> <add initializationPage="/Initializer.aspx" /> </applicationInitialization> </system.webServer>以下は、Application Initialization のGoogle翻訳結果
doAppInitAfterRestart
アプリケーションの再起動が発生するたびに初期化プロセスが自動的に開始されることを指定します。これは初期化プロセスは、アプリケーションプールの再起動後に開始されることを指定するアプリケーションエレメントでpreLoadEnabled属性とは異なることに注意してください。デフォルトはfalseskipManagedModules
初期化中にマネージモジュールをロードするかどうか。デフォルトはfalseremapManagedRequestsTo
アプリケーションの初期化中に要求を再マッピングするページを指定します。(※ロード中に表示する静的ページを指定)initializationPage
アプリケーションの再起動時に初期化されるアプリケーションを指定します。(複数指定可)詳しい使い方は下記サイトを参考にして下さい
IIS Application InitializationでASP.NETアプリの起動を高速化(ウォームアップ)
Use IIS Application Initialization for keeping ASP.NET Apps alive確認
IISリセットして、対象のWebサイトにアクセスして速くなっていればいいです。
これまでと速度差があまり感じなく本当に動作しているのか分かりにくい場合があります。initializationPageを設定したにも関わらず、IISログに出力されていないので心配になります。
これは、IIS applicationInitializationリクエストは内部で行われるため、IISのログには出力されないのです。確認するには初期ページでリクエストのユーザーエージェントが「IIS Application Initialization Preload」となっていれば動作していることになります。
参照:Azure Web App Application InitializationInitializer.aspxnamespace SampleWebApp { public partial class Initializer : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { // ログ・ファイルへの出力ストリームを生成 StreamWriter sw = new StreamWriter( Server.MapPath("~/init.log"), true, Encoding.GetEncoding("Shift_JIS")); // 日付、リクエストのユーザーエージェントを出力 StringBuilder sb = new StringBuilder(); sb.Append(DateTime.Now.ToString()); sb.Append("\t"); sb.Append(Request.UserAgent); sw.WriteLine(sb.ToString()); sw.Close(); } } }web.config<system.webServer> ... <applicationInitialization /> <add initializationPage="/Initializer.aspx" /> </applicationInitialization> </system.webServer>IISResetする度に、initializationPage が呼ばれていることが確認できる。
init.log2020/04/05 16:51:00 IIS Application Initialization Warmup最後に
これにより4秒以内でおさまるようになりました。ただ通常より少し遅いかなって時がありますが、それでも1秒か2秒程度なので問題ありません。
今回の場合、Web APIなので初回アクセスはWeb APIのページを指定しています。
applicationInitializationの設定の「doAppInitAfterRestart」と「skipManagedModules」が説明が分かりにくいですね。
「doAppInitAfterRestart=”true”」にするとエラーになったので、これは「false」にします。デフォルトは「false」です。
参照したサイトだと「skipManagedModules=”true”」となっていることがありますが、今回は「false」でも問題ありませんでした。デフォルトは「false」です。
デフォルト設定の場合、記述する必要はないのでシンプルに「initializationPage」のみにしています。web.config<system.webServer> ... <applicationInitialization /> <add initializationPage="/api/S0100/S0102" /> </applicationInitialization> </system.webServer>参照
- 投稿日:2020-04-05T17:01:57+09:00
Unityのスクリプトのあれこれ
概要
Unityのスクリプトで使う関数を毎回調べてしまうため、よく使うものをまとめておく。
(随時追加予定)C#関数
配列内をランダムに取得する
string[] words = new string[3] {"a","b","c"}; Debug.Log(words[Random.Range(0, words.Length)]); // b(出力はランダムに変化)ゲームオブジェクトの設定
Prefabをゲームオブジェクトに設定する
あらかじめ、Assetsフォルダ内に「Resources」フォルダを作成し、その中にPrefabをおく。
(以下の例ではResourcesフォルダ内にPrefabsフォルダを作り、その中にobj1というprefabを置いている。)GameObject obj = new GameObject(); obj = (GameObject)Resources.Load("Prefabs/obj1")
- 投稿日:2020-04-05T17:01:57+09:00
Unityのスクリプト関数まとめ
概要
Unityのスクリプトでよく使う関数をまとめておく。
(随時追加予定)C#関数
配列内をランダムに取得する
string[] words = new string[3] {"a","b","c"}; Debug.Log(words[Random.Range(0, words.Length)]); // b(出力はランダムに変化)ゲームオブジェクトの設定
オブジェクトを生成する
// 引数(ゲームオブジェクト, 位置, 回転) Instantiate(gameObj, Vector3.zero, Quaternion.identity);親オブジェクトを設定(子オブジェクトとして生成する)
[SerializeField] private GameObject parentObj; // 親オブジェクト // 子オブジェクトを生成して親オブジェクトの設定する GameObject childObj = Instantiate(obj, Vector3.zero, Quaternion.identity); childObj.transform.parent = parentObj.transform;Prefabをゲームオブジェクトに設定する
あらかじめ、Assetsフォルダ内に「Resources」フォルダを作成し、その中にPrefabをおく。
(以下の例ではResourcesフォルダ内にPrefabsフォルダを作り、その中にobj1というprefabを置いている。)GameObject obj = new GameObject(); obj = (GameObject)Resources.Load("Prefabs/obj1")2D画像の設定(Sprite)
以下の例ではinspectorウインドウでSprite画像を設定し、ゲームオブジェクトに設定
[SerializeField] private Sprite sprite_image; gameObj.GetComponent<SpriteRenderer>().sprite = sprite_image;
- 投稿日:2020-04-05T17:01:57+09:00
Unityのスクリプトまとめ
概要
Unityのスクリプトでよく使う関数をまとめておく。
(随時追加予定)C#関数
✨連想配列
Dictionary(Key, Value)の形で値を保持する。以下は宣言と初期化。
Dictionary<string, int> Cards = new Dictionary<string, int>() { { "player", 100 }, { "enemy", 200 } };✨切り上げ・切り捨て・四捨五入
切り上げ(正の無限大に向かう)
float num1 = 1.15f; float num2 = -1.25f; Debug.Log(Mathf.Ceil(num1)); // 2 Debug.Log(Mathf.Ceil(num2)); // -1切り捨て
using System; // Math使用時は必要 float num1 = 1.15f; float num2 = -1.25f; // ①負の無限大に向かう Debug.Log(Mathf.Floor(num1)); // 1 Debug.Log(Mathf.Floor(num2)); // -2 // ②0に向かう Debug.Log(Math.Truncate(num1)); // 1 Debug.Log(Math.Truncate(num2)); // -1四捨五入
using System; // Math使用時は必要 float num1 = 1.5f; float num2 = 2.5f; float num3 = -1.5f; float num4 = -2.5f; float num5 = 1.25f; // ①偶数への丸め込み(切り上げの場合は一番近い偶数になる) Debug.Log(Mathf.Round(num1)); // 2 Debug.Log(Mathf.Round(num2)); // 2 Debug.Log(Mathf.Round(num3)); // -2 Debug.Log(Mathf.Round(num4)); // -2 Debug.Log(Mathf.Round(num5)); // 1 // ②通常の四捨五入 Debug.Log(Math.Round(num1, MidpointRounding.AwayFromZero)); // 2 Debug.Log(Math.Round(num2, MidpointRounding.AwayFromZero)); // 3 Debug.Log(Math.Round(num3, MidpointRounding.AwayFromZero)); // -2 Debug.Log(Math.Round(num4, MidpointRounding.AwayFromZero)); // -3 Debug.Log(Math.Round(num5, MidpointRounding.AwayFromZero)); // 1 // ③桁数指定 (第2引数に得たい小数の桁数を指定) Debug.Log(Math.Round(num5, 1, MidpointRounding.AwayFromZero)); // 1.3✨配列内をランダムに取得する
string[] words = new string[3] {"a","b","c"}; Debug.Log(words[Random.Range(0, words.Length)]); // b(出力はランダムに変化)ゲームオブジェクトの設定
✨オブジェクトを生成する
// 引数(ゲームオブジェクト, 位置, 回転) Instantiate(gameObj, Vector3.zero, Quaternion.identity);✨親オブジェクトを設定(子オブジェクトとして生成する)
以下の例ではinspectorウインドウで親オブジェクトをあらかじめ設定
[SerializeField] private GameObject parentObj; // 親オブジェクト // 子オブジェクトを生成して親オブジェクトの設定する GameObject childObj = Instantiate(obj, Vector3.zero, Quaternion.identity); childObj.transform.parent = parentObj.transform;✨オブジェクト名に[Clone]がつかないようにする
ゲームオブジェクト生成時、オブジェクト名に[Clone]がつく。これをつかないようにする。
GameObject obj = Instantiate(gameObj, Vector3.zero, Quaternion.identity); obj.name = gameObj.name;✨Prefabをゲームオブジェクトに設定する
あらかじめ、Assetsフォルダ内に「Resources」フォルダを作成し、その中にPrefabをおく。
(以下の例ではResourcesフォルダ内にPrefabsフォルダを作り、その中にobj1というprefabを置いている。)GameObject obj = new GameObject(); obj = (GameObject)Resources.Load("Prefabs/obj1")✨2D画像の設定(Sprite)
以下の例ではinspectorウインドウでSprite画像を設定し、ゲームオブジェクトに設定
[SerializeField] private Sprite sprite_image; gameObj.GetComponent<SpriteRenderer>().sprite = sprite_image;
- 投稿日:2020-04-05T16:56:13+09:00
【Unity初心者】チュートリアルを終えた人が知るべきこと3つ
対象の方
なんらかのUnityチュートリアルを終えて制作の雰囲気がわかっている方
知るべきこと3つ
他の記事を見る際に以下の点に注意して読むと内容が分かりやすくなるはずです。
次の3つが私がUnityを学ぶうえで、徐々につかんでいった内容になります。1.変数宣言の型にはUnity独自のものがある
変数宣言の型といったらint、float、stringを最初に学習すると思います。
Unityでは、それらも使用しますが、
Unity上で登場する次のものも宣言する必要があります。
型の種類はまだまだあります。
変数宣言の型にはUnity独自のものがたくさんあることを覚えておくだけでOKです。2.メソッドには2種類ある
・C#やUnity側であらかじめ用意されているメソッド
・自分で処理内容を定義するメソッド私は最初、全部のメソッドは用意されたものがあると思っていました。
メソッドは作れる!!あらかじめ用意されているメソッドは今後紹介していきます。
3.初期スクリプトの中身
初期スクリプト全体 & それぞれざっくりの説明
①名前空間
必要な時に随時追加する必要があります。
詳しくは「れー@DKRevel」さんの記事を参考にして下さい。
https://dkrevel.com/makegame-beginner/namespace②クラス宣言
スクリプトの名前が「TEST_script」ってことがわかればOK③スタート関数
「変数の初期値」、「初回に一度きりの処理」を書きます。④アップデート関数
連続的な処理を書きます。
1フレームの時間間隔はPCごとに変わるようです。
(一定の秒数などで処理したい時は、Time.deltatimeでフレーム間の時間を取得する。 詳細割愛)以上、私が最初に教えてほしかった内容でしたー。
- 投稿日:2020-04-05T16:09:52+09:00
【C#入門】Listの使い方について|要素の追加・削除、ソート、検索
この記事では、《SQL Server のストアドプロシージャ》について、
業務を通して学習した内容を、備忘録としてまとめています。
- 『List』とは…?
- 『List』の要素の追加・削除する方法
- 『List』をソート・検索する方法
こういった内容についてまとめています。
※本記事は、自分で学習したことのまとめ用として書いています。
尚、解説で誤った点があれば、スローして頂ければ喜んでキャッチしますのでお願い致します。『List』 とは…?
『List』とは・・・
同じデータ型の値をまとめて取り扱うためのクラスです。
『List』は配列と異なり、簡単に要素を追加・削除することができるメソッドが用意されているので…
要素数が変化する場合には、『List』を使用します。
C#では、配列のようなデータ構造に対して、途中でデータを追加・削除したい場合にListクラスを使用します。
『List』 の基本的な使い方
まず、使用する際は下記を記述します。
using System; using System.Collections.Generic;『List』 の宣言・初期化について
program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { // int型を格納するリストを宣言 var intlist = new List<int>(); // 初期化した際の初期値を指定する場合は、以下のように宣言 var number = new List<int>() { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; // string型を格納するリストを宣言 var strList = new List<string>() { "hoge", "hogehoge" }; } } }『List』 の要素へのアクセス方法
// 配列のように添え字でアクセスできます int item = list[1];実際に動かしてみます。
program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { var sampleList = new List<string>() { "hoge", "hogehoge" }; Console.WriteLine(sampleList[1]); } } }実行結果:
hogehoge『List』 の要素を全部取り出す
var sampleList = new List<string>() { "hoge", "hogehoge" }; foreach (var str in sampleList) { Console.WriteLine(str); }実際に動かしてみます。
program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { var sampleList = new List<string>() { "hoge", "hogehoge" }; foreach (var str in sampleList) { Console.WriteLine(str); } } } }実行結果:
hoge hogehoge『List』 と 『ArrayList』 の違いは…?
ここでは、『List』と『ArrayList』の違いを解説します。
『List』は…
指定したデータ型の要素しか格納できませんが、要素を取り出すときにキャストをする必要がありません。
そのため・・・
データ型があらかじめ決まっている場合には、『List』を使う方が便利です。
反対に、『ArrayList』は…
異なるデータ型を格納することができますが、要素を取り出すときにキャストする必要があり、コードの記述に手間がかかります。
基本的には、『ArrayList』よりも『List』の方が使いやすいので、『List』を使用すれば大丈夫かと思います。
『List』 の要素の追加・削除する方法|
Add
/Remove
『List』 の要素の追加|
Add
メソッドここでは・・・
『List』に要素を追加する方法を解説します。
『List』に要素を追加するには、
Add
メソッドを使います。
Add
メソッドは、下記のように使用します。program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { var sampleList = new List<string>(); sampleList.Add("hoge"); sampleList.Add("hogehoge"); foreach (var str in sampleList) { Console.WriteLine(str); } } } }実行結果:
hoge hogehoge『List』 の要素の削除|
Remove
メソッドここでは・・・
『List』の要素を削除する方法を解説します。
『List』の要素を削除するには、
Remove
メソッドを使います。
Remove
メソッドは、下記のように使用します。program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { var sampleList = new List<string>(); sampleList.Add("hoge"); sampleList.Add("hogehoge"); sampleList.Remove("hogehoge"); foreach (var str in sampleList) { Console.WriteLine(str); } } } }実行結果:
hoge『List』 を並び替え(ソート)する方法|
OrderBy
メソッドここでは・・・
『List』をソートする方法を解説します。
『List』をソートするには、LINQ拡張の
OrderBy
メソッドを使います。
OrderBy
メソッドは、下記のように使用します。program.csusing System; using System.Collections.Generic; using System.Linq; namespace Sample { class Program { static void Main(string[] args) { var number = new List<int>() { 20, 10, 40, 30, 60, 50, 80, 70, 100, 90 }; var orderedNumber = number.OrderBy(x => x) //.OrderByDescending(x => x) foreach (var num in orderedNumber) { Console.WriteLine(num); } } } }『List』 を検索する方法|
Where
メソッド、Contains
メソッド『List』の要素を検索して、目的の要素を探すにはいくつかの方法がありますが…
通常は、LINQ拡張を使えばいいです。
ただし・・・
要素数が多く、かつ、要素が昇順に並んでいるときに特定の要素を検索する場合は、
Listクラスの
BinarySearch
メソッドが高速です。また・・・
要素が含まれているどうかを判定するには、Listクラスの
Contains
メソッドを使用します。
Where
メソッド|条件を指定して要素を検索する『List』の要素を検索して、条件に一致する要素を取得するには、LINQ拡張の
Where
メソッドを使います。
Where
メソッドは、下記のように使用します。program.csusing System; using System.Collections.Generic; using System.Linq; namespace Sample { class Program { static void Main(string[] args) { var number = new List<int>() { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; var selectedNumber = number.Where(x => x > 50); foreach (var num in selectedNumber) { Console.WriteLine(num); } } } }実行結果:
60 70 80 90 100
Contains
メソッド|指定した値が含まれるか判定する『List』の要素を検索して値が含まれるかどうかを判定するには、Listクラス
Contains
メソッドを使います。
Contains
メソッドは、下記のように使用します。program.csusing System; using System.Collections.Generic; namespace Sample { class Program { static void Main(string[] args) { var sampleList = new List<string>(); sampleList.Add("hoge"); sampleList.Add("hogehoge"); string item = "hogehoge"; if (sampleList.Contains(item)) { Console.WriteLine("{0}が見つかりました", item); } else { Console.WriteLine("{0}は見つかりませんでした", item); } foreach (var str in sampleList) { Console.WriteLine(str); } } } }実行結果:
hogehogeが見つかりました hoge hogehoge
- 投稿日:2020-04-05T14:13:03+09:00
RealSenseをWPFで利用する(CMake不使用)
概要
2020/04/05 現在のIntel RealSense SDK 2.0で、Visual Studio 2019 の C# (WPFアプリ)で、RealSense D435 をまず動かすまでの手順メモです。
GitHub の .NET Wrapper for Intel RealSense SDK にあるサンプルは CMake 前提で用意されていますが、使い慣れていない人も多いかと思います。
そこで CMake は使わずに チュートリアル2のキャプチャ を動かすまでを説明します。ポイント
- プラットフォームを「Any CPU」から「x64」に変更する
- SDKのフォルダのbin\x64にある Intel.Realsense.dll を参照に追加
- SDKのフォルダのbin\x64にある realsense2.dll を実行ファイルの出力ディレクトリーにコピー
以上が準備として重要でした。
検証環境
以下の環境で試しました。
- RealSense SDK 2.33.1.13
- RealSense D435 Firmware 05.12.03.00
- Visual Studio Community 2019 Ver. 16.4.5
- .NET Framework 4.7.2前提条件
RealSense SDK はインストールしてあること。
インストール時には 「.NET Developer Package」を含めます。
Intel RealSense Viewer でカメラの動作確認をしておいてください。
0. 新しいプロジェクトの作成
WPFアプリ(.NET Framework)を選択
プロジェクト名を指定して作成
ここでは RealSenseWPFtest という名前にしました。プロジェクトを用意したら、この記事のメインとなる RealSense を使う準備です。
1. 「AnyCPU」→「x64」に変える
(もし32ビットで作りたい、という場合は以降「x64」は「x86」に置き換えて読んでください。)
構成マネージャーを開く
ツールバーで最初「Any CPU」と出ている部分を押して、「構成マネージャー」を選びます。
プラットフォームの 新規作成
新しいプラットフォームとして x64 を作成します。
コピー元は Any CPU のままでいいです。
Visual Studio 上部のツールバーで x64 が選ばれた状態になっていればOKです。
2. Intel.Realsense.dll を参照に追加
RealSense SDK は通常「C:\Program Files (x86)\Intel RealSense SDK 2.0」というフォルダにインストールされるはずです。その中の bin\x64 にある DLL を使えるようにしていきます。
そのうちの Intel.Realsense.dll は「参照」を含めることでusing Intel.RealSense;
ができるようになります。プロジェクト(ここでは RealSenseWPFtest)の中の「参照」を右クリックし、「参照の追加(R)...」を選択します。
左のメニューから「参照」を選択して、右下の「参照(B)...」ボタンを押します。
C:\Program Files (x86)\Intel RealSense SDK 2.0\bin\x64
のフォルダにある、Intel.Realsense.dll を選択して「追加」
その後、参照マネージャー ウィンドウは閉じます。3. realsense2.dll を追加
先の Intel.Realsense.dll は中で realsense2.dll を呼び出しています。アプリの実行時にこれを見つけられる必要があるため、実行ファイルと同じフォルダにコピーされるようにしておきます。
今度は Visual Studio とは別に、C:\Program Files (x86)\Intel RealSense SDK 2.0\bin\x64 フォルダを開いておきます。
そこから realsense2.dll のファイルを、プロジェクト(ここでは RealSenseWPFtest の所)にドロップします。
プロジェクトに realsense2.dll の複製が追加されます。
現れた realsens2.dll を選択して、プロパティの「出力ディレクトリにコピー」を「新しい場合はコピーする」にします。
以上で、このWPFアプリの実行フォルダに必要なDLLがコピーされるようになります。
4. 画面とコード作成
GitHubの cs-tutorial-2-capture サンプルと同様のものを作ってみます。
なお元のライセンスは Apache License Version 2.0 です。
ウィンドウの編集
サンプルの Window.xaml から Grid タグの中身だけコピーして MainWindow.xaml のGrid内に貼付けます。
また、Windowタグ内に
Closing="control_Closing"
も貼り付けておきます。
編集後の MainWindow.xaml
clr-namespace は必ずしも RealSenseWPFtest ではなく自分のプロジェクトに合わせてください。
MainWindow.xaml<Window x:Class="RealSenseWPFtest.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:RealSenseWPFtest" mc:Ignorable="d" Closing="control_Closing" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBox x:Name="txtTimeStamp" Grid.Column="0" /> <Image x:Name="imgDepth" Grid.Column="0" /> <Image x:Name="imgColor" Grid.Column="1" /> </Grid> </Window>
コードの編集
サンプルにある Window.xaml.cs から、MainWindow.xaml.cs に、namespaceが異なることなどを考慮して必要な部分を移植します。
編集後のコードをここに置きますので、これをコピーして貼り付けてもいいです。
編集後の MainWindow.xaml.cs
namespace は RealSenseWPFtest ではなく自分のプロジェクトに合わせてください。
MainWindow.xaml.csusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using Intel.RealSense; namespace RealSenseWPFtest { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { private Pipeline pipeline; private Colorizer colorizer; private CancellationTokenSource tokenSource = new CancellationTokenSource(); static Action<VideoFrame> UpdateImage(Image img) { var wbmp = img.Source as WriteableBitmap; return new Action<VideoFrame>(frame => { var rect = new Int32Rect(0, 0, frame.Width, frame.Height); wbmp.WritePixels(rect, frame.Data, frame.Stride * frame.Height, frame.Stride); }); } public MainWindow() { InitializeComponent(); try { Action<VideoFrame> updateDepth; Action<VideoFrame> updateColor; // The colorizer processing block will be used to visualize the depth frames. colorizer = new Colorizer(); // Create and config the pipeline to strem color and depth frames. pipeline = new Pipeline(); var cfg = new Config(); cfg.EnableStream(Stream.Depth, 640, 480); cfg.EnableStream(Stream.Color, Format.Rgb8); var pp = pipeline.Start(cfg); SetupWindow(pp, out updateDepth, out updateColor); Task.Factory.StartNew(() => { while (!tokenSource.Token.IsCancellationRequested) { // We wait for the next available FrameSet and using it as a releaser object that would track // all newly allocated .NET frames, and ensure deterministic finalization // at the end of scope. using (var frames = pipeline.WaitForFrames()) { var colorFrame = frames.ColorFrame.DisposeWith(frames); var depthFrame = frames.DepthFrame.DisposeWith(frames); // We colorize the depth frame for visualization purposes var colorizedDepth = colorizer.Process<VideoFrame>(depthFrame).DisposeWith(frames); // Render the frames. Dispatcher.Invoke(DispatcherPriority.Render, updateDepth, colorizedDepth); Dispatcher.Invoke(DispatcherPriority.Render, updateColor, colorFrame); Dispatcher.Invoke(new Action(() => { String depth_dev_sn = depthFrame.Sensor.Info[CameraInfo.SerialNumber]; txtTimeStamp.Text = depth_dev_sn + " : " + String.Format("{0,-20:0.00}", depthFrame.Timestamp) + "(" + depthFrame.TimestampDomain.ToString() + ")"; })); } } }, tokenSource.Token); } catch (Exception ex) { MessageBox.Show(ex.Message); Application.Current.Shutdown(); } } private void control_Closing(object sender, System.ComponentModel.CancelEventArgs e) { tokenSource.Cancel(); } private void SetupWindow(PipelineProfile pipelineProfile, out Action<VideoFrame> depth, out Action<VideoFrame> color) { using (var p = pipelineProfile.GetStream(Stream.Depth).As<VideoStreamProfile>()) imgDepth.Source = new WriteableBitmap(p.Width, p.Height, 96d, 96d, PixelFormats.Rgb24, null); depth = UpdateImage(imgDepth); using (var p = pipelineProfile.GetStream(Stream.Color).As<VideoStreamProfile>()) imgColor.Source = new WriteableBitmap(p.Width, p.Height, 96d, 96d, PixelFormats.Rgb24, null); color = UpdateImage(imgColor); } } }
5. 実行
動かした結果、色分けされた深度とカメラの画像が映りました!。
Any CPU として動かした場合
参照に追加はできていないとコードでエラー表示があるため分かりやすいです。
ですが「Any CPU」を「x64」にするというのは気づきにくいです。
そのまま動かすと、このような BadImageFormatException が出ました。
終了時の例外
実行させたウィンドウを閉じる際、Debugだとタイムスタンプを表示させている部分で TaskCanceledException が出やすいかもしれません。
タイムスタンプ表示が不要ならば、その部分(82~86行目)をコメントアウトしておいてもいいかもしれません。
- 投稿日:2020-04-05T14:10:04+09:00
AtCoder Beginner Contest 161 D - Lunlun Number
Atcoderに取り組んだ際のメモです。
問題はこちら結果
Point : 600
Performance : 522
■A[完答 21:03:31] ABC Swap
■B[完答 21:19:22] Popular Vote
■C[完答 21:39:11] Replacing Integer
■D[TLE + バグ] Lunlun Number
■E[未着手] Yutori
■F[未着手] Division or SubstractionD - Lunlun Number
■問題内容
隣り合うどの 2 つの桁の値についても、差の絶対値が 1 以下になる整数をルンルン数とする。
整数Kが与えられた時、小さい方から数えてK番目のルンルン数を求めよ。
制約:1≤K≤10^5コンテスト中に考えた事
- 「制約:1≤K≤10^5」だから1から順にルンルン数であるかチェックしていく全探索で単純にいけそう。
- 探索高速化のロジックが必要(であったが上手くいかない)
- なぜこの探索高速化のロジックが上手くいかないのか
「制約:1≤K≤10^5」だから1から順にルンルン数であるかチェックしていく全探索で単純にいけそう。
そんなに単純ではなかった。そのまま実装したが、テスト段階でTLEが発生した。小さい方からK番目のルンルン数なので、探索する数がKという訳ではないことに気づく必要があった。
探索高速化のロジックが必要(であったが上手くいかない)
小さい数から順に探索する場合、次のルンルン数までたどり着くのに無駄な数をチェックすることが多い。その無駄な探索回数を極力減らせないかというアプローチで探索高速化のロジックを実装した。
i桁目の数とi+1桁目の数の差が1より大きい時が異なる時、次にルンルン数となるのはi桁目に8を足した数の時でないかと推測した。例えば、11,12,…と探索すると13はルンルン数でないことが分かる。この時、13 +8→ 21 から次のルンルン数の候補に21が挙げられ、21はルンルン数なので、再び22,23,…と探索していく。別の例として、124の時、124 +8 → 132 より132となる。これはルンルン数ではないので、再度 132 +80 → 212 より次のルンルン数候補を得、そして212はルンルン数である。
ABC161D.cspublic void Solve(ConsoleInput cin) { var K = cin.ReadInt; long ans = 0; long count = 0; if (K <= 12) { WriteLine(K); return; } ans = 20; count = 12; while (true) { ans++; //WriteLine(ans); var isRunrun = true; var chars = ans.ToString().ToArray(); for (int i = 0; i < chars.Length - 1; i++) { var gap = chars[i] < chars[i + 1] ? chars[i+1] - chars[i] : chars[i] - chars[i + 1]; if (gap > 1) { isRunrun = false; var wk = 1; for(var l = 0; l < chars.Length - 2 - i; l++) { wk *= 10; } ans += 8 * wk - 1; break; } } if (isRunrun) { count++; //WriteLine("count : {0} ans : {1}" ,count,ans); } if (count == K) break; } WriteLine(ans); }しかし、この方法ではルンルン数の探索は確かに早くなるが、このD問題を解く為には不十分であった。
なぜこの探索高速化のロジックが上手くいかないのか
このD問題では、小さい方から数えてK番目のルンルン数を求める必要がある。しかし、先ほどのロジックでは小さい順にルンルン数を算出することが出来なかった。
正しくない例として、124の場合があげられる。先ほどのロジックでは212が次のルンルン数として挙げられる。しかし、次に来るべきルンルン数は、210である。単純に違ってた桁に8を足すだけではうまくいかないようである。
解答から学んだこと
解説されていた解法
キューを使って、ルンルン数の条件を満たす数字を昇順に列挙する。
1. キューに1,2,3,4,5,6,7,8,9を投入する。
2. キューから数字を1つ取り出して、その数字を左にシフトして作れる数字をキューに投入する。
例えば、1を取り出し多場合は、10の位が1になり、1の位に使用可能な数字は0,1,2なので、10,11,12をキューに投入する。
3. K回繰り返すと出てくる数が解答となる。自分の解法と解説されていた解法との差
私の解法と解説されていた解法では明らかに違う点が、ルンルン数探索へのアプローチの方法である。
解説されていたものではルンルン数のみを列挙していたことに対し、私の解法ではルンルン数への探索を減らすことを前提としていた。私の解法でもあるルンルン数と次のルンルン数への規則性を利用しようとしていた。しかし、その規則性を突き詰めぬまま、探索ありきの考えに固執していた為、上記のようなロジックとなってしまった。提出したコード(解説を元に実装)
キューを使用することでこんなにもシンプルに実装することが出来た。キューを使った実装は初めてだったので、新鮮で面白かった。今後はキューを使用するという発想も持てるようにしたい。
ABC161D_after.csusing System; using System.Text; using System.Linq; using System.Collections; using System.Collections.Generic; using static System.Console; using static System.Math; namespace AtCoder { public class Program { public static void Main(string[] args) { new Program().Solve(new ConsoleInput(Console.In, ' ')); } public void Solve(ConsoleInput cin) { var K = cin.ReadInt; var LunLunNumber = new Queue<long>(); //キューに1~9を追加 for (int i = 1; i < 10; i++) { LunLunNumber.Enqueue(i); } var count = 0; long ans = 0; while (true) { count++; if (count >= K) { ans = LunLunNumber.Dequeue(); break; } //次のルンルン数をキューに追加する var num = LunLunNumber.Dequeue(); var numMod10 = num % 10; if (numMod10 != 0) LunLunNumber.Enqueue(num * 10 + numMod10 - 1); LunLunNumber.Enqueue(num * 10 + d); if (numMod10 != 9) LunLunNumber.Enqueue(num * 10 + numMod10 + 1); //WriteLine(num); } WriteLine(ans); } } public class ConsoleInput { private readonly System.IO.TextReader _stream; private char _separator = ' '; private Queue<string> inputStream; public ConsoleInput(System.IO.TextReader stream, char separator = ' ') { this._separator = separator; this._stream = stream; inputStream = new Queue<string>(); } public string Read { get { if (inputStream.Count != 0) return inputStream.Dequeue(); string[] tmp = _stream.ReadLine().Split(_separator); for (int i = 0; i < tmp.Length; i++) inputStream.Enqueue(tmp[i]); return inputStream.Dequeue(); } } public string ReadLine { get { return _stream.ReadLine(); } } public int ReadInt { get { return int.Parse(Read); } } public long ReadLong { get { return long.Parse(Read); } } public double ReadDouble { get { return double.Parse(Read); } } public string[] ReadStrArray(long N) { var ret = new string[N]; for (long i = 0; i < N; ++i) ret[i] = Read; return ret; } public int[] ReadIntArray(long N) { var ret = new int[N]; for (long i = 0; i < N; ++i) ret[i] = ReadInt; return ret; } public long[] ReadLongArray(long N) { var ret = new long[N]; for (long i = 0; i < N; ++i) ret[i] = ReadLong; return ret; } } }
- 投稿日:2020-04-05T05:27:56+09:00
オブジェクトを複製しよう(Prehub)
前置き
C#なら一度書けば覚えるのに、Unity関係の機能は何度やっても覚えられない……。(-_-)zzz
ゲームのステージ作るのにPrehub使いたいのになあ。( ;∀;)
このブログは何のためにあるのか、それは覚えるのが面倒なもののメモのためだー!(定期)ここから本編
3分Prehubクッキング
1分目Prehubにしたいオブジェクトを作りましょう。
2分目PrehubにしたいオブジェクトをAsset内にドラック&ドロップしましょう。
3分目Prehubにしたいオブジェクトが青くなったのを確認して、お茶を飲みましょう。
大量生産!大量消費!
prehub.csusing UnityEngine; //最初にステージを生成する public class stagecreate : MonoBehaviour { //スクリプトにPrehubをアタッチする public GameObject fields; void Start() { //Prehubを作成 GameObject field = Instantiate(fields) as GameObject; //ここで位置情報を入力したり、名前を変えたり、後はお好みで。 } }Instantiate関数ってのを使います。Prehubを生成する関数という認識で大丈夫!('ω')
destroy.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Control : MonoBehaviour { void Update() { //一定範囲内にいないとこのオブジェクトを破壊する if(this.transform.position.x > 50) { Destroy(this.gameObject); } } //何かに当たったら反応 void OnTriggerStay(Collider other) { //当たったものが"weapon"だったらこのオブジェクトを破壊 if (other.gameObject.name == "weapon") { Destroy(this.gameObject); } } }参考までに作ったPrehubを破壊するオブジェクト。(Prehubである必要は特にない)
Prehubに直接このスクリプトをアタッチすれば動いてくれる。('ω')ノ
たくさんPrehubを破壊する場合はプールという機能を使えばいいらしいけど、勉強不足なので、勉強後にそれについてもメモ書いとこうかなあ(計画倒れになるやつ)Instantiate関数についてはこれが詳しく書いてくれてます。(*^^)v
オブジェクト生成の仕方 Instantiate
https://qiita.com/Teach/items/c28b4fe5ca8dc4c83e26#prefab%E3%81%AEtransform%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%A7%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88
- 投稿日:2020-04-05T04:21:10+09:00
PowerShell でスクリーンショットを Excel シートに自動ペースト
C♯ のたすけをかりてクリップボードのイベント駆動に成功
拙文『スクリーンショットをExcelシートに半自動でペーストする(エビデンス取得用)』 (Excel VBA) ではイベント駆動に挫折し無限ループをまわしていますが、こちらは成功いたしました。
Paste-ScreenShotToExcelSheet.ps1Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; using System.Windows.Forms; public class ClipboardListeningForm : Form { [DllImport("user32.dll", SetLastError = true)] private extern static void AddClipboardFormatListener(IntPtr hwnd); [DllImport("user32.dll", SetLastError = true)] private extern static void RemoveClipboardFormatListener(IntPtr hwnd); public event EventHandler ClipboardUpdate; public const int WM_CLIPBOARDUPDATE = 0x031D; protected override void OnLoad(EventArgs e) { AddClipboardFormatListener(Handle); base.OnLoad(e); } protected override void Dispose(bool disposing) { RemoveClipboardFormatListener(Handle); base.Dispose(disposing); } protected override void WndProc(ref Message m) { if (m.Msg == WM_CLIPBOARDUPDATE) { OnClipboardUpdate(EventArgs.Empty); m.Result = IntPtr.Zero; } else base.WndProc(ref m); } protected virtual void OnClipboardUpdate(EventArgs e) { EventHandler handler = ClipboardUpdate; if (handler != null) { handler(this, e); } } } '@ -ReferencedAssemblies System.Windows.Forms Add-Type -AssemblyName Microsoft.Office.Interop.Excel $form = New-Object -TypeName ClipboardListeningForm $form.Text = 'Start Capturing' $excel = New-Object -ComObject Excel.Application $excel.Visible = $true $xlBook = $excel.Workbooks.Add([Microsoft.Office.Interop.Excel.xlWBATemplate]::xlWBATWorksheet) $xlWorksheet = $xlBook.Worksheets(1) $toggleButton = New-Object -TypeName System.Windows.Forms.CheckBox $toggleButton.Appearance = [System.Windows.Forms.Appearance]::Button $toggleButton.Left = ($form.ClientRectangle.Width - $toggleButton.Width) / 2 $toggleButton.Top = ($form.ClientRectangle.Height - $toggleButton.Height) / 2 $toggleButton.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $toggleButton.Text = 'Start' $toggleButton.add_CheckedChanged({ if ($toggleButton.Checked) { $toggleButton.Text = 'Stop' $form.Text = 'Capturing' } else { $toggleButton.Text = 'Start' $form.Text = 'Stopped' } }) $form.Controls.Add($toggleButton) $form.add_ClipboardUpdate({ $targetColumn, $targetRow = 2, 2 if ($toggleButton.Checked -and [System.Windows.Forms.Clipboard]::ContainsImage()) { $shapesCount = $xlWorksheet.Shapes.Count if ($shapesCount -gt 0) { $targetRow = $xlWorksheet.Shapes($shapesCount).BottomRightCell.Offset(1).Row } $xlWorksheet.Cells($targetRow, $targetColumn).Select() $xlWorksheet.Paste() } }) $form.ShowDialog() $xlWorksheet, $xlBook, $excel = $null, $null, $null参考文献
- C#でClipboard監視(Windows API) - Qiita
- クリップボードを監視してテキストボックスに追加していく感じの。 · GitHub
- Monitor Clipboard changes in C# using AddClipboardFormatListener / WM_CLIPBOARDUPDATE. See Clipboard class: http://msdn.microsoft.com/en-us/library/system.windows.clipboard(v=vs.110).aspx · GitHub
- PowerShell で Excel をどうのこうのすることに興味を持ってくれると嬉しい - Qiita
- 投稿日:2020-04-05T00:47:11+09:00
【WPF】ImageSourceもUIスレッドで作る必要がある
TL;DR;
NG
ImageSource thumbnail = await Task.Run(() => { // ワーカスレッド var bitmap = new Bitmap(image)); // Drawing.Image => Bitmap 変換 var imageSouce = Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions())); // Bitmap => ImageSource return imageSource }); // UIスレッド viewModel.Add(new ViewModel(){ Thumbnail = thumbnail, });System.ArgumentException: 'DependencySource は、DependencyObject と同じ Thread 上で作成する必要があります。'
OK
Bitmap bitmap = await Task.Run(() => { // ワーカスレッド var bitmap = new Bitmap(image)); // Drawing.Image => Bitmap 変換 return bitmap; }); // UIスレッド var thumbnail = Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions())); // Bitmap => ImageSource viewModel.Add(new ViewModel(){ Thumbnail = thumbnail, });やりたかったこと
何枚かある画像を
Drawing.Image
で入力されます。
それをなるべくUIスレッドを止めずに、非同期でControls.Image
に表示します。詳細
WPFでは、UI操作はUIスレッドで行わなければなりません。
なので、XAMLでx:Name
でクラス内に持ってきて直接いじる場合はもちろんのこと、
BindされているViewModelのプロパティやコレクションも変更した際にUI操作が刺さったイベントが発火するので、UIスレッドで行う必要があります。
ここまでは自然に思えます。しかし、ImageSourceの生成はUIスレッドで行わなければならないようです。
名前空間も違いますし(System.Windows.Controls
とSystem.Windows.Media
)、ImageSourceの生成は結構重いのでできればワーカスレッドに逃がしたいところではありますが。// PropertyChangedの発火はUIスレッドが期待される abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChanged([CallerMemberName]string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // 適当なViewModel class ViewModel { ImageSource thumbnail; public ImageSource Thumbnail { get => thumbnail; set { RaisePropertyChanged(); thumbnail = value; } } } async Task ApplyImageAsync(ObservableCollection<ViewModel> viewModels, Image image) { var bitmap = await Task.Run(() => new Bitmap(image)); // Drawing.Image => Bitmap 変換 ImageSource thumbnail = Imaging.CreateBitmapSourceFromHBitmap(source.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); // Bitmap => ImageSource // ただ変換しているだけのように思えるが、実はImageSourceの生成はUIスレッドで行わなければならない。 // しかも例外が起こるのは、ImageSource生成時ではなく、BindされたViewModelのOnXXXChangedが飛んで // 実際にImageSourceが使われるときになるので注意が必要。 var viewModel = new ViewModel(){ Thumbnail = thumbnail, }; // この例では新規インスタンスなのでPropertyChangedは発火しても何も起こらないが、 // すでにBindされている場合はPropertyChangedの発火でUI操作が走る可能性があるので、 // ViewModelのプロパティを操作するときはUIスレッドでやる必要がある。 viewModel.Add(viewModel); // ObservableCollection<T>.OnCollectionChangedが呼ばれ // その先にはUI操作が刺さっているので、UIスレッドでAddする必要がある。 }[補足] Async/Awaitについて
私も嘗てはなかなか理解できなかったのですが、次のように考えるとすんなり理解できました。
(※あくまでもイメージです)asyncメソッドの戻り値は
Task
/Task<TResult>
型と決められています。(void
もありますが例外だと思ってください)
つまり、呼び出し側が戻り値としてもらえるのは、「タスク情報」です。
このタスク情報には、タスク、つまりasyncメソッドが「終了しているか」「asyncメソッドでreturn
した値」が取り出せます。Task<int> task = GetICountAsync(); while(!task.IsComplete) { // ビジーループで待ち受け } int count = task.Result;というように書くこともできます。
ただし、ビジーループではUIスレッドが止まってしまい、フリーズしたように見えてしまうのでよくありません。
そこで、awaitが登場します。
awaitには二つの機能があります。一つ目は、タスク情報から真の戻り値を取り出すことです。先ほどのビジーループの例では、
GetCountAsync()
は、Task<int>
型の変数で受け取っていました。
しかし、awaitを使うと、int count = await GetCountAsync();
というように受ける変数の型が変わります。これにより単純にコードが短くなりハッピーになります。次のようなTask<TResult>
=>TResult
への変換機能だと思ってください。TResult Await(Task<TResult> task) => task.Result; int count = Await(GetCountAsync());二つ目は、
Task<TResult>
(A)を受け取ったら即座にそのメソッドを中断します。その代わりにタスク情報(B)を返します。
メソッドの残りの処理については受け取ったタスク情報(A)が終わったときのイベントに「元のスレッドのタスク処理待ちに積む」処理を刺します。(元のスレッドというのは、正確にはSynchronizationContext.Current
です。)
積む処理にはタスク情報の真の戻り値を渡します。これが一つ目の機能ですね。なので、残っていた処理はちゃんとUIスレッドで実行されるというわけです。そして、元スレッドの処理待ちに積まれた処理が完了すると、注目していたawaitを含むasyncメソッドが終了します。
そして、awaitが中断した代わりに呼び出し元に返していたタスク情報(B)も完了済みになります。
呼び出し元もawaitで呼び出していたとしたら呼び出し元の残っていた処理が消化され、呼び出し元メソッドも終了します。
そして呼び出し元の呼び出し元が…というように遡っていきます。じゃあ、始端はどうなるのだというと、
Task<Result>
で受け取ったまま特に何もしていなければ、なにもされないだけです。{ Task<int> task = GetICountAsync(); // GetICountAsyncがタスク情報を返してきた時点で制御が戻る } var hoge = Hoge(); // taskが完了したかなど関係なく普通に進んでいく ...ということでまとまるとawaitは
- タスク情報(A)受け取り
- メソッドを中断
- Aが終わったときに「Aの真の戻り値で残りの処理を元のスレッドの処理待ちに積む」処理がされるようにAに仕込む
- 呼び出し元にタスク情報(B)を返す
ということを「await」と書くだけでやってくれるものです。
まだ一つ、不明な点があります。始端ががあれば終端があるはずです。awaitはタスク情報を受け取りタスク情報を返します。最初にタスク情報を返してくるのは何なのかといった問題です。
もちろん、return new Task<Result>()
でもいいのですが、これを使うことはまずありません。
実用では、大体二つの方法を使います。
一つ目は、組み込みのTaskを返すTask.Delay
やFile.ReadTextAllAsync
などのメソッドです。内部的にnew Task<TResult>()
と思って下さい。
二つ目は、ある意味一つ目に含まれますが、Task.Run(Func<TResult>)
です。これはFunc<TResult>
をワーカスレッドに逃がしてTask
を返してくれます。自作で時間のかかるメソッドを作ったときにはTask.Run
に突っ込んでタスク情報を返すようにしてあげるようにするといいと思います。(このメソッドはワーカスレッドなのでUIを触ってはいけません!)まとめると、
本来戻り値のバケツリレーであるメソッド呼び出しをタスク情報のバケツリレーに置き換え、真の戻り値は終わったらやっといてねー。あ、UIスレッドで」といったところでしょうか。余談
職場で「Async/Await解禁!」となって喜んでいたのですが、ちゃんと他人にも説明できるようにと思ってついでにまとめてみました。
のですが…補足の方が長くなってしまいました…もはやタイトル違いですね。
保身ですが、Async/Awaitを受け止められることを目的とした説明なので、厳密にはちょっと違うし暗黙でシチュエーションが限定されていることを言訳しておきます。ちなみに前にもAsync/Awaitについて書いてます。
async/await で書いたコードと実行されるスレッド(Qiita)
参考