Open5

C# の雑多なメモ

いしころいしころ

xUnit の InlineData

いつも忘れるのでめも

[Theory]
[InlineData(1, 2.1, "aa")]
[InlineData(4, 100.13, "bb")]
public void HogeTest(int x, double y, string result) { ... }
いしころいしころ

短い文字列の UTF-8 バイト列を得る

/*
byte[] ToUtf8Bytes(string s)
{
    Span<byte> span = stackalloc byte[s.Length * 4];
    var length = Encoding.UTF8.GetBytes(s, span);
    return span.Slice(0, length).ToArray();
}*/

// 普通に byte[] を返すオーバーロードあったわ
string s = "sss";
byte[] utf8Bytes = Encoding.UTF8.GetBytes(s);
いしころいしころ

参照先 csproj の appsettings をコピーしたくないときの対処

問題

たとえば

  • Project A (unit test project など)
    • appsettings.json
  • Project B
    • appsettings.Production.json

のようなプロジェクトがあるとする.
Project A -> Project B へのプロジェクト参照があって,
Project Aappsettings.json を, Project Bappsettings.Production.json をそれぞれ出力ディレクトリにコピーする設定になっているとする.

この場合, Project A の出力ディレクトリにも Project B 由来の appsettings.Production.json がコピーされるが,Project A での config の読み込みに Host.CreateDefaultBuilder などを使っている場合,デフォルトの environment が Production に設定されるため, Project A の appsettings.json よりも Project B 由来の appsettings.Production.json が優先されてしまう.

ここで, Project B に変更を入れずに, Project A の出力ディレクトリに appsettings.Production.json を作りたくないとき,どうしたらよいか.

解1

Project A の appsettings.json を appsettings.Production.json に rename する
ただ,単体テストプロジェクトでは

  • production とは?って感じ. appsettings.Production.json にしている意図も伝わらない
  • 他の Project B 由来の appsettings.Development.json などは出力ディレクトリに残る

解2

Project A の AfterBuild ターゲットのあとに, Project B 由来の appsettings.xxx.json を削除する処理を入れる.

csproj に下記追加

  <!-- 参照している Project B から appsettings.xxx.json がコピーされ,設定が上書きされるため削除 -->
  <Target Name="DeleteAppSettingsFromReferencedProject" AfterTargets="AfterBuild">
    <Delete Files="$(TargetDir)appsettings.Production.json" />
    <Delete Files="$(TargetDir)appsettings.Development.json" />
  </Target>

ビルド時に Project B 由来の appsettings.Production.json がコピーされるが, AfterBuild の処理により削除される.

いしころいしころ

CsvHelper の CultureInfo の扱いについて

本番環境と開発環境で CsvHelper の出力結果が変わってしまいハマったのでメモ。
古いバージョン (v12) をカルチャ指定なしで使っており、 CultureInfo.CurrentCulture 相当のものが使われていたもよう。

テスト結果は windows 10, .NET 5 (LINQ Pad) の日本語環境で実行したもの。

現行 ver (v27.0.4)

CsvWriter のコンストラクタに CultureInfo を渡す(必須。null では NullReferenceException)
CultureInfo によって DateTime など、カルチャ依存のものは結果が変わる。

テスト用コード:

using System.Globalization;

void Main()
{
	var record = new Hoge(1, DateTime.Now);
	// WriteCsvTest("null", record, null).Dump(); null reference exception
	WriteCsvTest("invariant culture", record, CultureInfo.InvariantCulture).Dump();
	WriteCsvTest("current culture", record, CultureInfo.CurrentCulture).Dump();
	WriteCsvTest("ja-jp", record, CultureInfo.GetCultureInfo("ja-JP")).Dump();
}

// You can define other methods, fields, classes and namespaces here
public record Hoge(int Id, DateTime Date);
public record WritingResult(string Name, string result);

static WritingResult WriteCsvTest(string testCaseName, Hoge record, CultureInfo cultureInfo)
{
	using var memory = new MemoryStream();
	using (var writer = new StreamWriter(memory))
	using (var csvWriter = new CsvWriter(writer, cultureInfo))
	{
		csvWriter.WriteRecord(record);
	}

	var result = Encoding.UTF8.GetString(memory.ToArray());
	
	return new WritingResult(testCaseName, result);
}

結果:

旧 ver (v12.0)

旧バージョンでは CultureInfo は必須でない。
指定がない場合は CultureInfo.CurrentCulture と同じ動作をする。→ カルチャが異なる環境で実行した場合、シリアライズ結果が変わってしまうので注意。

テスト用コード:

void Main()
{
	var record = new Hoge(1, DateTime.Now);
	WriteCsvTest("null", record, null).Dump();
	WriteCsvTest("invariant culture", record, CultureInfo.InvariantCulture).Dump();
	WriteCsvTest("current culture", record, CultureInfo.CurrentCulture).Dump();
	WriteCsvTest("ja-jp", record, CultureInfo.GetCultureInfo("ja-JP")).Dump();
}

// You can define other methods, fields, classes and namespaces here
public record Hoge(int Id, DateTime Date);
public record WritingResult(string Name, string result);

static WritingResult WriteCsvTest(string testCaseName, Hoge record, CultureInfo cultureInfo)
{
	using var memory = new MemoryStream();
	using (var writer = new StreamWriter(memory))
	using (var csvWriter = new CsvWriter(writer, new Configuration() { CultureInfo = cultureInfo }, true))
	{
		csvWriter.WriteRecord(record);
	}

	var result = Encoding.UTF8.GetString(memory.ToArray());

	return new WritingResult(testCaseName, result);
}

結果: