ある時突然ストリートファイター5がやりたくなることが誰でもあると思います。
そして、手元には Surface Go 3(※) だけしかない。そんな日もたまにはあります。
※真ん中のスペックのモデル(最初に発売された2モデルの上位モデルの方)
■ ベンチマーク結果
ベンチマークは完走しました!
お、行けるか!? と思ったら結果は
STREET FIGHTER Ⅴ をプレイするには PC のスペックが不足しています。
画面品質を低にしてもでした。
無念。
ある時突然ストリートファイター5がやりたくなることが誰でもあると思います。
そして、手元には Surface Go 3(※) だけしかない。そんな日もたまにはあります。
※真ん中のスペックのモデル(最初に発売された2モデルの上位モデルの方)
ベンチマークは完走しました!
お、行けるか!? と思ったら結果は
STREET FIGHTER Ⅴ をプレイするには PC のスペックが不足しています。
画面品質を低にしてもでした。
無念。
ちょっとゲームのベンチマークを動かそうと思ったときに、動かなかったのでメモ。
スペック的に DirectX のバージョンは満たしている...と思っていたのですが次のエラーが。
The following component(s) are required to run this program: DirectX Runtime
次のサイトからランタイムをインストール
これで動くようになりました。
リフレクションを使って構造体のプロパティに値をセットする方法です。
クラスと同じように書いてはうまくいかなかったのでメモ。
ここを見ればすべてが解決します。
Sample s = new Sample(); var type = typeof(Sample); var p = type.GetProperty("PropA"); p.SetValue(s, "value"); Console.WriteLine(s.PropA); // 結果は null 期待通りにならない struct Sample { public string PropA { get; set; } }
構造体を Box 化します
Sample s = new Sample(); var type = typeof(Sample); var p = type.GetProperty("PropA"); object o = (object)s; p.SetValue(o, "value"); var s2 = (Sample)o; Console.WriteLine(s.PropA); // 結果は null Console.WriteLine(s2.PropA); // 結果は value 期待通り struct Sample { public string PropA { get; set; } }
ちょっとつらいですね。
以前にコマンドライン引数を雑に扱う記事を書きました。
コンソールアプリ、ちょっとした処理を行う際に雑に作ってしまうのが結構いいのですが、何気にコマンドライン引数の解釈コードが面倒です。 それはもう、あらゆる要素をソースコードに書き込んで毎回ソースコード上の値を書き換えてビルドして使うほどに。
というわけで、引数を扱うコードを雑に書いてきたのですが、いつまでもいろいろ不完全だったりコードも雑すぎたので今後いじっていきやすいように NuGet パッケージ化してしまいました。
dotnet add package Rksoftware.ArgumentsBuilder
解釈した引数の値を格納する record を用意します。
using Rksoftware.ArgumentsBuilder.Attributes; record struct Arguments([Parameter] string? Aparam, [Parameter] string? Bparam, string? A, string? B, string? C, bool D, bool E) { }
Parameter という属性が付いている引数に、コマンドライン引数の何もない普通の引数が入ってきます。たとえば
<command> 1 2
として実行すると、引数 Aparam の値が 1 に、Bparam の値が 2 になります。
Parameter という属性が付いていない string? 型の引数は - や / で始まるオプション指定の値が入ってきます。たとえば
<command> -a 1 /b 2
として実行すると、引数 A の値が 1 に、B の値が 2 になります。
Parameter という属性が付いていない string? 型の引数は - や / で始まるオプション指定があった場合、true が入ってきます。たとえば
<command> -d /e
として実行すると、引数 D の値が true に、E の値が true になります。
using Rksoftware.ArgumentsBuilder.Attributes; record struct Arguments([Parameter] string? Aparam, [Parameter] string? Bparam, string? A, string? B, string? C, bool D, bool E) { }
interface IService { } class Service : IService { public string Run(Arguments arguments) => arguments.ToString(); }
ApplicationRun という拡張メソッドで、DI でとってくるインタフェースと引数レコードの型および引数を指定するだけで OK。
この例では ApplicationRun の中で IService インタフェースの実行クラスの中から Arguments 型の引数をもつメソッドを探して実行してくれます。
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Rksoftware.ArgumentsBuilder.Hosts; using IHost host = Host.CreateDefaultBuilder(args).ConfigureServices((_, services) => services.AddSingleton<IService, Service>()).Build(); var result = host.ApplicationRun<IService, Arguments>(args); Console.WriteLine((string?)result);
簡単ですね。
以前にコマンドライン引数を雑に扱う記事を書きました。
コンソールアプリ、ちょっとした処理を行う際に雑に作ってしまうのが結構いいのですが、何気にコマンドライン引数の解釈コードが面倒です。 それはもう、あらゆる要素をソースコードに書き込んで毎回ソースコード上の値を書き換えてビルドして使うほどに。
というわけで、引数を扱うコードをメモしておきます。まだいろいろ不完全なので今後いじっていきたいコードのちょっとバージョンアップ版コードです。
今回は引数を格納するオブジェクトを record にできるようにしました。
今回はインスタンスを生成する前にパラメーターに対応するプロパティの名前がわからなければならないので、パラメーター名のリストを static にしてみました。 引数を格納するクラスには次のインタフェースを実装してもらいます。
public interface IArguments { static IEnumerable<string> ParameterNames { get; } = new string[] { }; }
実装例
record struct Arguments(string? Aparam, string? Bparam, string? A, string? B, string? C, bool D, bool E) : IArguments { public static IEnumerable<string> ParameterNames => new[] { nameof(Aparam), nameof(Bparam) }; }
一つのメソッドの中にひたすら書き続けたので、かなり読みにくいので今後書き換えていきたいです。
ただ読むのは大変ですが、コピペで使うには楽かもしれません。
public static class ArgumentsBuilder { public static T Parse<T>(string[] args) where T : IArguments, new() { var argumentsType = typeof(T); List<string> parameters = new(); Dictionary<PropertyInfo, string> options = new(); Dictionary<PropertyInfo, bool> switchs = new(); PropertyInfo? optionProperty = default; foreach (var arg in args.Select(arg => arg ?? string.Empty)) { { var pre = arg.First(); if (new[] { '-', '/' }.Contains(pre)) { var optionName = new string(arg.SkipWhile(c => c == pre).ToArray()); optionProperty = argumentsType.GetProperty(optionName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (optionProperty == null) continue; if (optionProperty.PropertyType == typeof(bool)) { switchs.Add(optionProperty, true); optionProperty = null; continue; } options.Add(optionProperty, ""); continue; } } if (optionProperty == null) { parameters.Add(arg); continue; } options[optionProperty] = arg; optionProperty = null; } var parameterNames = ((IEnumerable<string>)(argumentsType.GetProperty(nameof(IArguments.ParameterNames))?.GetValue(null) ?? new string[0])).ToList(); foreach (var o in options.Where(o => parameterNames.Contains(o.Key.Name)).ToArray()) options.Remove(o.Key); foreach (var o in switchs.Where(o => parameterNames.Contains(o.Key.Name)).ToArray()) options.Remove(o.Key); var constractor = argumentsType.GetConstructors().OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); T arguments = constractor switch { null => new(), _ => ((Func<T>)(() => { var constractorParameterLength = constractor.GetParameters().Length; var constractorParameters = constractor.GetParameters(); var constractorParameterValues = constractorParameters.Select(p => { var name = p.Name ?? string.Empty; if (parameterNames.Contains(name)) { parameterNames.Remove(name); var value = parameters.FirstOrDefault(); if (parameters.Count > 0) parameters.RemoveAt(0); return value; } { var option = options.FirstOrDefault(o => o.Key.Name == name); if (!string.IsNullOrWhiteSpace(option.Key?.Name)) { options.Remove(option.Key); return option.Value; } } { var @switch = switchs.FirstOrDefault(o => o.Key.Name == name); if (!string.IsNullOrWhiteSpace(@switch.Key?.Name)) { switchs.Remove(@switch.Key); return @switch.Value; } } return (object?)null; }).ToArray(); return (T)constractor.Invoke(constractorParameterValues); }))(), }; foreach (var parameter in Enumerable.Zip(parameterNames, parameters)) argumentsType.GetProperty(parameter.First)?.SetValue(arguments, parameter.Second); foreach (var option in options) option.Key.SetValue(arguments, option.Value); foreach (var @switch in switchs) @switch.Key.SetValue(arguments, @switch.Value); return arguments; } }
<実行ファイル名> aparam -a aopt -d -b bopt bparam /c copt
var parsed = ArgumentsBuilder.Parse<Arguments>(args); // パラメーター parsed.Aparam; // aparam parsed.Bparam; // bparam; // オプション parsed.A; // aopt parsed.B; // bopt parsed.C; // copt // スイッチ parsed.D; // true parsed.E; // false
雑に作るコンソールアプリ用のコピペ元としてはこんな感じでいいでしょうかね。
以前にコマンドライン引数を雑に扱う記事を書きました。
コンソールアプリ、ちょっとした処理を行う際に雑に作ってしまうのが結構いいのですが、何気にコマンドライン引数の解釈コードが面倒です。 それはもう、あらゆる要素をソースコードに書き込んで毎回ソースコード上の値を書き換えてビルドして使うほどに。
というわけで、引数を扱うコードをメモしておきます。まだいろいろ不完全なので今後いじっていきたいコードのちょっとバージョンアップ版コードです。
今回、- や / で始まる引数をオプションまたはスイッチと呼びます。
- や / で始まる引数の次の引数が対応する値となる場合、オプション。
- や / の後に値を取らない (実体としては bool 型プロパティに true をセットする) 場合、スイッチと呼びます。
- や / で始まる引数でもその値でもないものをパラメーターと呼びます。
よくわからないと思うのでこの記事の最後の実例を見てください。
今回は引数を格納するクラスをユーザーが作って、そのオブジェクトに引数の値を詰めるようにしてみました。
引数を格納するクラスには次のインタフェースを実装してもらいます。
public interface IArguments { IEnumerable<string> ParameterNames { get; } }
実装例
class Arguments :IArguments { // パラメータを頭から格納するプロパティの名前リスト(リストの先頭から順にその名前を持つプロパティに値をセットしていく) public IEnumerable<string> ParameterNames => new[] { nameof(Aparam), nameof(Bparam) }; public string? Aparam { get; set; } public string? Bparam { get; set; } public string? A { get; set; } public string? B { get; set; } public string? C { get; set; } public bool D { get; set; } public bool E { get; set; } }
public static T Parse<T>(string[] args) where T : IArguments, new() { var argumentsType = typeof(T); List<string> parameters = new(); Dictionary<PropertyInfo, string> options = new(); Dictionary<PropertyInfo, bool> switchs = new(); PropertyInfo? optionProperty = default; foreach (var arg in args.Select(arg => arg ?? string.Empty)) { { var pre = arg.First(); if (new[] { '-', '/' }.Contains(pre)) { var optionName = new string(arg.SkipWhile(c => c == pre).ToArray()); optionProperty = argumentsType.GetProperty(optionName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (optionProperty == null) continue; if (optionProperty.PropertyType == typeof(bool)) { switchs.Add(optionProperty, true); optionProperty = null; continue; } options.Add(optionProperty, ""); continue; } } if (optionProperty == null) { parameters.Add(arg); continue; } options[optionProperty] = arg; optionProperty = null; } var arguments = new T(); foreach (var parameter in Enumerable.Zip(arguments.ParameterNames, parameters)) argumentsType.GetProperty(parameter.First)?.SetValue(arguments, parameter.Second); foreach (var option in options) option.Key.SetValue(arguments, option.Value); foreach (var @switch in switchs) @switch.Key.SetValue(arguments, @switch.Value); return arguments; }
<実行ファイル名> aparam -a aopt -d -b bopt bparam /c copt
var parsed = ArgumentsBuilder.Parse<Arguments>(args); // パラメーター parsed.Aparam; // aparam parsed.Bparam; // bparam; // オプション parsed.A; // aopt parsed.B; // bopt parsed.C; // copt // スイッチ parsed.D; // true parsed.E; // false
雑に作るコンソールアプリ用のコピペ元としてはこんな感じでいいでしょうかね。
ただ、このコード少し言語仕様的に古いと思うのでまたアップデートしたいです。
PropertyInfo を取得する際、大文字小文字を区別して取得したい方は非常にまれだと思います。
大多数の大文字小文字を区別せずに PropertyInfo を取得したい方向けのお話です。ちなみに私も大文字小文字を区別せずに PropertyInfo を取得したい派です。
BindingFlags.IgnoreCase が大文字小文字を区別せずに取得するフラグです。しかし、次のコードは期待通りに動作しません。
var property = type.GetProperty(propertyName, BindingFlags.IgnoreCase);
// property はnull。PropertyInfo が取得できていない。
期待通りでないこの挙動が仕様です。
var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
何度も失敗すると思います、というか私が何度も失敗しています。次失敗したときに検索してコピペしましょう。