tech.guitarrapc.cóm

Technical updates

Windows でエクスプローラーを使って .gitconfig ファイルを作る

Windows のエクスプローラーで .gitignore というファイル名を作ろうとするとエラーが出ます。

.gitconfig という名前のファイルを作ろうとするとエラー

これをコマンドラインを使わず作る簡単な方法を紹介します。

目次

. から始まる名前のファイルを作る時は末尾にも . を置く

エクスプローラーを拡張子が表示できるようにしている前提です。

File name extensions (ファイル拡張子) を有効にしておく

エクスプローラーで. から始まるファイルを作るときは、.作りたいファイル名. と末尾にも. を置いてみてください。

例えば、.gitconfig というファイルを作りたいなら .gitconfig. です。

.gitconfig を作るために .gitconfig. と入力する

すると、ファイル拡張子を変えてもいいか聞かれるのでYes (はい) を選択します。

ファイル拡張子の変更ダイアログが表示される

.gitconfig ファイルができました。

.gitconfig というファイルができる

ファイル種別をみると、GITCONFIG file となっており、察できます。

ファイル種別が .以降の名称

簡単と感じてもらえるといいですね。

コマンドラインで作る

bash でも、コマンドプロンプトでも、PowerShell でも C#でも Goでもなんでもok です。

touch をいれているなら touch で。 コマンドプロンプトなら、type nulcopy nul を使うと空のファイルが作れます。 PowerShell なら、Out-FileNew-Item でもいいでしょう。

gist.github.com

なんでもいいのでどれでもどうぞ。

まとめ

数年前に教えてくださったのが @mayuki 、ありがとうございます。

Programming 向け Awesome Fonts の選択

特に Windows 環境で感じるのですが、プログラミングでフォントを選ぶのは重要だと思います。

今回、ふとした思い立ちでフォントを見直してみました。

続きはこちら

tech.guitarrapc.com

目次

TL;DR;

Dejavu Sans Mono - Bront でしばらくいくが、「g」がぐねっとしたフォントがあればそれに変えたい。

結果

個人の感覚でスコアを点けてみました。 結果だけ見てみると、なるほど DejaVuSansMono-Bront が今のところ一歩だけよいみたいです。

Fontのスコア付け

基準は

Target Border(:eyes:) Bad(-1) Good (+1)
oO08 distinct each 0 has dot inside 0 has slash
iIlL1 distinct each l and 1 are almost same l and 1 are completely different
g9qCGQ distinct each g is Slab serif g is not Slab serif, Q is serif font
~-+=> no ligature not center or any unbalance n/a
Left and right side padding too narrow TODO: measure
Top and bottom side padding too wide TODO: measure
Size too small 2/3 or 3/4 height of small letters
Balance Letter Paddings and Boldness

明日には違ってそうなので、後日、客観的に判別するため具体的な指標となる自分の好みの数値を測ろうかと思います。

Fonts-lab/SCORE.md at master · guitarrapc/Fonts-lab · GitHub

GitHub

個人的なフォントお試し結果をリポジトリに記録しています。今後もGitHub 上のREADME を更新していく予定なので参考にどうぞ。

github.com

Motivation

個人的に、Windows環境での font は Consolas を長年使っており、都度他のフォントを試しては納得いかず consolas に戻していました。

改めて2019年で何かないかなぁと見直したのがきっかけ。

個人的にConsolas に感じる課題は、小文字L と 数字1 が見分けつきにくいことと、微妙に文字体の詰まり加減があとわずかになんとかならないのか感と、古いっぽいような微妙な感触であること。

Requirements

  • モノスペースフォント(等幅フォント)必須。プロポーショナルフォントは絶対だめ
  • 文字幅が少しあるほうがいい 1
  • 行が少し詰まっているほうが全体を見れてうれしい 2
  • 0 は スラッシュいれてほしい
  • 小文字L と 1 の区別は大事
  • 小文字 g がグねってしていると q と区別つきやすくて最高
  • Ligature はいらない 3
  • もともとプログラム内で日本語はかかないので入っていなくてもok 4
  • 横は狭いより広くてok
  • 縦が広いのはダメ
  • フォントは大き目がよい

