- 投稿日:2020-05-26T23:56:56+09:00
【C#】 ファイルの作成・書き込みのやり方。
自己紹介
こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。ファイル作成
例えば、a.txtというファイルを作りたい場合以下のように書くことができます。
MakeFile_a.csusing System; using System.IO; class Test { public static void Main() { using (FileStream fs = File.Create("./a.txt")) ; } }また、ファイルを他から参照することも可能です。
MakeFile_a2.cspublic static void Main() { string path = "./a.txt" using (FileStream fs = File.Create(path)) ; }ファイル書き込み
ファイルの書き込みは、上書き保存タイプと追記タイプがあります。
上書き保存
overwrite.csusing System; using System.IO; using System.Text; class Test { static void Main() { string arg = "hello"; Encoding enc = Encoding.GetEncoding("Shift_JIS"); using (StreamWriter writer = new StreamWriter("./a.txt", false, enc)) { writer.WriteLine(arg); } } }追記タイプ
変わるところは
using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
です。add.csusing System; using System.IO; using System.Text; class Sample { static void Main() { string arg = "world"; Encoding enc = Encoding.GetEncoding("Shift_JIS"); using (StreamWriter writer = new StreamWriter("./a.txt", true, enc)) { writer.WriteLine(arg); } } }いかがでしょうか?
いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。
- 投稿日:2020-05-26T23:56:56+09:00
C# ファイルの作成・読み込み・書き込みのやり方。
自己紹介
こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。ファイル作成
例えば、a.txtというファイルを作りたい場合以下のように書くことができます。
MakeFile_a.csusing System; using System.IO; class Test { public static void Main() { using (FileStream fs = File.Create("./a.txt")) ; } }また、ファイルを他から参照することも可能です。
MakeFile_a2.cspublic static void Main() { string path = "./a.txt" using (FileStream fs = File.Create(path)) ; }ファイル読み込み
後日更新
ファイル書き込み
後日更新
いかがでしょうか?
いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。
- 投稿日:2020-05-26T23:56:56+09:00
C# ファイルの作成・書き込みのやり方。
自己紹介
こんにちは。tetraです。
最近C#の学習を始めました。C#歴2ヶ月目の新卒エンジニアです。
仕事中にファイルの扱い方が分からなくて時間をかなり使ったのでチートシートとして作成します。ファイル作成
例えば、a.txtというファイルを作りたい場合以下のように書くことができます。
MakeFile_a.csusing System; using System.IO; class Test { public static void Main() { using (FileStream fs = File.Create("./a.txt")) ; } }また、ファイルを他から参照することも可能です。
MakeFile_a2.cspublic static void Main() { string path = "./a.txt" using (FileStream fs = File.Create(path)) ; }ファイル書き込み
ファイルの書き込みは、上書き保存タイプと追記タイプがあります。
上書き保存
overwrite.csusing System; using System.IO; using System.Text; class Test { static void Main() { string arg = "hello"; Encoding enc = Encoding.GetEncoding("Shift_JIS"); using (StreamWriter writer = new StreamWriter("./a.txt", false, enc)) { writer.WriteLine(arg); } } }追記タイプ
変わるところは
using (StreamWriter writer = new StreamWriter("./a.txt", true, enc))
です。add.csusing System; using System.IO; using System.Text; class Sample { static void Main() { string arg = "world"; Encoding enc = Encoding.GetEncoding("Shift_JIS"); using (StreamWriter writer = new StreamWriter("./a.txt", true, enc)) { writer.WriteLine(arg); } } }いかがでしょうか?
いかがでしょうか。経験が浅いため至らぬところがまだまだあります。
もし間違い等に気がつきましたら、コメント又はtetraまでお知らせください。
- 投稿日:2020-05-26T22:23:04+09:00
C# ストップウォッチクラス
クラス
using System;
using System.Diagnostics;
namespace myStpWtch
{
public class CStopWatch
{
public Stopwatch myStopWatch = new Stopwatch(); //時間経過をはかるためのクラス
public Boolean sw = false; //スイッチOFF状態public void Start() { myStopWatch.Start(); //計測開始 sw = true; //スイッチON状態 } public void Stop() { myStopWatch.Stop(); //計測停止 sw = false; //スイッチOFF状態 } public void Reset() { myStopWatch.Reset(); } }}
BODY
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;using System.Diagnostics;
using System.Windows.Forms.DataVisualization.Charting;using myStpWtch;
namespace myCamera
{
public partial class Form1 : Form
{
CStopWatch stpwatch = new CStopWatch();public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { //=========================================== // StopWatch //=========================================== //=========================================== // CameraTab //=========================================== //=========================================== // MouseTab //=========================================== } private void btnStart_Click(object sender, EventArgs e) { if (stpwatch.sw == false) // OFF状態なら { stpwatch.Start(); //時間計測開始 timer1.Start(); //時間表示 btnReset.Enabled = false; //リセットボタン不許可 btnStart.Text = "Stop"; //ストップボタンに変更 } else { stpwatch.Stop(); //時間計測開始 timer1.Stop(); //時間固定 btnReset.Enabled = true; //リセットボタン不許可 btnStart.Text = "Start"; //ストップボタンに変更 } } private void btnReset_Click(object sender, EventArgs e) { stpwatch.Reset(); //初期化 txtStpWtch.Text = stpwatch.myStopWatch.Elapsed.ToString(); } private void timer1_Tick(object sender, EventArgs e) { //label1にスタートから現在までの時間を表示させる txtStpWtch.Text = stpwatch.myStopWatch.Elapsed.ToString(); } }}
- 投稿日:2020-05-26T21:38:50+09:00
Pidginを使ったPEGパーザ構築の手習い
前書き
軽量・軽快・拡張性バッチという謳い文句の benjamin-hodgson/Pidginというパーザコンビネータを通してPEGパーザの作成を学習した記録(継続中)
SQL:2003のBNFを題材として選んだ。準備
毎度の如く、
NUnit
のプロジェクトを作成して振る舞いを確認。
Pidgin
の導入は、Nugetの手順に従って参照に追加した。& dotnet add <<>MyProject> package Pidgin --version 2.3.0リテラルのパーザ
最初のマイルストーンとして、
select 2 * 3 as y from hogeを解析できるようにする。
そのためにselect句
のリテラルの解析を最初の目標とした。Booleanリテラル
SQLは三値論理なので、TRUE / FALSE / UNKNOWNの3つの値が存在する。
using NUnit.Framework; using Pidgin; public class ParseSelectTest { [Test] public void _Bool値のパーズ() { var bool_p = Parser.OneOf(Parser.Try(Parser.CIString("TRUE")), Parser.Try(Parser.CIString("FALSE")), Parser.Try(Parser.CIString("UNKNOWN"))); var result1_1 = bool_p.Parse("TRUE"); Assert.That(result1_1.Success, Is.True, "[1.1]パーズは成功しなければならない"); Assert.That(result1_1.Value, Is.EqualTo("TRUE"), "[1.1]取り出されたリテラル"); var result1_2 = bool_p.Parse("true"); Assert.That(result1_2.Success, Is.True, "[1.2]パーズは成功しなければならない"); Assert.That(result1_2.Value, Is.EqualTo("true"), "[1.2]取り出されたリテラル"); var result2 = bool_p.Parse("false"); Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない"); Assert.That(result2.Value, Is.EqualTo("false"), "[2]取り出されたリテラル"); var result3 = bool_p.Parse("unknown"); Assert.That(result3.Success, Is.True, "[3]パーズは成功しなければならない"); Assert.That(result3.Value, Is.EqualTo("unknown"), "[3]取り出されたリテラル"); } }整数リテラル
手戻りを書いていないためか、消費できたところまででOKとしてしまうらしい。
public class ParseSelectTest { // (snip) [Test] public void _数字リテラルのパーズ_符号なし整数の場合() { var unum_p = Parser.Digit.AtLeastOnceString(); var result1 = unum_p.Parse("8 "); Assert.That(result1.Success, Is.True, "[1]パーズは成功しなければならない"); Assert.That(result1.Value, Is.EqualTo("8"), "[1]取り出された数字リテラル"); var result2 = unum_p.Parse("9876543210987"); Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない"); Assert.That(result2.Value, Is.EqualTo("9876543210987"), "[2]取り出された数字リテラル"); var result3 = unum_p.Parse("9876543XYZ"); Assert.That(result3.Success, Is.True); Assert.That(result3.Value, Is.EqualTo("9876543"), "[3]取り出された数字リテラル"); } }小数リテラル
符号なし整数との共存に苦労した。
public class ParseSelectTest { // (snip) [Test] public void _数字リテラルのパーズ_小数の場合() { var unum_p = Parser.Digit.AtLeastOnceString(); var frac_num_p = Parser.Char('.').Then(unum_p, (left, right) => left + right); var result4 = frac_num_p.Parse(".654"); Assert.That(result4.Success, Is.True, "[4]パーズは成功しなければならない"); Assert.That(result4.Value, Is.EqualTo(".654"), "[4]取り出された数字リテラル"); var decimal_p = unum_p .Then(frac_num_p.Optional(), (left, right) => right.HasValue ? left + right.Value : left) ; var result5 = decimal_p.Parse("1234.567"); Assert.That(result5.Success, Is.True, "[5]パーズは成功しなければならない"); Assert.That(result5.Value, Is.EqualTo("1234.567"), "[5]取り出された数字リテラル"); var result6 = decimal_p.Parse("567"); Assert.That(result6.Success, Is.True, "[6]パーズは成功しなければならない"); Assert.That(result6.Value, Is.EqualTo("567"), "[6]取り出された数字リテラル"); var decimal_p2 = decimal_p.Then(Parser<char>.End, (l, r) => l); var result7 = decimal_p2.Parse("5678UV"); Assert.That(result7.Success, Is.Not.True, "[7]パーズは失敗しなければならない"); Assert.That(result7.Error, Is.Not.Null, "[7]エラーあり"); Assert.That(result7.Error.ErrorPos.Col, Is.EqualTo(5), "数字ではないところで失敗"); } }科学表記の小数リテラル
小数の焼き直しでなんとかなった。
public class ParseSelectTest { // (snip) [Test] public void _数字リテラルのパーズ_小数の場合() { var unum_p = Parser.Digit.AtLeastOnceString(); var exp_part_p = Parser.CIChar('E').Then(unum_p, (l, r) => l + r); var result8 = exp_part_p.Parse("E31"); Assert.That(result8.Success, Is.True, "[8]パーズは成功しなければならない"); Assert.That(result8.Value, Is.EqualTo("E31"), "[8]取り出された数字リテラル"); var result8_2 = exp_part_p.Parse("e13"); Assert.That(result8_2.Success, Is.True, "[8_2]パーズは成功しなければならない"); Assert.That(result8_2.Value, Is.EqualTo("e13"), "[8_2]取り出された数字リテラル"); var exp_num_p = unum_p.Then(exp_part_p.Optional(), (l, r) => r.HasValue ? l + r.Value : l); var result9 = exp_num_p.Parse("1234E31"); Assert.That(result9.Success, Is.True, "[9]パーズは成功しなければならない"); Assert.That(result9.Value, Is.EqualTo("1234E31"), "[9]取り出された数字リテラル"); var result10 = exp_num_p.Parse("234"); Assert.That(result10.Success, Is.True, "[10]パーズは成功しなければならない"); Assert.That(result10.Value, Is.EqualTo("234"), "[10]取り出された数字リテラル"); } }任意の数字リテラル
ここまでの数字パーザを組み合わせただけ。
public class ParseSelectTest { // (snip) [Test] public void _数字のパーズ_Parserのチョイス() { var uint_p = Parser.Digit.AtLeastOnceString(); var frac_num_p = Parser.Char('.').Then(uint_p, (l, r) => l + r); var exact_num_p = uint_p.Then(frac_num_p, (l, r) => l + r); var exp_part_p = Parser.CIChar('E').Then(uint_p, (l, r) => l + r); var exp_num_p = uint_p.Then(exp_part_p.Optional(), (l, r) => r.HasValue ? l + r.Value : l); var unum_p = Parser.OneOf(Parser.Try(exact_num_p), Parser.Try(exp_num_p), uint_p); var result1_1 = uint_p.Parse("1234567890123"); Assert.That(result1_1.Success, Is.True, "[1.1]パーズは成功しなければならない"); Assert.That(result1_1.Value, Is.EqualTo("1234567890123"), "[1.1]取り出された数字リテラル"); var result1_2 = unum_p.Parse("1234567890123"); Assert.That(result1_2.Success, Is.True, "[1.2]パーズは成功しなければならない"); Assert.That(result1_2.Value, Is.EqualTo("1234567890123"), "[1.2]取り出された数字リテラル"); var result2 = unum_p.Parse("1234.567"); Assert.That(result2.Success, Is.True, "[2]パーズは成功しなければならない"); Assert.That(result2.Value, Is.EqualTo("1234.567"), "[2]取り出された数字リテラル"); var result3 = unum_p.Parse("1234E31"); Assert.That(result3.Success, Is.True, "[3]パーズは成功しなければならない"); Assert.That(result3.Value, Is.EqualTo("1234E31"), "[3]取り出された数字リテラル"); var sign_p = Parser.CIOneOf('+', '-'); var snum_p = sign_p.Optional().Then(unum_p, (l, r) => l.HasValue ? l.Value + r: r); var result4 = snum_p.Parse("1234567890123"); Assert.That(result4.Success, Is.True, "[4]パーズは成功しなければならない"); Assert.That(result4.Value, Is.EqualTo("1234567890123"), "[4]取り出された数字リテラル"); var result5 = snum_p.Parse("-98765432"); Assert.That(result5.Success, Is.True, "[5]パーズは成功しなければならない"); Assert.That(result5.Value, Is.EqualTo("-98765432"), "[5]取り出された数字リテラル"); var result6 = snum_p.Parse("+2224444"); Assert.That(result6.Success, Is.True, "[6]パーズは成功しなければならない"); Assert.That(result6.Value, Is.EqualTo("+2224444"), "[6]取り出された数字リテラル"); } }今日はここまで
余談
Pidgin
でググると、真っ先にインスタントメッセンジャーの方が引っかかるのでとてもググラビリティが低い。
あとQiitaのPidgin
タグがインスタントメッセンジャーの方のために作られた感があったので、混乱を避けるため泣く泣くタグから除外した。
- 投稿日:2020-05-26T20:38:56+09:00
[Roslyn] オブジェクトインスタンス化コード生成に必要な要素の調査(おまけ)
前書き
オブジェクトインスタンス化コード生成に必要な要素の調査で、オブジェクトインスタンス化に必要な最低限の要素を取得したが、せっかくなので取得結果を用いて、実際にインスタンス化のコード生成をユニットテストを書いて確認したので投下しておく。
確認内容
生成するコードとして、
- Nullable + コンストラクタをもつ構造体
- プロパティでの初期化が必要な構造体
- IEnumerable + 公開フィールドでの初期化が必要な構造体
の3パターンについて、これらの型を戻りとするメソッドを持つインターフェースでもって確認した。
意味解析の結果を取得する際、ASTを構築する必要があるため、そのあたりの諸々ののコードを静的クラス
SyntaxGeneratorHelper
として用意した(Roslynによるインターフェースの実装クラスの構築で作ったコードを叩き台にして切り出しただけ)。public static class SyntaxGeneratorHelper { public static UsingDirectiveSyntax ToUsingDirective(string inUsing) { return SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(inUsing).WithLeadingTrivia(SyntaxFactory.Space)) .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed) ; } public static CSharpCompilation CreateCompilation(SyntaxTree inDaoAST, SyntaxTree inEntityAST) { var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); var opts = new CSharpCompilationOptions( outputKind: OutputKind.DynamicallyLinkedLibrary ); return CSharpCompilation.Create("autoGen", syntaxTrees: new[] { inDaoAST, inEntityAST }, references: new[] { AssemblyMetadata.CreateFromFile(typeof(object).Assembly.Location).GetReference(), MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")), MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")), }, options: opts ); } } }コード生成についてのユニットテスト
以下ソースコード生成を行うソースコード
using NUnit.Framework; using System; using System.IO; using System.Collections.Immutable; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using GenSyntaxTestHelpers; using SemSymbols; namespace GenTypeInitializationTests { public class GenTypeInitializationTest { [SetUp] public void Setup() { } private static readonly string IntfSource1 = @" namespace SemModels { public interface IColorDao1 { ColorData? FindById(int id); } } "; private static readonly string EntitySource1 = @" namespace SemModels { public readonly struct ColorData { public int Id { get; } public string Name { get; } public int Red { get; } public int Green { get; } public int Blue { get; } public ColorData(int id, string name, int red = default, int green = default, int blue = default) => (Id, Name, Red, Green, Blue) = (id, name, red, green, blue); } } "; private static readonly string IntfSource2 = @" namespace SemModels { public interface IColorDao2 { ColorDataMut FindById(int id); } } "; private static readonly string EntitySource2 = @" namespace SemModels { public struct ColorDataMut { public int Id { get; set; } public int Code { get => this.Id; } public string Name { get; set; } public int Red { get; set; } public int Green { get; set; } public int Blue { get; set; } } } "; private static readonly string IntfSource3 = @" using System.Collections.Generic; namespace SemModels { public interface IColorDao3 { IEnumerable<ColorDataMut2> FindAll(); } } "; private static readonly string EntitySource3 = @" namespace SemModels { public struct ColorDataMut2 { public int id; public string name; public int red; public int green; public int blue; } } "; [Test] public void _メソッドの実装_Nullableな戻り値_コンストラクタが適用される場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource1); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource1); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var implTree = SyntaxFactory.ParseSyntaxTree(@" namespace SemModelsImpl { public class ColorDaoImpl: IColorDao1 { } } "); var classTree = implTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<ClassDeclarationSyntax>() .First() ; var method = intfTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<MethodDeclarationSyntax>() .First() ; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(method.ReturnType); SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx); var parameters = ctx.Constructors[0].Symbols; var arg = method.ParameterList.Parameters[0].Identifier.Text; var implMethod = method .WithSemicolonToken(default) .WithLeadingTrivia(SyntaxFactory.Space) .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens()) .WithBody( SyntaxFactory.Block( SyntaxFactory.ReturnStatement( SyntaxFactory.ObjectCreationExpression( SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space), SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList<ArgumentSyntax>(new[] { SyntaxFactory.Argument( SyntaxFactory.NameColon(parameters[0].Name), SyntaxFactory.Token(default), SyntaxFactory.IdentifierName(arg) ), SyntaxFactory.Argument( SyntaxFactory.NameColon(parameters[1].Name), SyntaxFactory.Token(default), SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("cyan")) ), SyntaxFactory.Argument( SyntaxFactory.NameColon(parameters[2].Name), SyntaxFactory.Token(default), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)) ), SyntaxFactory.Argument( SyntaxFactory.NameColon(parameters[3].Name), SyntaxFactory.Token(default), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ), SyntaxFactory.Argument( SyntaxFactory.NameColon(parameters[4].Name), SyntaxFactory.Token(default), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ), })), null ) .WithLeadingTrivia(SyntaxFactory.Space) ) ) ) ; classTree = classTree.AddMembers(new[] { implMethod }); var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0]; var usings = new[] { "SemModels" }; var newUnit = SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls())) .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList()) ; var emitResult = this.EmitGenUnit("ConstructorEntityDao", newUnit, asm => { Assert.That(asm.GetTypes().Length, Is.EqualTo(1), "生成されたクラス数"); var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray(); Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl"), "生成された型名"); var instance = (SemModels.IColorDao1)asm.CreateInstance("SemModelsImpl.ColorDaoImpl"); var data = instance.FindById(4); Assert.That(data, Is.InstanceOf<System.Nullable<SemModels.ColorData>>(), "目的の値が生成されていること"); Assert.That(data.HasValue, Is.True, "内包する型のインスタンスが生成されていること"); Assert.That(data.Value.Id, Is.EqualTo(4), "渡したIdと一致していること"); Assert.That(data.Value.Name, Is.EqualTo("cyan"), "色名"); Assert.That(data.Value.Red, Is.EqualTo(0), "赤成分"); Assert.That(data.Value.Green, Is.EqualTo(255), "緑成分"); Assert.That(data.Value.Blue, Is.EqualTo(255), "青成分"); }); foreach (var d in emitResult.Diagnostics) { TestContext.Progress.WriteLine(d); } Assert.That(emitResult.Success, Is.True, "コンパイル結果"); } [Test] public void _メソッドの実装_Nullableな戻り値_コプロパティによる初期化が適用される場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource2); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource2); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var implTree = SyntaxFactory.ParseSyntaxTree(@" namespace SemModelsImpl { public class ColorDaoImpl2: IColorDao2 { } } "); var classTree = implTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<ClassDeclarationSyntax>() .First() ; var method = intfTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<MethodDeclarationSyntax>() .First() ; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(method.ReturnType); SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx); var props = ctx.PropertyTypeVars.Symbols; var arg = method.ParameterList.Parameters[0].Identifier.Text; var implMethod = method .WithSemicolonToken(default) .WithLeadingTrivia(SyntaxFactory.Space) .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens()) .WithBody( SyntaxFactory.Block( SyntaxFactory.ReturnStatement( SyntaxFactory.ObjectCreationExpression( SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space), SyntaxFactory.ArgumentList(), SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression, SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] { SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(props[0].Name), SyntaxFactory.IdentifierName(arg) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(props[1].Name), SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("magenta")) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(props[2].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(props[3].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(props[4].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ) }) ) ) .WithLeadingTrivia(SyntaxFactory.Space) ) ) ) ; classTree = classTree.AddMembers(new[] { implMethod }); var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0]; var usings = new[] { "SemModels" }; var newUnit = SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls())) .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList()) ; var emitResult = this.EmitGenUnit("PropertyEntityDao", newUnit, asm => { Assert.That(asm.GetTypes().Length, Is.EqualTo(1), "生成されたクラス数"); var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray(); Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl2"), "生成された型名"); var instance = (SemModels.IColorDao2)asm.CreateInstance("SemModelsImpl.ColorDaoImpl2"); var data = instance.FindById(5); Assert.That(data, Is.InstanceOf<SemModels.ColorDataMut>(), "目的の値が生成されていること"); Assert.That(data.Id, Is.EqualTo(5), "渡したIdと一致していること"); Assert.That(data.Name, Is.EqualTo("magenta"), "色名"); Assert.That(data.Red, Is.EqualTo(255), "赤成分"); Assert.That(data.Green, Is.EqualTo(0), "緑成分"); Assert.That(data.Blue, Is.EqualTo(255), "青成分"); }); foreach (var d in emitResult.Diagnostics) { TestContext.Progress.WriteLine(d); } Assert.That(emitResult.Success, Is.True, "コンパイル結果"); } [Test] public void _メソッドの実装_Nullableな戻り値_公開フィールドによる初期化が適用される場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource3); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource3); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var implTree = SyntaxFactory.ParseSyntaxTree(@" namespace SemModelsImpl { public class ColorDaoImpl3: IColorDao3 { } } "); var classTree = implTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<ClassDeclarationSyntax>() .First() ; var method = intfTree.GetCompilationUnitRoot() .DescendantNodes() .OfType<MethodDeclarationSyntax>() .First() ; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(method.ReturnType); SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx); var fields = ctx.FieldTypeVars.Symbols; var implMethod = method .WithSemicolonToken(default) .WithLeadingTrivia(SyntaxFactory.Space) .WithModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword).AsTokens()) .WithBody( SyntaxFactory.Block( SyntaxFactory.YieldStatement( SyntaxKind.YieldReturnStatement, SyntaxFactory.Token(SyntaxKind.YieldKeyword), SyntaxFactory.Token(SyntaxKind.ReturnKeyword).WithLeadingTrivia(SyntaxFactory.Space), SyntaxFactory.ObjectCreationExpression( SyntaxFactory.IdentifierName(ctx.NamedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)).WithLeadingTrivia(SyntaxFactory.Space), SyntaxFactory.ArgumentList(), SyntaxFactory.InitializerExpression(SyntaxKind.ObjectInitializerExpression, SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] { SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(fields[0].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(5)) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(fields[1].Name), SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("yellow")) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(fields[2].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(fields[3].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(255)) ), SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName(fields[4].Name), SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0)) ) }) ) ) .WithLeadingTrivia(SyntaxFactory.Space), SyntaxFactory.Token(SyntaxKind.SemicolonToken) ) ) ) ; classTree = classTree.AddMembers(new[] { implMethod }); var ns = (NamespaceDeclarationSyntax)implTree.GetCompilationUnitRoot().Members[0]; var usings = new[] { "System.Collections.Generic", "SemModels" }; var newUnit = SyntaxFactory.CompilationUnit().AddMembers(ns.WithLeadingTrivia(null).WithMembers(classTree.AsMemberDecls())) .WithUsings(usings.Select(SyntaxGeneratorHelper.ToUsingDirective).ToSyntaxList()) ; var emitResult = this.EmitGenUnit("FieldEntityDao", newUnit, asm => { Assert.That(asm.GetTypes().Length, Is.EqualTo(2), "生成されたクラス数(自動生成されたIteratorを含むため)"); var typeNames = asm.GetTypes().Select(t => t.FullName).ToArray(); Assert.That(typeNames, Does.Contain("SemModelsImpl.ColorDaoImpl3"), "生成された型名"); var instance = (SemModels.IColorDao3)asm.CreateInstance("SemModelsImpl.ColorDaoImpl3"); var seq = instance.FindAll(); Assert.That(seq, Is.InstanceOf<IEnumerable<SemModels.ColorDataMut2>>(), "目的の値が生成されていること"); Assert.That(seq.Any(), Is.True, "内包する型のインスタンスが生成されていること"); Assert.That(seq.Count(), Is.EqualTo(1), "要素数"); var data = seq.First(); Assert.That(data.id, Is.EqualTo(5), "渡したIdと一致していること"); Assert.That(data.name, Is.EqualTo("yellow"), "色名"); Assert.That(data.red, Is.EqualTo(255), "赤成分"); Assert.That(data.green, Is.EqualTo(255), "緑成分"); Assert.That(data.blue, Is.EqualTo(0), "青成分"); }); foreach (var d in emitResult.Diagnostics) { TestContext.Progress.WriteLine(d); } Assert.That(emitResult.Success, Is.True, "コンパイル結果"); } private EmitResult EmitGenUnit(string inAsmName, CompilationUnitSyntax inGenTree, Action<Assembly> inCallback) { using(var stream = new MemoryStream()) { var dotnetCoreDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); var opts = new CSharpCompilationOptions( outputKind: OutputKind.DynamicallyLinkedLibrary ); var newCompilation = CSharpCompilation.Create(inAsmName, syntaxTrees: new[] { SyntaxFactory.SyntaxTree(inGenTree) }, references: new[] { AssemblyMetadata.CreateFromFile(typeof(object).Assembly.Location).GetReference(), MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "netstandard.dll")), MetadataReference.CreateFromFile(Path.Combine(dotnetCoreDirectory, "System.Runtime.dll")), AssemblyMetadata.CreateFromFile(this.GetType().Assembly.Location).GetReference(), }, options: opts ); var emitResult = newCompilation.Emit(stream); if (emitResult.Success) { Assert.That(inCallback, Is.Not.Null); stream.Position = 0; var buf = new byte[stream.Length]; stream.Read(buf, 0, buf.Length); var asm = Assembly.Load(buf); inCallback(asm); } return emitResult; } } } } namespace SemModels { public readonly struct ColorData { public int Id { get; } public string Name { get; } public int Red { get; } public int Green { get; } public int Blue { get; } public ColorData(int id, string name, int red = default, int green = default, int blue = default) => (Id, Name, Red, Green, Blue) = (id, name, red, green, blue); } public interface IColorDao1 { ColorData? FindById(int id); } public struct ColorDataMut { public int Id { get; set; } public string Name { get; set; } public int Red { get; set; } public int Green { get; set; } public int Blue { get; set; } } public interface IColorDao2 { ColorDataMut FindById(int id); } public struct ColorDataMut2 { public int id; public string name; public int red; public int green; public int blue; } public interface IColorDao3 { IEnumerable<ColorDataMut2> FindAll(); } }追記
テストコードにコメントアウトしたゴミを拾い集めコード生成の手引きとしてまとめる
Roslyn
の@SyntaxFactory`はアホほどメソッドが生えててどれを使ったいいか結構迷う。その際の手引きとして、
var body = SyntaxFactory.ParseCompilationUnit("public SemModels.ColorData x(int id) { return new SemModels.ColorData() { Red = 128 }; }"); foreach (var n in body.DescendantNodesAndTokensAndSelf()) { TestContext.Progress.WriteLine($"{n.Kind().ToString().PadRight(30)}{n.ToFullString()}"); }のように文字列から構文木を実体化させ、コンソールに出力してみるのが近道かも。
(NUnitの場合、TestContext.Progress.WriteLine
メソッドで、出力してくれる)また、組み立てた構文木についても、
ToFullString()
メソッドを呼ぶことで文字列に戻すことができる(インデントはめちゃくちゃな可能性あり)TestContext.Progress.WriteLine(newUnit.ToFullString());
- 投稿日:2020-05-26T19:48:47+09:00
【C#】LINQでJOINは、メソッド構文よりクエリ構文
LINQで複数テーブルをJoinする場合は、メソッド構文ではなくクエリ構文で記述した方が可読性は高い
複数エンティティをメソッド構文で内部結合すると可読性が低い。
複数エンティティ結合する必要がある場合は、クエリ構文で記述した方が可読性は上がる。メソッド構文とクエリ構文で比較
4テーブル結合した場合のメソッド構文とクエリ構文の例
メソッド構文の例
var query = db.Parent .Join(db.ChildA, x => x.id, y => y.id, (p, a) => new { p, a }) .Join(db.ChildB, x => x.p.id, y => y.id, (grp, b) => new { grp, b }) .Join(db.ChildC, x => x.grp.p.id, y => y.id, (grp, c) => new { grp, c }) .Join(db.ChildD, x => x.grp.grp.p.id, y => y.id, (grp, d) => new { grp, d }) .Select(x => new { x.grp.grp.grp.p, x.grp.grp.grp.a, x.grp.grp.b, x.grp.c, x.d });クエリ構文の例
var query = from p in db.Parent join a in db.ChildA on p.id equals a.id join b in db.ChildB on p.id equals b.id join c in db.ChildC on p.id equals c.id join d in db.ChildD on p.id equals d.id select new {p,a,b,c,d};
- 投稿日:2020-05-26T12:19:46+09:00
IE/Edgeに保存されているログインデータを出力してみる
はじめに
Chromeに保存されているパスワードを解読してみる
に引き続き、今回はInternet ExplorerとMicrosoft EdgeがどのようにWebサイトのログインデータを保存しているのか調べてみました。IE/Edgeのログインデータの保存方法
調べてみたところ、ログインデータは「Windows Vaults」と呼ばれる特殊なフォルダに保存されていることがわかりました。
具体的には、C:\Windows\System32\vaultcli.dllに含まれているWindows VaultsのAPIを使用してアクセスする仕組みになっているようです。ログインデータを読み込んでみる
ログインデータを読み込んで出力するプログラムは以下のようになります。
using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; namespace FetchVaultCredential { public static class Program { public enum VAULT_ELEMENT_TYPE : int { Undefined = -1, Boolean = 0, Short = 1, UnsignedShort = 2, Int = 3, UnsignedInt = 4, Double = 5, Guid = 6, String = 7, ByteArray = 8, TimeStamp = 9, ProtectedArray = 10, Attribute = 11, Sid = 12, Last = 13 } public enum VAULT_SCHEMA_ELEMENT_ID : int { Illegal = 0, Resource = 1, Identity = 2, Authenticator = 3, Tag = 4, PackageSid = 5, AppStart = 100, AppEnd = 10000 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct VAULT_ITEM_WIN8 { public Guid SchemaId; public IntPtr pszCredentialFriendlyName; public IntPtr pResourceElement; public IntPtr pIdentityElement; public IntPtr pAuthenticatorElement; public IntPtr pPackageSid; public ulong LastModified; public int dwFlags; public int dwPropertiesCount; public IntPtr pPropertyElements; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct VAULT_ITEM_WIN7 { public Guid SchemaId; public IntPtr pszCredentialFriendlyName; public IntPtr pResourceElement; public IntPtr pIdentityElement; public IntPtr pAuthenticatorElement; public ulong LastModified; public int dwFlags; public int dwPropertiesCount; public IntPtr pPropertyElements; } [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] public struct VAULT_ITEM_ELEMENT { [FieldOffset(0)] public VAULT_SCHEMA_ELEMENT_ID SchemaElementId; [FieldOffset(8)] public VAULT_ELEMENT_TYPE Type; } [DllImport("vaultcli.dll")] public extern static int VaultOpenVault(ref Guid vaultGuid, int offset, ref IntPtr vaultHandle); [DllImport("vaultcli.dll")] public extern static int VaultCloseVault(ref IntPtr vaultHandle); [DllImport("vaultcli.dll")] public extern static int VaultFree(ref IntPtr vaultHandle); [DllImport("vaultcli.dll")] public extern static int VaultEnumerateVaults(int offset, ref int vaultCount, ref IntPtr vaultGuid); [DllImport("vaultcli.dll")] public extern static int VaultEnumerateItems(IntPtr vaultHandle, int chunkSize, ref int vaultItemCount, ref IntPtr vaultItem); [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] public extern static int VaultGetItem_WIN8(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr pPackageSid, IntPtr zero, int arg6, ref IntPtr passwordVaultPtr); [DllImport("vaultcli.dll", EntryPoint = "VaultGetItem")] public extern static int VaultGetItem_WIN7(IntPtr vaultHandle, ref Guid schemaId, IntPtr pResourceElement, IntPtr pIdentityElement, IntPtr zero, int arg5, ref IntPtr passwordVaultPtr); public static void GetLogins() { // Windows Vaultsをチェック var OSVersion = Environment.OSVersion.Version; var OSMajor = OSVersion.Major; var OSMinor = OSVersion.Minor; Type VAULT_ITEM; if (OSMajor >= 6 && OSMinor >= 2) { // Windows 8以降 VAULT_ITEM = typeof(VAULT_ITEM_WIN8); } else { // Windows 7以前 VAULT_ITEM = typeof(VAULT_ITEM_WIN7); } // VAULT_ITEM_ELEMENTからItemValueを取り出す object GetVaultElementValue(IntPtr vaultElementPtr) { object results; object partialElement = Marshal.PtrToStructure(vaultElementPtr, typeof(VAULT_ITEM_ELEMENT)); FieldInfo partialElementInfo = partialElement.GetType().GetField("Type"); var partialElementType = partialElementInfo.GetValue(partialElement); IntPtr elementPtr = (IntPtr)(vaultElementPtr.ToInt64() + 16); switch ((int)partialElementType) { case 7: // String(パスワード) IntPtr StringPtr = Marshal.ReadIntPtr(elementPtr); results = Marshal.PtrToStringUni(StringPtr); break; case 0: // bool results = Marshal.ReadByte(elementPtr); results = (bool)results; break; case 1: // Short results = Marshal.ReadInt16(elementPtr); break; case 2: // Unsigned Short results = Marshal.ReadInt16(elementPtr); break; case 3: // Int results = Marshal.ReadInt32(elementPtr); break; case 4: // Unsigned Int results = Marshal.ReadInt32(elementPtr); break; case 5: // Double results = Marshal.PtrToStructure(elementPtr, typeof(Double)); break; case 6: // GUID results = Marshal.PtrToStructure(elementPtr, typeof(Guid)); break; case 12: // セキュリティ識別子 IntPtr sidPtr = Marshal.ReadIntPtr(elementPtr); var sidObject = new System.Security.Principal.SecurityIdentifier(sidPtr); results = sidObject.Value; break; default: // VAULT_ELEMENT_TYPEが実装されていないので無視 results = null; break; } return results; } int vaultCount = 0; // VaultのGUIDポインタ IntPtr vaultGuidPtr = IntPtr.Zero; // 全てのVaultを取得 var result = VaultEnumerateVaults(0, ref vaultCount, ref vaultGuidPtr); if (result != 0) { throw new Exception("Vaultを取得できません。(0x" + result.ToString() + ")"); } // GUID対応表 IntPtr guidAddress = vaultGuidPtr; Dictionary<Guid, string> vaultSchema = new Dictionary<Guid, string>(); vaultSchema.Add(new Guid("2F1A6504-0641-44CF-8BB5-3612D865F2E5"), "Windows Secure Note"); vaultSchema.Add(new Guid("3CCD5499-87A8-4B10-A215-608888DD3B55"), "Windows Web Password Credential"); vaultSchema.Add(new Guid("154E23D0-C644-4E6F-8CE6-5069272F999F"), "Windows Credential Picker Protector"); vaultSchema.Add(new Guid("4BF4C442-9B8A-41A0-B380-DD4A704DDB28"), "Web Credentials"); vaultSchema.Add(new Guid("77BC582B-F0A6-4E15-4E80-61736B6F3B29"), "Windows Credentials"); vaultSchema.Add(new Guid("E69D7838-91B5-4FC9-89D5-230D4D4CC2BC"), "Windows Domain Certificate Credential"); vaultSchema.Add(new Guid("3E0E35BE-1B77-43E7-B873-AED901B6275B"), "Windows Domain Password Credential"); vaultSchema.Add(new Guid("3C886FF3-2669-4AA2-A8FB-3F6759A77548"), "Windows Extended Credential"); vaultSchema.Add(new Guid("00000000-0000-0000-0000-000000000000"), null); for (int i = 0; i < vaultCount; i++) { // Vaultを開く object vaultGuidString = Marshal.PtrToStructure(guidAddress, typeof(Guid)); Guid vaultGuid = new Guid(vaultGuidString.ToString()); guidAddress = (IntPtr)(guidAddress.ToInt64() + Marshal.SizeOf(typeof(Guid))); IntPtr vaultHandle = IntPtr.Zero; string vaultType; if (vaultSchema.ContainsKey(vaultGuid)) { vaultType = vaultSchema[vaultGuid]; } else { vaultType = vaultGuid.ToString(); } result = VaultOpenVault(ref vaultGuid, 0, ref vaultHandle); if (result != 0) { throw new Exception(vaultType + "からVaultを開けませんでした。" + "(0x" + result.ToString() + ")"); } // Vault内のアイテムを列挙 int vaultItemCount = 0; IntPtr vaultItemPtr = IntPtr.Zero; result = VaultEnumerateItems(vaultHandle, 512, ref vaultItemCount, ref vaultItemPtr); if (result != 0) { throw new Exception(vaultType + "からのVaultの列挙に失敗しました。" + "(0x" + result.ToString() + ")"); } var structAddress = vaultItemPtr; if (vaultItemCount > 0) { for (int j = 1; j <= vaultItemCount; j++) { // Vaultの列挙を開始 var currentItem = Marshal.PtrToStructure(structAddress, VAULT_ITEM); structAddress = (IntPtr)(structAddress.ToInt64() + Marshal.SizeOf(VAULT_ITEM)); IntPtr passwordVaultItem = IntPtr.Zero; // フィールド情報の検索 FieldInfo schemaIdInfo = currentItem.GetType().GetField("SchemaId"); Guid schemaId = new Guid(schemaIdInfo.GetValue(currentItem).ToString()); FieldInfo pResourceElementInfo = currentItem.GetType().GetField("pResourceElement"); IntPtr pResourceElement = (IntPtr)pResourceElementInfo.GetValue(currentItem); FieldInfo pIdentityElementInfo = currentItem.GetType().GetField("pIdentityElement"); IntPtr pIdentityElement = (IntPtr)pIdentityElementInfo.GetValue(currentItem); FieldInfo dateTimeInfo = currentItem.GetType().GetField("LastModified"); ulong lastModified = (ulong)dateTimeInfo.GetValue(currentItem); IntPtr pPackageSid = IntPtr.Zero; if (OSMajor >= 6 && OSMinor >= 2) { // 新しいバージョンにはパッケージSIDがある FieldInfo pPackageSidInfo = currentItem.GetType().GetField("pPackageSid"); pPackageSid = (IntPtr)pPackageSidInfo.GetValue(currentItem); result = VaultGetItem_WIN8(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, pPackageSid, IntPtr.Zero, 0, ref passwordVaultItem); } else { result = VaultGetItem_WIN7(vaultHandle, ref schemaId, pResourceElement, pIdentityElement, IntPtr.Zero, 0, ref passwordVaultItem); } if (result != 0) { throw new Exception("Error occured while retrieving vault item. Error: 0x" + result.ToString()); } object passwordItem = Marshal.PtrToStructure(passwordVaultItem, VAULT_ITEM); FieldInfo pAuthenticatorElementInfo = passwordItem.GetType().GetField("pAuthenticatorElement"); IntPtr pAuthenticatorElement = (IntPtr)pAuthenticatorElementInfo.GetValue(passwordItem); // 認証情報を取得 object cred = GetVaultElementValue(pAuthenticatorElement); object packageSid = null; if (pPackageSid != IntPtr.Zero && pPackageSid != null) { packageSid = GetVaultElementValue(pPackageSid); } if (cred != null) // 取得に成功したデータを表示 { Console.WriteLine("Vault Type : {0}", vaultType); object resource = GetVaultElementValue(pResourceElement); if (resource != null) { Console.WriteLine("Resource : {0}", resource); } object identity = GetVaultElementValue(pIdentityElement); if (identity != null) { Console.WriteLine("Identity : {0}", identity); } if (packageSid != null) { Console.WriteLine("PacakgeSid : {0}", packageSid); } Console.WriteLine("Credential : {0}", cred); Console.WriteLine("LastModified : {0}", System.DateTime.FromFileTimeUtc((long)lastModified)); Console.WriteLine(); } } } } } public static void Main(string[] args) { GetLogins(); Console.ReadKey(true); } } }実行する際は、ビルド設定の「32 ビットを選ぶ」のチェックを外してください。
仕組みとしては、Windows VaultのAPIをDllImportで読み込み、それを使ってVaultの構造体を取得してデータを取り出しています。
取得したデータ自体に暗号化などはされていないようです。これを実行すると、以下のようにログインデータが出力されます。
Vault Type : Web Credentials Resource : https://login.microsoftonline.com/ Identity : foobar314159@yahoo.co.jp Credential : TekitounaPassword LastModified : 2020/05/23 15:50:49 Vault Type : Web Credentials Resource : https://accounts.google.com/ Identity : unknown141421356@gmail.com Credential : SecurePass123456 LastModified : 2020/05/26 11:52:57おわりに
という訳で、IE/Edgeに保存されているログインデータも取得できてしまいました。
実は、Windows Vaultsに関するAPIの仕様はMicrosoft公式からは公開されていません。
だから、Microsoftは特にログインデータに暗号化処理をしなかったのだと思います。
しかし、海外のハッカー達にとってWindows Vaultsの仕様を一から自力で解析することなど朝飯前だったようです。恐ろしいですね。参考文献
- SharpWeb/SharpEdge.cs at master · djhohnstein/SharpWeb · GitHub
- PowerSploit/Get-VaultCredential.ps1 at master · PowerShellMafia/PowerSploit · GitHub
- The Secrets of Internet Explorer Credentials
- Password Algorithms: Internet Explorer 10 (Windows Vault)
- Contraseñas de Internet Explorer y Microsoft Edge
- 投稿日:2020-05-26T03:21:48+09:00
【VRChat】 Udon開発する上での注意点【Unity】
はじめに
この記事は
VRChat
におけるUdon
およびUdonSharp
(U#
)を使った際の備忘録です。
これからUdon
を使い始める人のために書き連ねておきます。この記事は2020/5/26現在のVRChatを前提に書いています。
Udonとパフォーマンスチューニング
Udon
は実質、UnityのAPIをラップして呼び出しているだけにすぎません。
そのためUnityでプログラミングをするときと同じ様にパフォーマンスにもこだわる必要があります。プロファイラを見よう
Unityには標準で
Profiler
という機能が備わっています。
1フレーム単位でどのような処理が実行され、それにどれくらいの時間がかかっているかをチェックすることができます。詳しい使い方はこちらを参考にしてください。
- Profiler ウィンドウ
- 【Unity】CPUプロファイラでパフォーマンスを改善する 前編
- 【Unity】CPUプロファイラでパフォーマンスを改善する 後編
- 【Unite 2017 Tokyo】最適化をする前に覚えておきたい技術
なお、プロファイラで動作を監視すること自体がかなりの負荷となります。
プロファイラを使っている間はfpsがガタ落ちしますが、しょうがないと割り切ってください。
(普通のUnity開発なら回避策があるのですが、VRChat
だと仕様上どうしようもできないです)Raw Hierarchyで調査
プロファイラの表示を
Raw Hierarchy
に切り替えて時間がかかっている順にソートすると何がボトルネックか調査することができます。
とくにこのモードだとUdon VM
の中身の実行順も見ることが出来ます。
Udon
の仕様上、どのスクリプトであるか名前はわからないのですが、メソッド呼び出しの様子からどのスクリプトかあたりをつけることはできます。GCアロケートを避けよう
とくに負荷の原因となりやすいものは
GC Alloc
と表示されているものです。
これはUnity上で、プログラムを実行するために必要なメモリを確保する動作を表しています。
(GCアロケート
と呼ぶ)そしてこの確保したメモリですが、解放される瞬間に
VRChat
が一瞬フリーズしてしまいます。
(GC
(ガベージコレクタ)が実行される、と呼びます)
GC
が実行される頻度は少ないほどfpsに与える影響は小さくなります。
逆に高頻度でGC
が実行されると、体感できるレベル(ひどいと数十fps)で影響がでてきます。
そのためGC
の実行をさける、つまりGC Alloc
の頻度を下げる工夫が必要となります。
string
は避けよう
C#
の仕様上、string
(文字列)は定義するだけでかなりのGC Alloc
を引き起こします。
そのためUpdate()
で毎フレーム文字列を生成するなどしていると、パフォーマンスにかなりの悪影響を及ぼします。極力
string
は使わない、使うにしても必要なタイミングで必要なだけ生成する工夫が必要です。
U#
は別コンポーネントのメソッド呼び出しがコスト非常に便利な
UdonSharp
ですが、見えないところでコストがかかります。
それはUdonSharpBehaviour
から別のUdonSharpBehaviour
なオブジェクトのメソッドを呼びだす時です。たとえば、次のような
U#
スクリプトがあったとして。Runnerusing UdonSharp; using UnityEngine; namespace DebugTest { public class Runner : UdonSharpBehaviour { private Rigidbody _rigidbody; void Start() { _rigidbody = GetComponent<Rigidbody>(); } public Vector3 GetCurrentVelocity() { return _rigidbody.velocity; } } }Observerusing UdonSharp; using UnityEngine; namespace DebugTest { public class Observer : UdonSharpBehaviour { [SerializeField] private Runner _runner; private void Update() { // 取得するだけで何も使わない var velocity = _runner.GetCurrentVelocity(); } } }これの
Observer.cs
側をUdon Assembly
にトランスパイルした結果をみるとこうなっています。_update: PUSH, __0_const_intnl_SystemUInt32 # { # var velocity = _runner.GetCurrentVelocity(); PUSH, _runner PUSH, __0_const_intnl_SystemString EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SendCustomEvent__SystemString__SystemVoid" PUSH, _runner PUSH, __1_const_intnl_SystemString PUSH, __0_intnl_SystemObject EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject" PUSH, __0_intnl_SystemObject PUSH, __0_intnl_UnityEngineVector3 COPY PUSH, __0_intnl_UnityEngineVector3 PUSH, __0_velocity_Vector3 COPY PUSH, __0_intnl_returnTarget_UInt32 #Function epilogue COPY JUMP_INDIRECT, __0_intnl_returnTarget_UInt32注目して欲しいところは、他の
UdonSharpBehaviour
へのメソッド呼び出しがSendCustomEvent
とGetProgramVariable
に変換されているところです。
(メソッドに引数を渡すとSetProgramVariable
も追加される)そしてこの
SendCustomEvent
とGetProgramVariable
ですが、なぜかGC Alloc
します。ということで、
U#
を用いた場合、気軽にメソッド呼び出しを実行するとそれだけでGC Alloc
が発生します。
普通のUnity開発ではノーコストな操作が、U#
ではコストがかかる点はかなり罠な気がします。OnTriggerStay大暴走
UdonBehaviour
にはOnTriggerStay
が定義されています。
そのため「VRC_Pickup
+UdonBehaviour
なオブジェクト」を一箇所に大量にまとめて配置するとOnTriggerStay
が暴走します。数個程度なら問題ないですが、数十個レベルで一箇所にまとめると
fps
がガタ落ちするレベルで影響がでてきます。
アイテムを一箇所にまとめておいて擬似的な「無限湧き」を作るようなことはやめておきましょう。Udon Synced Variables役に立たない問題
結論からいうと
Udon Synced Variables
はパフォーマンスのために 「使わない」 が正解です。
Udon
にはUdon Synced Variables
という機能があります。
こちらは指定したプリミティブな変数をネットワークをまたいで同期する機能です。
(U#
でいうところの[UdonSynced]
)ですがこの
Udon Synced Variables
、挙動が結構ヤバイです。
Owner
は常時パラメータを相手に送信し続ける- 転送量が増えるとパケットロスして不着となる
- スループットがかなり低い
同期するオブジェクト、変数の数が増えると
Death Run Detected: dropped N events
というエラーが大量に出てきます。
これが発生してしまうと、変数同期の成功率が極端に下がってしまいます。そのため、
Udon Synced Variables
で大量のデータを同期することはまったくオススメできません。たとえば、オブジェクトの位置と姿勢(
Vector3
+Quaternion
)をUdon Synced Variables
で同期するのは止めたほうがいいでしょう。
私が試した場合ではオブジェクト数が20個を超えたあたりからパケロスが発生しました。
さらにVRChat
の通信にかなりの負荷をかけるためか、Player
の挙動までもが不安定になりました。ちなみに、この仕様ではほぼ使い物にならないのでフィードバック報告済みではあります。
補足: Udon Synced Variablesについての公式フォーラムでの報告
VRChat
のフォーラムのこちらの投稿では次のように報告されています。
- 2つの
Udon Synced Variables
な文字列をもつUdon Behaviour
をシーンにいくつか配置- 8個置いた程度ではパケロスはほぼゼロ
- 16個置くとパケロスが発生する
とのことなので、
Udon Synced Variables
を使う場合はオブジェクト数が少ない場合のみにした方が無難でしょう。文字列にエンコードして同期する、は高コスト
また、とある場所で「オブジェクトの状態を
string
にエンコードしてUdon Synced Variables
で同期する」という手法が提案されていました。
Udon Synced Variables
で配列が同期できないのを回避するために編み出された手法ですが、こちらかなりコストが高いです。
- 大量の
string
を生成することによるGC Alloc
- 長い文字列を常時伝送するネットワークへの負荷
そのため本当にどうしようもないときの最終手段としとっておいて、常用はしないほうが無難でしょう。
(とはいえどこれしか方法が無いならば使わざるを得ないのがUdon
のツライところなのですが…。)位置同期
オブジェクトの位置を同期する方法ですが、次の2とおり(実質1とおり)があります。
- A:
Udon Synced Variables
で位置姿勢を送る- B:
Udon Behaviour
のSynchronize Position
を使うAのパターンは前述の問題があるのでオススメできません。
ということで実質的にBの「Synchronize Position
」一択になります。この
Synchronize Position
はちゃんと差分同期してくれるため、大量にオブジェクトがあってもネットワークへの負荷は小さいです。Synchronize Positionの同期ズレ問題
Synchronize Position
は差分同期してくれるためネットワーク負荷は小さいのですが、大量にオブジェクトがある場合、後からワールドに参加した人には正しく位置と姿勢が同期されない場合があります。
こちらはワールドにいるプレイヤー数とオブジェクト数によりますが、「5人以上かつ20個くらいオブジェクトを動かした」あたりから発生してきます。原因はハッキリとはしていないのですが、どうも次の複数の問題が絡んでいるっぽいです。
- オブジェクトの
Owner
が新規参加した人に正しく同期されず、Master
がOwner
にみえる問題Owner
がオブジェクトの位置同期が完了しない問題前者についてはバグ報告済みですが、後者についてはいまいち挙動がつかめていないため報告していません。
Synchronize Positionの同期ズレ対策
対処療法として、次の対策をいれましょう。
実際にモノレールワールドで実施している対策がこれです。
触れていないPickupオブジェクトはすべてMasterが所有権をもつ
- オブジェクトを持っている間は持っている人に
Owner
を渡す- 手を離したら
Master
に所有権を返すようにする- 若干安定するが、それでもまだ同期ズレは起きる
強制的に位置を同期する仕組みをいれる
Master
側で同期対象のオブジェクトをすべて少しだけ位置と姿勢をズラす- 数秒後に元の位置姿勢に戻す
(同期ズレの発生をゼロにはできないので、同期ズレが起きる前提で対策した方が早い)かなりツラミがある仕組みですが、現状これくらいしか大量のオブジェクトを安定して同期する方法がありません。
まとめ
Udon
つらいし、UdonSharp
も結構ツライです。
それなりのUnity開発経験と、Unityでパフォーマンスチューニングをできるスキルが求められますね。
- 投稿日:2020-05-26T02:05:18+09:00
Blazor WebAssembly を触ってみる - その①環境を整える、サンプルを動かす
Blazor WebAssembly とは
Web ブラウザー内で .NET コードを実行する WebAssembly ベースの SPA フレームワークです。
Blazor で開発したアプリやその依存関係、.NET ランタイムがブラウザーにダウンロード、実行されるという仕組みです。
サーバーサイドだけではなく、クライアントサイドまで C# で開発できるようになるので、C# が使える方にとっては便利そうですね。
また、C# から JavaScript の呼び出すこともその逆もできますので、これまでの開発したリソースも活かせそうです。
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-webassemblyつい先日正式にリリースされたのでサンプルを動かすところまで試して見たいと思います。
https://www.publickey1.jp/blog/20/blazor_webassemblycnetwebmicrosoft_build_2020.html
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/試してみる
以下のドキュメントを元に試してみます。
最初の Blazor アプリをビルドする
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1ここから先は VS Code でデバッグ実行するところまでの手順ですが、手っ取り早く動かしたい方は以下のコマンドだけで十分です。
dotnet new blazorwasm -o BlazorApp1 cd BlazorApp1 dotnet run用意するもの
- .NET Core 3.1 以上の SDK (https://dotnet.microsoft.com/download/dotnet-core/3.1)
- Visual Studio Code (https://code.visualstudio.com/)
手順
1. テンプレートのインストール (.NET Core 3.1.300 よりも低いバージョンのときのみ)
コマンドプロンプトより dotnet --version を実行して 3.1.300 よりも低いバージョンが表示されたときは、以下のコマンドを実行して最新のテンプレートをインストールします。
Blazor WebAssembly の正式サポートは .NET Core SDK 3.1.300 からだからでしょう。dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-rc1.20223.42. VS Code の設定
Settings (Ctrl + ,) より debug.javascript.usePreview を True にします。
次に C# と JavaScript Debugger (Nightly) の Extension をインストールします。
3. テンプレートからアプリを作成
dotnet new blazorwasm -o WebApplication1中身はこんな感じです。ASP.NET Core っぽいですね。
4. 実行
Run -> Start Debugging よりデバッグ実行します。
初回実行時には VS Code のデバッグ設定のファイルである launch.json を作成するため、以下のような画面が表示されますので、.NET Core を選択します。(その後、もう一度 Start Debugging をクリックします)
.NET Core
DEBUG CONSOLE に以下のようなメッセージが表示されたら、ブラウザで http://localhost:5000 を開きます。
以下のように Hello, world! の画面が表示されたら成功です。
ディベロッパーツールを開いてみると、たしかに .NET Core のアセンブリがロードされているようです。
いろいろ気になりますが今回はここまで。
- 投稿日:2020-05-26T02:05:18+09:00
Blazor WebAssembly を触ってみる
Blazor WebAssembly とは
Web ブラウザー内で .NET コードを実行する WebAssembly ベースの SPA フレームワークです。
Blazor で開発したアプリやその依存関係、.NET ランタイムがブラウザーにダウンロード、実行されるという仕組みです。
サーバーサイドだけではなく、クライアントサイドまで C# で開発できるようになるので、C# が使える方にとっては便利そうですね。
また、C# から JavaScript の呼び出すこともその逆もできますので、これまでの開発したリソースも活かせそうです。
https://docs.microsoft.com/ja-jp/aspnet/core/blazor/?view=aspnetcore-3.1#blazor-webassemblyつい先日正式にリリースされたのでサンプルを動かすところまで試して見たいと思います。
https://www.publickey1.jp/blog/20/blazor_webassemblycnetwebmicrosoft_build_2020.html
https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/試してみる
以下のドキュメントを元に試してみます。
最初の Blazor アプリをビルドする
https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/build-your-first-blazor-app?view=aspnetcore-3.1ここから先は VS Code でデバッグ実行するところまでの手順ですが、手っ取り早く動かしたい方は以下のコマンドだけで十分です。
dotnet new blazorwasm -o BlazorApp1 cd BlazorApp1 dotnet run用意するもの
- .NET Core 3.1 以上の SDK (https://dotnet.microsoft.com/download/dotnet-core/3.1)
- Visual Studio Code (https://code.visualstudio.com/)
手順
1. テンプレートのインストール (.NET Core 3.1.300 よりも低いバージョンのときのみ)
コマンドプロンプトより dotnet --version を実行して 3.1.300 よりも低いバージョンが表示されたときは、以下のコマンドを実行して最新のテンプレートをインストールします。
Blazor WebAssembly の正式サポートは .NET Core SDK 3.1.300 からだからでしょう。dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-rc1.20223.42. VS Code の設定
Settings (Ctrl + ,) より debug.javascript.usePreview を True にします。
次に C# と JavaScript Debugger (Nightly) の Extension をインストールします。
3. テンプレートからアプリを作成
dotnet new blazorwasm -o WebApplication1中身はこんな感じです。ASP.NET Core っぽいですね。
4. 実行
Run -> Start Debugging よりデバッグ実行します。
初回実行時には VS Code のデバッグ設定のファイルである launch.json を作成するため、以下のような画面が表示されますので、.NET Core を選択します。(その後、もう一度 Start Debugging をクリックします)
.NET Core
DEBUG CONSOLE に以下のようなメッセージが表示されたら、ブラウザで http://localhost:5000 を開きます。
以下のように Hello, world! の画面が表示されたら成功です。
ディベロッパーツールを開いてみると、たしかに .NET Core のアセンブリがロードされているようです。
いろいろ気になりますが今回はここまで。
- 投稿日:2020-05-26T00:44:58+09:00
C#でFormにBingマップを表示する
はじめに
Windows Formsに触れたことがある人なら、きっと誰もがマップをFormに埋め込みたいと思うでしょう。
しかしWindows Formsには、残念ながらマップを表示するコントロールはありません。
しかし、CefSharpとBing Maps APIを組み合わせればそのような機能を実現できます。
今回はそのやり方を紹介します。HTMLファイルを作る
仕組みとしては、Bing Mapsを埋め込んだHTMLファイルをCefSharpで表示する形となります。
本当はGoogle Maps APIを使いたかったのですが、クレジットカード情報を登録する必要があるみたいで面倒だったので、今回はあきらめました。Bing Maps APIを使うにはMicrosoftのアカウントが必要です。持っていない方は登録しましょう。
持っている方はBing Maps APIのページにアクセスし、「Sign in」ボタンを押してサインインします。
サインインできたら、上のメニューの「My account」から「My keys」をクリックします。
「Create key」という画面が表示されたら、必要事項を記入し「Create」をクリックします。
するとキーの一覧が表示されるので、「Show key」を押せばキーが表示されます。
このキーは後で使うのでコピーしておいてください。それではHTMLを記述していきます。
<!DOCTYPE html> <html> <head> <title>Bing Maps API</title> <meta charset="utf-8" /> <script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=[キーを指定]&setLang=ja' async defer></script> <script type='text/javascript'> var map; function GetMap() { map = mapStart(35, 140, 10); } // Mapを初期化 // 緯度, 経度, 拡大率(1~20) function mapStart(lat, lon, num) { return new Microsoft.Maps.Map('#myMap', { center: new Microsoft.Maps.Location(lat, lon), // 表示モード(road:普通, aerial:航空写真) mapTypeId: Microsoft.Maps.MapTypeId.aerial, zoom: num }); } // ピンを立てる(緯度, 経度) function mapPushpin(lat, lon) { const location = new Microsoft.Maps.Location(lat, lon); const pin = new Microsoft.Maps.Pushpin(location, { color: "red", // ピンの色 draggable: true, // ドラッグできるか }); map.entities.push(pin); } </script> </head> <body style="margin:0;"> <div id="myMap" style="position:relative;width:100%;height:100%;"></div> </body> </html>[キーを指定]のところに先ほど取得したキーを指定してください。
これをC:\Maps.htmlとして保存します。CefSharpの導入
次に、Bingマップを表示するアプリのプロジェクトを作成しておきましょう。
作成したら、このプロジェクトに「CefSharp」というパッケージを導入します。
CefSharpはChromiumのC#向け実装です。「HTMLファイルを表示したいならWebBrowserコントロールを使えばいいのでは?」
と思うかもしれませんが、WebBrowserコントロールは内部に古いIEのエンジンを使っているので、エラーが頻発してまともに使えません。今回は、NuGetでCefSharp.WinFormsをインストールします。
インストールが完了したら、プロジェクトのフォルダにある設定ファイル(csprojファイル)をテキストエディタで開きます。
以下のように、<CefSharpAnyCpuSupport>true</CefSharpAnyCpuSupport>をPropertyGroupタグの中に追加して保存します。
次に、App.configファイルを開き、configurationタグの中に
<runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="x86"/> </assemblyBinding> </runtime>これでCefSharpの導入は完了です。
Bingマップを表示するプログラム
それでは、プログラムを作っていきます。
以下のようにPanelやステータスバーなどを置きます。
CefSharpはツールボックスに表示できないので、Panelの内部に合わせて表示するようにします。
以下が今回のプログラムです。
using System; using System.Windows.Forms; using CefSharp; using CefSharp.WinForms; namespace BingMapsSample { public partial class Form1 : Form { string maphtml = @"C:\Maps.html"; // さっきのHTMLファイル ChromiumWebBrowser cefBrowser; CefSettings settings; public Form1() { InitializeComponent(); button1.Enabled = false; toolStripStatusLabel1.Text = "読み込み中..."; settings = new CefSettings(); // レンダリングを最適化(これをやらないとバグる) settings.SetOffScreenRenderingBestPerformanceArgs(); Cef.Initialize(settings); cefBrowser = new ChromiumWebBrowser(maphtml); cefBrowser.LoadingStateChanged += CefBrowser_LoadingStateChanged; // Panelに合わせて表示 panel1.Controls.Add(cefBrowser); cefBrowser.Dock = DockStyle.Fill; } private void CefBrowser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e) { if(!e.IsLoading) { // 読み込み完了時 Invoke((MethodInvoker)delegate { toolStripStatusLabel1.Text = "読み込み完了"; button1.Enabled = true; }); } } private void button1_Click(object sender, EventArgs e) { // Javascriptの実行 cefBrowser.ExecuteScriptAsync("map = mapStart(" + textBox1.Text + "," + textBox2.Text + "," + "10);"); cefBrowser.ExecuteScriptAsync("mapPushpin(" + textBox1.Text + "," + textBox2.Text + ");"); } } }button1_Click内ではExecuteScriptAsyncメソッドを使って、HTMLファイルで定義した関数を呼び出しています。
実行するとこのようにマップが表示されます。
緯度と経度を入力して「移動」を押せば
このようにマークが付きます。おわりに
以上、C#でFormにBingマップを表示する方法でした。
回りくどい方法でしたが、意外とちゃんと動くので是非試してみてください。参考文献
- 投稿日:2020-05-26T00:33:38+09:00
[Roslyn] オブジェクトインスタンス化コード生成に必要な要素の調査
前書き
Roslynによるインターフェースの実装クラスの構築で、ソースコードをパーズし、ASTを作るところまでは見たので、続きとして戻り値の型のインスタンスを生成する。
インスタンスを生成するためには、少なくとも
- 目的の型の名称
- 引数
- 引数の型名
- 仮引数名
- 名前付き引数形式で値を渡す場合
- デフォルト引数の有無
- インスタンス科においてパラメータ修飾子(
out
、ref
など)は不要そう- 初期化子
- コンストラクタが明示されず、プロパティやフィールドへの直接代入する場合
が必要。
また、
IEnumerable<T>
やNullable<T>
でエンベロープする場合は、それらの型も必要。実行時であれば、リフレクションで取得できるが、まだビルド前のためそれも叶わない。
このような状況で、Roslyn
では意味解析の結果を取得するAPI
(セマンティックAPI)が提供されている。そこでオブジェクトインスタンス化に必要な要素の収集するため、この
セマンティックAPI
を使用してを調べた記録を残す。調査結果
ここでは結果を保持しておく型として
using System.Linq; using System.Collections.Immutable; using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace SemSymbols { public enum TypeVerCategory { Unknown, Constructor, Properties, Fields, } public struct TypeVar<TSymbol> where TSymbol: ISymbol { public TypeVerCategory Category { get; private set; } public ImmutableArray<TSymbol> Symbols { get; private set; } public TypeVar(TypeVerCategory inCategory, ImmutableArray<TSymbol> inSymbols) { this.Category = inCategory; this.Symbols = inSymbols; } } public static class TypeVar { public static TypeVar<IParameterSymbol> OfConstructor(ImmutableArray<IParameterSymbol> inSymbols) { return new TypeVar<IParameterSymbol>(TypeVerCategory.Constructor, inSymbols); } public static TypeVar<IPropertySymbol> OfProperties(ImmutableArray<IPropertySymbol> inSymbols) { return new TypeVar<IPropertySymbol>(TypeVerCategory.Properties, inSymbols); } public static TypeVar<IFieldSymbol> OfFields(ImmutableArray<IFieldSymbol> inSymbols) { return new TypeVar<IFieldSymbol>(TypeVerCategory.Fields, inSymbols); } } public struct MethodParamTypeVar { IParameterSymbol[] Parameters { get; set; } } public struct EntitiyContext { public SpecialType ContainerType { get; set; } public INamedTypeSymbol NamedType { get; set; } public TypeVar<IParameterSymbol>[] Constructors { get; set; } public TypeVar<IPropertySymbol> PropertyTypeVars { get; set; } public TypeVar<IFieldSymbol> FieldTypeVars { get; set; } } }を用意し、セマンティックAPIを駆使して以下のメソッドを組み上げた。
namespace SemSymbols { // (snip) public static class SemSymbolHelper { public static bool TryCResolveReturnTypeContext(Microsoft.CodeAnalysis.TypeInfo inReturnTypeInfo, out EntitiyContext outCtx) { outCtx = default; var typeSymbol = inReturnTypeInfo.Type as INamedTypeSymbol; if (typeSymbol == null) return false; var fmtFqcn = new SymbolDisplayFormat( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable ); (outCtx.ContainerType, outCtx.NamedType) = ResolveAsContainerType(typeSymbol); if (outCtx.NamedType == null) return false; var memberSymbols = outCtx.NamedType.GetMembers(); outCtx.Constructors = memberSymbols .OfType<IMethodSymbol>() .Where(s => s.MethodKind == MethodKind.Constructor) .Where(s => s.Parameters.Length > 0) .Select(s => TypeVar.OfConstructor(s.Parameters)) .ToArray() ; outCtx.PropertyTypeVars = TypeVar.OfProperties( memberSymbols.OfType<IPropertySymbol>().Where(s => ! s.IsReadOnly).ToImmutableArray() ); outCtx.FieldTypeVars = TypeVar.OfFields( memberSymbols.OfType<IFieldSymbol>().Where(s => ! s.IsImplicitlyDeclared).ToImmutableArray() ); return true; } private static System.ValueTuple<SpecialType, INamedTypeSymbol> ResolveAsContainerType(INamedTypeSymbol inSymbol) { var fmtFqcn = new SymbolDisplayFormat( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable ); return inSymbol.ToDisplayString(fmtFqcn) switch { "System.Nullable" => (SpecialType.System_Nullable_T, inSymbol.TypeArguments[0] as INamedTypeSymbol), "System.Collections.Generic.IEnumerable" => (SpecialType.System_Collections_Generic_IEnumerable_T, inSymbol.TypeArguments[0] as INamedTypeSymbol), _ => (SpecialType.None, inSymbol), } ; } } }ユニットテスト
以下のユニットテスト、(最低限の要素が)収集できたことを確認した。
using NUnit.Framework; using System; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using GenSyntaxTestHelpers; using SemSymbols; namespace SemModelTests { public class SemModelTest { private static readonly string IntfSource1 = @" namespace SemModels { public interface IColorDao1 { ColorData? FindById(int id); } } "; private static readonly string EntitySource1 = @" namespace SemModels { public readonly struct ColorData { public int Id { get; } public string Name { get; } public int Red { get; } public int Green { get; } public int Blue { get; } public ColorData(int id, string name, int red = default, int green = default, int blue = default) => (Id, Name, Red, Green, Blue) = (id, name, red, green, blue); } } "; private static readonly string IntfSource2 = @" namespace SemModels { public interface IColorDao2 { ColorDataMut FindById(int id); } } "; private static readonly string EntitySource2 = @" namespace SemModels { public struct ColorDataMut { public int Id { get; set; } public int Code { get => this.Id; } public string Name { get; set; } public int Red { get; set; } public int Green { get; set; } public int Blue { get; set; } } } "; private static readonly string IntfSource3 = @" using System.Collections.Generic; namespace SemModels { public interface IColorDao3 { IEnumerable<ColorDataMut2> FindAll(); } } "; private static readonly string EntitySource3 = @" namespace SemModels { public struct ColorDataMut2 { public int id; public string name; public int red; public int green; public int blue; } } "; [Test] public void _戻り値型のコンテキストを解決_コンストラクタを持つ場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource1); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource1); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0]; var intf = (InterfaceDeclarationSyntax)ns.Members[0]; var meth = (MethodDeclarationSyntax)intf.Members[0]; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(meth.ReturnType); Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること"); Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.System_Nullable_T), "Nullableによるコンテナ型"); Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること"); Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorData"), "型名が一致すること"); Assert.That(ctx.Constructors.Length, Is.EqualTo(1), "1つコンストラクタが定義されていること(デフォルトコンストラクタは除外)"); Assert.That(ctx.Constructors[0].Category, Is.EqualTo(TypeVerCategory.Constructor), "カテゴリがコンストラクタであること"); Assert.That(ctx.Constructors[0].Symbols.Length, Is.EqualTo(5), "コンストラクタ引数の数が一致していること"); var arg1 = ctx.Constructors[0].Symbols[0]; { Assert.That(arg1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[1]がint型であること"); Assert.That(arg1.Name, Is.EqualTo("id"), "引数名[1]"); Assert.That(arg1.IsOptional, Is.Not.True, "引数[1はオプショナルではないこと"); } var arg2 = ctx.Constructors[0].Symbols[1]; { Assert.That(arg2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "引数[2]がstring型であること"); Assert.That(arg2.Name, Is.EqualTo("name"), "引数名[2]"); Assert.That(arg2.IsOptional, Is.Not.True, "引数[2]はオプショナルではないこと"); } var arg3 = ctx.Constructors[0].Symbols[2]; { Assert.That(arg3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[3]がint型であること"); Assert.That(arg3.Name, Is.EqualTo("red"), "引数名[3]"); Assert.That(arg3.IsOptional, Is.True, "引数[3]はオプショナル"); } var arg4 = ctx.Constructors[0].Symbols[3]; { Assert.That(arg4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[4]がint型であること"); Assert.That(arg4.Name, Is.EqualTo("green"), "引数名[4]"); Assert.That(arg4.IsOptional, Is.True, "引数[4]はオプショナル"); } var arg5 = ctx.Constructors[0].Symbols[4]; { Assert.That(arg5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "引数[5]がint型であること"); Assert.That(arg5.Name, Is.EqualTo("blue"), "引数名[5]"); Assert.That(arg5.IsOptional, Is.True, "引数[5]はオプショナル"); } Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること"); Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(0), "Readonlyプロパティは含めないこと"); Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること"); Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(0), "暗黙宣言されたフィールドは含めないこと"); } [Test] public void _戻り値型のコンテキストを解決_セッタープロパティによる初期化が要求される場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource2); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource2); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0]; var intf = (InterfaceDeclarationSyntax)ns.Members[0]; var meth = (MethodDeclarationSyntax)intf.Members[0]; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(meth.ReturnType); Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること"); Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.None), "エンティティ型"); Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること"); Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorDataMut"), "型名が一致すること"); Assert.That(ctx.Constructors.Length, Is.EqualTo(0), "1つもコンストラクタが定義されていないこと(デフォルトコンストラクタは除外)"); Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること"); Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(5), "Writableプロパティを持つこと"); var prop1 = ctx.PropertyTypeVars.Symbols[0]; { Assert.That(prop1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[1]がint型であること"); Assert.That(prop1.Name, Is.EqualTo("Id"), "プロパティ名[1]"); } var prop2 = ctx.PropertyTypeVars.Symbols[1]; { Assert.That(prop2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "プロパティ[2]がstring型であること"); Assert.That(prop2.Name, Is.EqualTo("Name"), "プロパティ名[2]"); } var prop3 = ctx.PropertyTypeVars.Symbols[2]; { Assert.That(prop3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[3]がint型であること"); Assert.That(prop3.Name, Is.EqualTo("Red"), "プロパティ名[3]"); } var prop4 = ctx.PropertyTypeVars.Symbols[3]; { Assert.That(prop4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[4]がint型であること"); Assert.That(prop4.Name, Is.EqualTo("Green"), "プロパティ名[4]"); } var prop5 = ctx.PropertyTypeVars.Symbols[4]; { Assert.That(prop5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "プロパティ[5]がint型であること"); Assert.That(prop5.Name, Is.EqualTo("Blue"), "プロパティ名[5]"); } Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること"); Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(0), "暗黙フィールドは除外されていること"); } [Test] public void _戻り値型のコンテキストを解決_公開フィールドを持つ場合() { var intfTree = SyntaxFactory.ParseSyntaxTree(IntfSource3); var entityTree = SyntaxFactory.ParseSyntaxTree(EntitySource3); var compiler = SyntaxGeneratorHelper.CreateCompilation(intfTree, entityTree); var ns = (NamespaceDeclarationSyntax)intfTree.GetCompilationUnitRoot().Members[0]; var intf = (InterfaceDeclarationSyntax)ns.Members[0]; var meth = (MethodDeclarationSyntax)intf.Members[0]; var model = compiler.GetSemanticModel(intfTree); var entityInfo = model.GetTypeInfo(meth.ReturnType); Assert.IsTrue(SemSymbolHelper.TryCResolveReturnTypeContext(entityInfo, out var ctx), "解決できていること"); Assert.That(ctx.NamedType, Is.Not.Null, "型名のシンボルが取得できていること"); Assert.That(ctx.NamedType.ToDisplayString(), Is.EqualTo("SemModels.ColorDataMut2"), "型名が一致すること"); Assert.That(ctx.ContainerType, Is.EqualTo(SpecialType.System_Collections_Generic_IEnumerable_T), "コレクションコンテナ型"); Assert.That(ctx.Constructors.Length, Is.EqualTo(0), "1つもコンストラクタが定義されていないこと(デフォルトコンストラクタは除外)"); Assert.That(ctx.PropertyTypeVars.Category, Is.EqualTo(TypeVerCategory.Properties), "カテゴリがプロパティであること"); Assert.That(ctx.PropertyTypeVars.Symbols.Length, Is.EqualTo(0), "Readonlyプロパティは含めないこと"); Assert.That(ctx.FieldTypeVars.Category, Is.EqualTo(TypeVerCategory.Fields), "カテゴリがフィールドであること"); Assert.That(ctx.FieldTypeVars.Symbols.Length, Is.EqualTo(5), "暗黙宣言されたフィールドは含めないこと"); var f1 = ctx.FieldTypeVars.Symbols[0]; { Assert.That(f1.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[1]がint型であること"); Assert.That(f1.Name, Is.EqualTo("id"), "フィールド名[1]"); } var f2 = ctx.FieldTypeVars.Symbols[1]; { Assert.That(f2.Type.SpecialType, Is.EqualTo(SpecialType.System_String), "フィールド[2]がstring型であること"); Assert.That(f2.Name, Is.EqualTo("name"), "フィールド名[2]"); } var f3 = ctx.FieldTypeVars.Symbols[2]; { Assert.That(f3.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[3]がint型であること"); Assert.That(f3.Name, Is.EqualTo("red"), "フィールド名[3]"); } var f4 = ctx.FieldTypeVars.Symbols[3]; { Assert.That(f4.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[4]がint型であること"); Assert.That(f4.Name, Is.EqualTo("green"), "フィールド名[4]"); } var f5 = ctx.FieldTypeVars.Symbols[4]; { Assert.That(f5.Type.SpecialType, Is.EqualTo(SpecialType.System_Int32), "フィールド[5]がint型であること"); Assert.That(f5.Name, Is.EqualTo("blue"), "フィールド名[5]"); } } } }