Compare

フォントをさくさく比較しつつ見ていくには、app.programmingfonts.org が最高なので、一通りみるといいです。ただし、自分のエディタで実際に入れてみないと実際の幅や感覚が違うので、これはという候補の比較にフォントインストールは必須です。

https://app.programmingfonts.org/#borg-sans-mono

app.programmingfonts.org を使うとフォントを左ペインで選んで比較できる

まとめてスクショ。

Kibelaでフォントのスクショを比較

個別に並べておきます。

Consolas 8.0

Dejavu Sans Mono - Bront

Hack

Source Code Pro (Variable)

MesloLGS-Regular

mononoki

Selection

DejaVu Sans Mono - Bront と Meslo の選択

  • DejaVu Sans Mono - Bront か MesloLGS-Regular がいい感じだけど、g が気に入らないので Consolas や Source Code Pro っぽいgが欲しい
  • ほかの文字、文字幅、行幅はバランスいい
  • DejaVu Sans Mono のほうが、MesloLGS-Regularよりも縦が詰まるのでプログラム全体を少しでも見るにはいい

Consolas や Source Code Pro と他の選択

  • 小文字の g がConsolas / Source Code Pro がめちゃめちゃいい
  • Consolasは 小文字L と 数字1 が見分けつきにくい

Hack と Source Code Pro と他の選択

  • Hack と Source Code Pro は 0 が ・なのが @ と見間違える感でいや

momonoki と他の選択

  • g も 0 も lと1 も満たしているが、文字幅の感覚が狭い。文字微妙

Consolas 8.0 と Consolas

  • 置き換えになるのでなんとも言い難い (違いがめっちゃ分かりにくい)
  • Consolas 8.0 でも 小文字 l と 1 の違いがつきにくかったのでないかなぁ
  • 文字幅は consolas と変わってなかった感じ

他のフォント

  • Fantasque Sans Mono: Ligature いらない
  • Inconsolata: Ligature いらない
  • M+: 文字が狭く読みにくいです。モノスペースなほうは特に顕著でむり

日本語を含めるなら

リンクすればいいと思います。

mofoolog.hateblo.jp


  1. Consolas比
  2. Source Code Pro比
  3. プログラミングで合字は構文とのバッティング起こしてて、ちょっと使う理由が見つからない
  4. もし日本語があるなら「英語:日本語 = 1:2」の比率が絶対

Sharplab のMemoryGraph を使ってメモリの状態を確認する

以前 TryRoslyn と言われてたサービスですが、今は Sharplab という名になっています。

このサービスを使うと、コードがILやネイティブコードにどのようにコンパイルされるか確認したり、実行したりオブジェクトのメモリ状態を確認できます。

例えば次の図は、構造体の文字列がどのようなメモリ状態なのかを示したものです。

Sharplab を使って、コードだけでなくメモリ状態を可視化することで理解を深めるきっかけにできるか見てみましょう。

目次

TL;DR;

Shaplab を使って可視化することで「分からないことを、何が分からないかわかるようにできる」と思うので活用するといいと思います。

LinqPadも似てますが、併用するとより幸せになれます。

Sharplabの基本

Sharplab にアクセスしてみましょう。

sharplab.io

次のようなシンプルなクラスが表示されていると思います。

using System;
public class C {
    public void M() {
    }
}

ここに色々コードを書いて試すことができます。

いつ使うのか

スマホやPCでも入力に困らないレベルでインテリセンスによるコード入力補助があります。さらに後述するコードの実行、解析、C#バージョンの選択までできます。

加えて、入力したコードが自動的に一意なurl で再表示できるので、コードの共有にもいいでしょう。

だいたいここまでできるとLinqPad でできることが網羅されており、nuget を使わない限りはLinqPadより気軽で便利な面も多いでしょう。

基本的な文法、Decompileや言語バージョンの違いの確認程度なら、Sharplab でいい感じがします。

インテリセンス

コードの共有

何かしらコードを入力するとURLがついて、入力ごとに変化しているのが分かると思います。

一意なURLがコードごとに生成される

このURLを踏むとそのコードを表示できるのでコードで状態を共有出来て非常に便利です。実際、C# のGitHub Issue などで SharpLabのURLで再現コードをシェアしているのも見かけます。

この記事でもコードごとにURLをシェアしてみましょう。

言語選択

画面上部のCodeから C# 以外に VB.NET や F# が選べます。

言語選択

表示の切り替え(Decompile)

下(あるいは右)ペインResults を選ぶといくつか選択肢があります。Decompileのまとまりから見ていきましょう。

Decompile の中で C# を選ぶとデコンパイルしたC#コード、IL を選ぶとIL状態、JIT Asm を選ぶとネイティブコードを確認できます。

Decompile C#

Decompile IL

Decompile JIT Asm

言語ごとのデコンパイル結果の比較

余談ですが、C#、VB.NET、F# それぞれで同じようなコードを書いた時のデコンパイル結果を最も簡単に確認できるサービスの1つかもしれません。

C# の次のコードを使ってデコンパイル結果を見てみましょう。

シンプルなクラスのコード

using System;
public class C {
    public void M() {
    }
}
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    public void M()
    {
    }
}

VB.NET を選んで表示される C# Decompile 結果は C# とほぼ同じですね。

Imports System
Public Class C
    Public Sub M()
    End Sub
End Class
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("0.0.0.0")]
public class C
{
    public void M()
    {
    }
}

F# だとガラッと雰囲気違うのは興味深いです。

open System
type C() =
    member this.M() = ()
using Microsoft.FSharp.Core;
using System;
using System.Reflection;

[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyVersion("0.0.0.0")]
[CompilationMapping(SourceConstructFlags.Module)]
public static class _
{
    [Serializable]
    [CompilationMapping(SourceConstructFlags.ObjectType)]
    public class C
    {
        public C()
        {
            ((object)this)..ctor();
        }

        public void M()
        {
        }
    }
}
namespace <StartupCode$_>
{
    internal static class $_
    {
    }
}

F#からC# Decompile

Other

表示切替の中に、Decompile以外にOther があります。

Syntax もその1つで、Sharplab を使うことでRoslyn によるSyntaxTree の状態も確認できます。

SyntaxTree状態

Roslyn拡張を書くときなどににらめっこしたりすることがありますが、LinqPad以外でもきれいにみれるのはうれしいものがあります。

Verify Only を選ぶことでコンパイルできるかの確認ができます。そもそもコード入力中にがしがし評価されてエラーがずらずらでるので、Verifyしなくとも気付けるのですが。

Verify

Explain を選ぶと、余り使われないシンタックスの説明が表示されます。例えば、予約語を変数名に使う @var シンタックスを使ってみましょう。

@varのコード

using System;
public class C {
    public void M() {
        var @var = "";
    }
}

@var is a verbatim identifier. [Docs]

Prefix @ allows keywords to be used as a name (for a variable, parameter, field, class, etc).

For example, M(string string) doesn't compile, while M(string @string) does.

Explainによる利用頻度の低いシンタックスの説明

インターフェースのデフォルト実装など、新しい機能はまだ説明がないのでちまちまIssueを見るといいです。

interface Hoge {
    string Text {get;set;}
    
    public void Fuga()
    {
        Console.WriteLine(Text);
    }
}

github.com

Run

コードを実行できるので、LinqPad で軽く実行するのをWebで試せると思うとC# の書き捨てコードとしては相当楽なのではないでしょうか。 この時、結果表示にはConsole.WriteLine() によるstdout 以外にも、<何かしらのオブジェクト>.Inspect() 拡張メソッドでも表示できます。Inspectメソッドは、LinqPadの.Dump() に近い感覚でグラフィカルにオブジェクトが表示されるのでいい感じです。

上 Console.WriteLine()、下 .Inspect() 拡張メソッド

他にもヒープ状態の確認に使えるInspect.Heap() メソッドや、スタック状態の確認に使えるInspect.Stack() メソッドがあります。

Runによるコード実行

using System;
// Run mode:
//   value.Inspect()      — often better than Console.WriteLine
//   Inspect.Heap(object) — structure of an object in memory (heap)
//   Inspect.Stack(value) — structure of a stack value
public static class Program {
    public static void Main() {
        Console.WriteLine("🌄");
        new [] {"hoge", "moge"}.Inspect();
        Inspect.Heap(new [] {"pi", "yo"});
        Inspect.Stack(new [] {1, 2});
    }
}

Runの表示結果

C# のメモリ状態を確認する

さて、この記事の本筋に戻りましょう。C# を書いているとコードからは挙動やILを見ないと判断に迷うケースは少なからずあります。私は少なくともたくさんあります。

Visual StudioやVS Codeでパッと思いつくいい感じのやり方がなく、私はもっぱらLinqPad でコードを実行しつつILを見たりして確認していました。UnityのProfilerを使うという手もありますが、少しノイズが多くやりにくさはぬぐえません。

メモリの状態を確認するには、メモリの状態が可視化されるのが直感的だと思うのですが、それがない/そこまでが遠いのですね。

そんな時にSharplab の`Inspect.MemoryGraph()メソッドを使うとメモリマップが可視化され、わかりやすさを大いに助けてくれます。早速これを使ってBoxing (ボックス化) と、構造体におけるstring の参照のされ方、クラスではどう変わるのかを見てみましょう。

Boxing を可視化する

Boxing は、C# で起こりやすい割に案外分かりにくいと感じる状態の1つです。説明は適当に資料に譲るとして、int をボックス化してobjectに代入した状態を考えてみましょう。

docs.microsoft.com

int をボックス化してobjectに代入したコード

using System;
public static class Program {
    public static void Main() {
        var x = 5;
        object y = x;
        Inspect.MemoryGraph(y);
    }
}

明らかに普段書かないコードのように見えますが、場合によっては書きます。ここまでわかりやすいとパット頭でBoxing状態が書けそうですが、実際に思い描いたものと一緒か見るのに 変数yInspect.MemoryGraph(o) として可視化してみましょう。

intをボックス化してobjectに代入した状態

では、int配列をobject配列でボックス化するとどうなるでしょうか。

int をボックス化してobject配列に代入した

using System;
public static class Program {
    public static void Main() {
        var o = new object[] {1, 2, 3};
        Inspect.MemoryGraph(o);
    }
}

変数oInspect.MemoryGraph(o) として可視化してみましょう。

Boxingの可視化

戻り値がvoidだとInspectしようがないので可視化できないので、次のコードはエラーになります。

voidはInspectできない

using System;
public static class Program {
    public static void Main() {
        Inspect.MemoryGraph(Console.WriteLine(1));
    }
}

「intがobjectでボックス化されている」と言葉で聞いて、コードでも書けるときはいいのですが、「これもしかしてボックス化してる?」という時にサクッと書いて、メモリの状態を可視化できるのはかなり便利だと思います。

構造体における文字列の参照状態を確認する

こんな疑問があります。

ILを見ればすぐにわかるケースですがSharplabを使えば可視化されます。次のコードを模擬コードとしてみてみましょう。値型のint も持たせて参照型のstringとどう違うかついでに確認します。

構造体で文字列を持たせたときのメモリ状態

public static class Program {
    public static void Main() {
        var s = new S();
        s.text = "before";
        s.num = 1;
        
        var t = s.text;
        
        var h = s;
        h.num = 2;
        
        s.text = "after";
        
        Inspect.MemoryGraph(s, t, h);
    }
    
    struct S
    {
        public string text;
        public int num;
    }
}

構造体における参照状態の確認

構造体は、参照型であるstring(文字列) を参照で持っていることが ref からわかりますね。 変数s は、after という文字列を代入しているので別の文字列を参照していることが分かります。

ちなみにLinqPad で結果を見ると次のようになります。

LinqPadによる構造体の状態確認

IL 見れば、なるほどldlocaLdfld

IL_0000:  nop         
IL_0001:  ldloca.s    00 // s
IL_0003:  initobj     UserQuery.S
IL_0009:  ldloca.s    00 // s
IL_000B:  ldstr       "before"
IL_0010:  stfld       UserQuery+S.text
IL_0015:  ldloca.s    00 // s
IL_0017:  ldc.i4.1    
IL_0018:  stfld       UserQuery+S.num
IL_001D:  ldloc.0     // s
IL_001E:  ldfld       UserQuery+S.text
IL_0023:  stloc.1     // t
IL_0024:  ldloc.0     // s
IL_0025:  stloc.2     // h
IL_0026:  ldloca.s    02 // h
IL_0028:  ldc.i4.2    
IL_0029:  stfld       UserQuery+S.num
IL_002E:  ldloca.s    00 // s
IL_0030:  ldstr       "after"
IL_0035:  stfld       UserQuery+S.text
IL_003A:  ldloc.0     // s
IL_003B:  call        LINQPad.Extensions.Dump<S>
IL_0040:  pop         
IL_0041:  ldloc.1     // t
IL_0042:  call        LINQPad.Extensions.Dump<String>
IL_0047:  pop         
IL_0048:  ldloc.2     // h
IL_0049:  call        LINQPad.Extensions.Dump<S>
IL_004E:  pop         
IL_004F:  ret         

クラスにおける参照状態を確認する

先ほどの構造体Sを、クラスにするとどうなるか見てみましょう。

クラスで文字列を持たせたときのメモリ状態

public static class Program {
    public static void Main() {
        var s = new S();
        s.text = "before";
        s.num = 1;
        
        var t = s.text;
        
        var h = s;
        h.num = 2;
        
        s.text = "after";
        
        Inspect.MemoryGraph(s, t, h);
    }
    
    class S
    {
        public string text;
        public int num;
    }
}

クラスにおける参照状態の確認

構造体と違い、それぞれの値が参照でつながっているのが分かりますが、分かりにくいですね!LinqPadで実行すると、クラスの時と構造体の時で結果が変わっていることが分かります。

LinqPadによるクラスの状態確認

違いは次の通りです。

オブジェクト 構造体 クラス
s.text after after
s.num 1 2
t before before
h.text before after
h.num 2 2

コードとメモリの状態を見ると理由は明らかです。

構造体における参照状態の確認
クラスにおける参照状態の確認

ShapLab で楽しいC# コーディングを過ごしましょう!

.NET CoreのGeneric Host で Windows Service を作成する

.NET Framework で Windows Service を作るときは、Windows Service のために地道に実装するのは大変.... なので、TopShelf を使うことが定番でした。以前 Nancy を Windows Service でホストする記事を書いたこともあります。

tech.guitarrapc.com

では、.NET Core ではどうでしょうか? TopShelf は .NET Standard 2.0 に対応しているので利用できます。

github.com

しかしGeneric Host は Windows Service も想定されており、かなり簡単に作成できるので見てみましょう。

前回の記事から関連させて、Windows Service + Web Jobs でホスティングすることを目標としてみます。

目次

TL;DR;

.NET Core + Generic Host でもWindows Serviceを作れます。 ここではその作り方とコツを見ていきましょう。

事前に読んでおきたい

tech.guitarrapc.com

tech.guitarrapc.com

Windows Service とは

これです。

Windows Service はめんどうごとが多い

Windows Service といえば作るのがめんどくさい筆頭です。TopShelf を使えばかなり楽ですが、Windows Service自体のハンドルが面倒なことには変わりありません。

特に開始、終了、(停止と再開は置いておいて)、実行ユーザー(プロファイル/アクセス権限)ははまりどころが多いでしょう。

.NET Core でもこの面倒さは変わらず存在します。

Windows Service + WebJobs は可能なのか

一見すると機能が上手く成り立たないように思えますが可能です。

実際に実装して使っています。先のエントリーがされていれば問題ありません。

tech.guitarrapc.com

Windows Service をホストする

順にみていきましょう。

Packageの追加

Windows Servie をホストするには、次のパッケージを追加します。

    <PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
    <PackageReference Include="System.ServiceProcess.ServiceController" Version="4.5.0" />

Windows Service処理時の実装

Windows Service で Start や Stop した時の処理を書きます。感覚的には TopShelf と同じです。

gist.github.com

HostBuilder への拡張メソッド追加

Windows Serviceは、VSなどではコンソールとして起動して、サービスホスティング時だけ先ほどの実装を使ってほしいです。 そこで、サービスホスティング時の処理を拡張メソッドで定義します。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

    public static class ServiceBaseLifetimeHostExtensions
    {
        public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
        {
            return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
        }

        public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
        {
            return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
        }
    }

Mainからの呼び出し

(VS上など)デバッガーアタッチされている、あるいは引数に--console コンソール時にコンソール実行するようにします。

RunConsoleAsync で Ctrl + C を待ち受け SIGTERM でカットしてくれるので便利です。

        static async Task Main(string[] args)
        {
            var isService = !(Debugger.IsAttached || args.Contains("--console"));
            var builder = CreateHostBuilder(args);
            if (isService)
            {
                await builder.RunAsServiceAsync();
            }
            else
            {
                await builder.RunConsoleAsync();
            }
        }

appsettings.json のパス解決

アプリのビルド時にappsettings.json のパスがサービス実行時だけ見つからないケースがあります。 この場合は、SetBasePathしておくといいでしょう。

Assembly からではなく、Processからとるのがオススメです。

                .ConfigureAppConfiguration((hostContext, configApp) =>
                {
                    configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                })

ビルド時にランタイムを含める

サービスホスト時の注意が、.NET Core Runtime の解決です。 経験した限りでは、.NET Core Runtime に対して System ユーザーでのPATH解決はうまくいかないことが多く困りどころになりそうです。 そのため現在のところランタイムを同梱させてビルドするほうが安定しており良いと判断しています。

dotnet ビルドするときにランタイムを指定するか、あるいはcsproj に指定しましょう。

dotnet publish -r win10-x64

csproj に指定するなら dotnet ビルド時にランタイム指定が不要です。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
  </PropertyGroup>

</Project>

これで、ランタイム同梱でビルドされます。 通常パブリッシュ向けビルドはdotnet publish のみですが、ランタイム同梱などの場合はdotnet build をしてからやるといいでしょう。

dotnet build
dotnet publish

サービス登録

さぁ準備完了です。sc コマンドでサービスを登録しましょう。

sc create myservice binPath=ビルドした.exeへのフルパス

次のメッセージが出れば登録完了です。

[SC] CreateService SUCCESS

サービスを開始しましょう。

sc start myservice

うまく起動できれば成功です!

SERVICE_NAME: myservice
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 31760
        FLAGS

Ref

The Background Tasks Based On Generic Host In .NET Core

Creating Windows service and Linux daemon with the same code base in .NET

Running a .NET Core Generic Host App as a Windows Service

Host .NET Core console application like Windows Service

NetCore 2.1 Generic Host as a service

.NET Standard や .NET Core におけるAzureのStorage関連nugetパッケージの選択

Azure の C# 向け Storage SDK は nuget でサクッとアプリケーションに追加できます。

しかし、現時点ではWindowsAzure.Storage パッケージとMicrosoft.Azure.Storage.Xxxx パッケージに分かれており、お互いが干渉合うために混在するとAssembly Bindingsを含む解決まで手間取ることになります。

いくつかトラブルに遭遇してきた経験から、どんな時にどのパッケージを選ぶのが良いのか考えます。*1

目次

TL;DR;

WebJobs (AzureFunctions) を使う場合、WindowsAzure.Storage を使うのが安定でオススメ。

Microsoft.Azure.Storage を使う場合、WindowsAzure.Storage で使っているクラスと重複し、完全修飾名やdllレベルで一致させてもバージョンによって実行時エラーが起こります。

Azure の ストレージを扱うための nuget パッケージ

Azure では、もともとWindowsAzure.Storage nuget パッケージを使ってストレージ系の処理を提供していました。

https://www.nuget.org/packages/WindowsAzure.Storage/

しかしこのパッケージは、Storage Blob / Storage Queue / Storage File / Storage Table など各種ストレージ系処理をまとめてしまっておりBlobだけ使いたい、Queueだけ使いたいなどというときには大きいパッケージです。

そこで、Blob / Queue / File を個別のnugetパッケージとして分離された新しいSDKが Microsoft.Azure.Storage.Xxxxx です。WindowsAzure.Storage の 9.4.0 からは個別パッケージで提供されています。

https://www.nuget.org/packages/Microsoft.Azure.Storage.Blob/ https://www.nuget.org/packages/Microsoft.Azure.Storage.Queue/ https://www.nuget.org/packages/Microsoft.Azure.Storage.File/

なお、Storage Table は提供されておらず、これはMicrosoft.Azure.CosmosDB.Table Microsoft.Azure.Cosmos.Table で提供されています。

https://www.nuget.org/packages/Microsoft.Azure.CosmosDB.Table/ https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Table/

これをもって、WindowsAzure.Storage は現在Legacy SDK と呼ばれています。(CosmosDB へのサポートは 2018年11月まで)

なお、Storage Teams の Blog ではこれはアナウンスされておらず WindowsAzure.Storage の NuGet ページに記載があります。

https://blogs.msdn.microsoft.com/windowsazurestorage/

GitHub リポジトリ

WindowsAzure.StorageMicrosoft.Azure.Storage.Xxxx も、次のリポジトリで管理されています。

https://github.com/Azure/azure-storage-net

WindowsAzure.Storage と Microsoft.Azure.Storage の違い

GitHub や NuGet では情報が散乱しているのですが、Stack Overflow によくまとまっています。

  1. Microsoft.Azure.Storage splits libraries to three parts, Blob, Queue and File, which means we can install separate package instead of the full edition
  2. Microsoft.Azure.Storage doesn't support Table API, it is transferred to Microsoft.Azure.CosmosDB.Table, which is only for .NET Framework right now
  3. Microsoft.Azure.Storage added NetStandard2.0 target support since 9.4.0-preview, which supports synchronous methods wrapped over the asynchronous APIs. WindowsAzure.Storage on NetStandard only has asynchronous APIs
  4. Microsoft.Azure.Storage v9.4 package moves back to use Microsoft.WindowsAzure.Storage namespace temporarily to ease the transition for existing libraries

https://stackoverflow.com/questions/53214947/what-is-the-difference-between-the-microsoft-azure-storage-and-windowsazure-stor

少し見ていきましょう。

.NET Standard の対応状況から判断する

Microsoft.Azure.Storage 9.4.2 時点です。

Azure Storage Table を使って、.NET Core アプリを作る場合、Windows.Azure.Storage を使う必要があります。

機能 WindowsAzure.Storage Microsoft.Azure.Storage Microsoft.Azure.CosmosDB.Table Microsoft.Azure.Cosmos.Table
Blob .NET Standard 1.3 .NET Standard 2.0 n/a n/a
Queue .NET Standard 1.3 .NET Standard 2.0 n/a n/a
File .NET Standard 1.3 .NET Standard 2.0 n/a n/a
Table .NET Standard 1.3 n/a なし (.NET Framework 4.5のみ) .NET Standard 2.0

WebJobs / AzureFunctions を使っているかで判断する

WebJobs (Azure Functions も中でつかっている) は、WindowsAzure.Storage を依存パッケージに含んでいます。

そのため、WebJobs / AzureFunctions を使う場合は、WindowsAzure.Storage を参照するのが妥当です。

このとき、WebJobs のパッケージバージョンと WindowsAzure.Storage のバージョンは合わせておくと Assembly Binding に合わずに済むので安定します。

Storage Table を使っているかどうかで判断する

WindowsAzure.StorageMicrosoft.Azure.CosmosDB.Table で型定義を解決できない状況があります。おとなしく WindowsAzure.Storage を使うといいでしょう。

Minimum .Net Standard2.0 対応したいかどうかで判断する

この場合、Microsoft.Azure.Storage を使わざるを得ないでしょう。.NET Standard 1.3 でも .NET Standard 2.0 プロジェクトで利用はできるので、最小バージョンを 2.0 以上にしたいか次第です。

型合わせパズルにはまりたくないかで判断する

WindowsAzure.StorageMicrosoft.Azure.Storage を併用した場合に、型合わせ問題が起こるので結構排他感は残っています。

混在している場合に型合わせパズルをしたくないなら、現状では WindowsAzure.Storage に統一するのが安定しています。

Tips

Microsoft Azure Storage Common は直接参照して使うものではない

nuget パッケージには、https://www.nuget.org/packages/Microsoft.Azure.Storage.Common/ がありますがこれを直接 NuGet で参照してはいけません。これは GitHub でCommonパッケージのフォルダにいくとREADMEで確認できます。(なぜGitHub に.... これではVSなどでパッケージを導入する場合に気付くことは難しいと感じます)

The Microsoft Azure Storage Common SDK for .NET is referenced by Azure Storage Blob/Queue/File SDKs and Azure CosmosDB Table SDK and should not be referenced directly by your application.

https://github.com/Azure/azure-storage-net/blob/master/Common/README.md

Entity定義のためにTableEntityを継承したクラスは利用プロジェクトとプロジェクト分離しない

守らないとつらい原則は1つです。

  • TableEntity の継承クラスは利用プロジェクトと異なるプロジェクトに定義しない

症状は実行時の初期化でアセンブリの解決がうまくいかず即死します。

  • 特定のマイナーバージョン同志で合わせる必要がある
  • 実行時にしか検査できない (Entity の初期化で死ぬのでアプリ実行時に100%起こります)

この2点の特性から「今のバージョンが問題ないか」判断するのが難しく、パッケージバージョンを上げていくことへの障壁となります。NuGet パッケージバージョンを合わせるだけではだめなのが難しいところです。

複数プロジェクトで TableEntity の型定義をしたいときに、この問題を回避する方法はいくつか考えられます。

  • インターフェースでの回避 : インターフェースを共有するプロジェクトで定義して、それぞれの利用プロジェクトで実装
  • テンプレートでの回避 : T4 や他テンプレートを使ってクラスを自動生成、参照させる

新しいcsproj なら .cs を個別に参照かかずに済むので テンプレートでの回避が可能ですが、トリッキーすぎます。 私はインターフェースで互換を担保するにとどめました。

Microsoft.CosmosDB.Table と Microsoft.Cosmos.Table の違い

現在、Microsoft.Azure.Cosmos.Table は現在 preview パッケージですが、CosmosDB に対応した .NET Standard 2.0 バージョンです。

API にも違いがありますが、 .NET Standard への対応が為されていることからMicrosoft.Cosmos.Table の GA がコミュニティからは期待されています。

https://github.com/Azure/azure-cosmos-dotnet-v2/issues/344

preview がとれたら、こちらを使いましょう。ダウンロードがびっくりするほど少なくて不安感が大きいのですが。

まとめ

Stack Overflow や GitHub Issue で現在まで複数件の同様のトラブルが報告されているので、一度は遭遇する印象があります。C# を使う中でも比較的つらいと感じるペインポイントが出ている印象です。

Microsoft の公式パッケージ、かつ Azureの中でももっとも使われやすい機能に関するSDKなので、言語的にもう少しトラブルが少ないと嬉しいと感じます。 混在しないようにパッケージシステムで防げたりするといいんでしょうね。

*1:昨日も苦労したためまとめることにしました