tech.guitarrapc.cóm

Technical updates

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:昨日も苦労したためまとめることにしました

2018年に使ったサービス

2017年から2018年でどうなったのか見てみます。昨年よりも少し細かく書きました。

tech.guitarrapc.com

基本方針は変わりません。

  • 有料・無料で同一程度のサービスがある場合無料のものを選択する
  • 有料のサービスはサブスクリプションで、月ベースでのみ使う

いずれにしても、手間がかかることに対してどこまで時間を省略できるかが鍵なので、不便を感じたら改善していくのです。

生活の中でやり方をサービスに合わせてどんどん変えているので、2017年と2018年でも結構普段の生活が変わってたり。

目次

有料

2017年から継続して有料課金しているサービスと2018年に新たに使い始めたサービスです。

サービス名 価格 種類 タイプ 用途
AWS $3.59/month クラウド 継続 Route53 / Lambda / S3 / KMS
Amazon Prime \3900/year 買い物 継続 ネットショッピング
GitHub Pro $7.00/month DVC 継続 Protected Branch を含めた有料機能 + コントリビューター3名を超えるため
GitKraken $49/year Git GUI 継続 macOS/Windows両方で利用
G Suite Basic \600/user/month オフィス 継続 Gmail / Group / Calendar / Drive & Document / Photo
Jump+ \900/month 雑誌 継続 そろそろおわりかな
TeamsId $3/user/month パスワード管理 継続 チーム管理の楽さが神がかってる
Udemy コース次第 スクール 継続 技術系の動画スクール。微妙
はてなブログ \8434/year ブログ 継続 今使ってるマネージドなブログサービス
マガジンポケット \840/month 雑誌 新規 そろそろおわりかな。新たに新規で追加したサービスです。
Money Forward \500/month 家計簿 新規 キャッシュフロー管理。新たに新規で追加したサービスです。
GCP \500/month クラウド 無料->有料 開発環境。これまで無料でしたが、有料に切り替えました。
Unity Plus \4,200/month 開発 無料->有料 開発環境。これまで無料でしたが、有料に切り替えました。

AWS

主にRoute53と開発環境、検証環境に利用しています。昨年考えていたGCPでのDNS管理は自社の方で行っており、変化や違いを両者で見ています。 設定はTerraform化されておりprivate repository + Atlantis + ngrock on localで運用してます。

Amazon Prime

以前から利用しています。ネットでの購入は原則Amazonに集約させているため、Primeがあるおかげで素早く調達できており助かっています。

GitHub Pro

2013年からずっとです。2019年1月に無料プランでもPrivate Repository を持てるようになりました。 個人アカウントでもProtected Branch などを使っているため今のところ継続しています。

GitKraken

私自身、公私でmacOSとWindowsの両方を使っています。そのためGit GUIはずっと悩ましい問題ですが、2018年はGitKrakenが完全に独壇場でした。OS問わず挙動が安定しており、SourceTreeにあってGitKrakenにない機能はいくつかありますが、SourceTreeのWindowsでの不安定さ、OS間で同じ体験を出来る方が格段にメリットに感じます。

Profileの分離で認証も分離できることもあり、お手伝いをするにあたってもかなり便利に感じます。

唯一感じるのは、GitHub が OAuth App 認証なので認可をしないといけないことでしょうか。

G Suite Basic

個人のSSOは安定してこれです。

やはりGroup の存在が一番大きく、Google認証に対応しているかどうかはサービス選択の基本の条件になりつつあります。

Jump+

読む作品をかなり減らしたものの継続しています。 ワンピースとDr. Stone とアクタージュが面白いです。逆にいうとこれしか読んでいないです。

TeamsId

2018年、終始お世話になりました。認証やデータが突然見えなくなったことがあったのがたまに傷ですが、チームでの管理の面では本当に楽です。

と、いいつつ記事を書いている2019年1月時点で、Bitwarden に乗り換えていますが。

Udemy

あまりみないのですが、AWS 周りで自分が普段から触らないサービスをちらちらみたりしていました。 それぐらいかな? Udemy の Youtube CMが本当に苦手です。

はてなブログ

カスタムドメインのため Pro を1年更新で使っています。

https 対応されたけど、2018年はあまりブログを書かなかったこともあり対応は先日でした。記事を書いていて感じるのですが、はてなブログにわくわくしていない自分を感じます。

マガジンポケット

以前はKindle だったのですが、アプリが出てたので切り替えました。

こちらも3作品程度しかみなくなったので、そろそろかな。

Money Foward

Zaim プレミアムからの乗り換えです。

乗り換えにあたり、Zaimの利用でうまくできなかった買い物フローを見直してシステムに合わせるようにしました。主に2点です。

  • 基本的に決済をクレジット化
  • どうしても現金の場合は現金収支だけつけて明細はオミットする

これでクレジット明細の連動によって、何もしなくてもMoney Forward で財務状態が可視化、トラッキングされます。たまに区分が抜けるのだけつけれておしまいです。

ここまで省力化しないとやらない現実がありました。

GCP

個人のリソースはAWS 多いのですが、会社としてはGCP がメインなのです。 ということで個人の開発環境です。

Unity Plus

2017年にやめたのですが再開です。 この間に Unity Teams Advanced が来てて続けておけばよかった感が半端ないです。

買い切り

サブスクリプションではない買い切りです。サービス以外にアプリやハードもあります。実際のところ書いていないハードは他にもありますが、開発と関連するところはこんな感じで。

昨年からの継続が混じっています。

サービス名 価格 種類 タイプ 用途
Flawless $35/買い切り 開発 継続 iOS開発アプリ (2017年~
Genius Productivity Bundle $8.99/買い切り PDF取り込み 新規 スマホでの事務処理アプリ (2018年~
Charles Proxy \1080 / 買い切り 開発 新規 iPhone でのプロクシ解析アプリ (2018年~
Tile \3,110/個 スマートタグ 新規 なくしたくないもののGPSトラッキング (2018年~
Google Home \15120/個 スマートスピーカー 新規 ok, google (2018年~
Apple TV 4K \19800/個 リモートディスプレイ 新規 macBookの映像だしたりAmazon Prime Video

Flawless

iOS 開発でどうしてもほしくなるのですねぇ。便利。

Genius Productivity Bundle

お仕事の過程で、PDFスキャンとかサインが必要な時に重宝します。 とはいえ丁寧なスキャンが必要な時はスキャナー使っています。

Charlles

iOS開発で便利。iOS 上の Fiddler 的な。

Tile

2018年12月に大事なものをなくすことが生じて、システム的に防止するためにこれにしました。

Mamorio や Qrio Tag 使ってましたが、電池交換できないし誤作動多いしでこっちに切り替えです。

Google Home

一番活躍するのはキッチンタイマーだったり.... Echoではありません

Apple TV 4K

年末に導入しました。

家では MacBook と Windowsを併用しているのですが、MacBook の時にモニターに投影するのがなかなかいかついのです。

duet は安定性の面では日常的につかいたいものではなく、Apple TV で代用することにしました。

あとは、Prime Video を試すことが稀にあります。

無料

サービス名 価格 種類 タイプ 用途
Azure 無料 クラウド 継続 AzureFunctions / AppService / AKS メイン
Azure DevOps 無料 CI 継続 .NET / PowerShell ビルド (Visual Studio Team Service のブランド変更)
Appveyor 無料 CI 継続 .NET/PowerShell ビルド
Circle CI 無料 CI 継続 .NET / Golang / 他 ビルド
draw.io 無料 構成図 継続 構成図を描くのに便利
Docker Hub 無料 コンテナリポジトリ 継続 コンテナ置き場
Eight 無料 名刺管理 継続 名刺はEight で交換しましょ....
Fastly 無料 CDN 継続 検証に Developer Account で。
Google Analytics 無料 Web 継続 ブログとか
Google Adsense 無料 広告 継続 ブログ
Kibela 無料 Wiki 継続 ドキュメント/Wiki/ Blog / 勉強会メモ
Slack 無料 チャット 継続 個人のいろいろストックです
Wunderlist 無料 TODO 継続 TODOの管理
一休 無料 予約 継続 レストラン、ホテル他
IFTTT 無料 グルー 継続 連動系はこっち
Zapier 無料 グルー 継続 連動系の検証に
duet 無料 リモートディスプレイ 継続 MacBook のディスプレイ拡張
LinkedIn 無料 プロフィール 新規 紹介
Wantedly 無料 プロフィール 新規 紹介
JapanTaxi 無料 タクシー 新規 困ったときに便利
Integromat 無料 グルー 新規 インスタントは処理はこっち
ネットワークプリント 無料 プリンター 新規 コンビニプリント

Azure

主に技術検証や無料内でのサービス利用なので無料枠で足りてるのですが、Subscription が切れたのでどうしようかなです。

Azure DevOps

Visual Studio Team Service からのリブランドですね。

Windows 系のビルド検証に使ってたのですが、YAMLサポートされたので楽になりました。無料以上は使わないです。

Appveyor

去年書きませんでしたが使ってはいます。

.NET 系のツールが入ったイメージでビルドできるので確かに楽ではありますが、癖が強くてなかなかアレ。

Circle CI

主たるビルドサービスは安定して CircleCI です。

draw.io

構成図はこれで。GitHub や Google Drive で保存しています。

Docker Hub

コンテナ置き場、2018年はほそぼそいろいろ起きました。

Eight

Eight で名刺交換って言われたことないんですけど、どうなってるんでしょうか。(Eightで名刺交換したいといわれたら好感度めっちゃ高いです)

Fastly

CDNだいじだいじ

Google Analytics

ブログの閲覧はこれ。

あと、G Suites の管理画面もとってます。

Google Adsense

試しに設定して放置しています。

Kibela

esa から乗り換えて一年、デザイン/UX変更が大きくなかなか考えるものがあります。

サービスの進化やロードマップ見ると、あんまり魅力を感じなくなりつつあるのですが、どうしようか考えています。

Slack

基本的な通知ややりとりはこっち。

Wunderlist

なくならないのかしら? Microsoft TODO に乗り換えかなぁ

一休

たまに使うたびに重宝します。

IFTTT

Slack への連動はこの子です。

Zapier

会社では使っているので、主に検証用途です。

duet

MacBook でディスプレイを簡単に拡張するのに使っています。

LinkedIn

昨年はいろいろ活動してたので LinkedIn は重宝しました。 だいたい海外や転職サイト、エージェントからばかりです。

国内はほぼ0。

Wantedly

いろいろ見てましたが、ちょっとどうなんだろう.....? かなり慎重に見ています。

メッセージ時々いただくのですが、反応しないのは、良くない話しか聞かない/私自身そういう経験したからです。

カジュアルに、「話を聞きに行く側」と「話を聞かれにきた側」で認識の差を感じます。

とはいえ、私もカジュアルな話という時に逸脱した経験あるので、悩ましいのかな。

JapanTaxi

困ったときにタクシーいるのかしら確認に便利です。 あと急いているときにしかタクシーのらないので、先に支払っておけるのはすばら。

Integromat

Integromat + IFTTT が基本です。

tech.guitarrapc.com

ネットワークプリント

うちにプリンターをおきたくないので、Google Drive から 転送してコンビニプリントしています。

無料継続(利用がなかったもの)

いらないというより、使う機会が乏しい

サービス名 価格 種類 タイプ 用途
Heroku 無料 PaaS 継続 アプリホスト
Visual Studio App Center 無料 CI/Analytics 継続 開発
Cloud Craft 無料 構成図 継続 3D系を書かなかった
inVision 無料 デザイン 継続 プロトタイプ前でした

Heroku

Heroku でホスティングしたいものがなかった。

Serverless で実装がおおいので自然と減ります。

Visual Studio App Center

仕事ではたまに使うのですが、昨年は個人では使いませんでした。

Cloud Craft

Draw.io に活躍の場が奪われています。

inVision

2017年は触ってたのですが、2018年は気付くと使わなかったです。

解約

サービス名 価格 種類 タイプ 用途
Zaim Premium ¥360/month 家計簿 切り替え 手作業が入っていたのがよくなかった
News Picks \1500/month ニュース 解約 始めはおもしろかったのに
Qrio SmarTag \4298/個 スマートタグ 切り替え 落として壊れた、アプリとの連動がよくなかった、電池が...
Mamorio \4298/個 スマートタグ 切り替え 落として壊れた、電池が...
Google Play Music \900/month 音楽ストリーミング 解約 Google Home で入ってみたのですがいらなかった
Grammarly 無料 文法チェック 解約 英語の文法チェック

Zaim Premium

Zaim本当に面倒で、特にレシートの取り込むとかは嫌でした。

現金での支払いが求められる店舗は本当に多く、この明細を把握することが負担の原因だったように思います。

クレジットを99% 前提にすることで基本的に確認不要、現金はおろした時点で決め打ちにすることでざっくりですが手間なしでの管理に倒すべきでした。ということでこれをMoney Fowardで実践して昨年はストレスなしでした。

News Picks

知人が読んでいる内容が面白くいくつか見てましたが、2018年4月ごろから面白い記事が減り、開くことが減ったため解約しました。

Qrio SmarTag

欲しいのはタグとアプリ(スマホ) が離れたときのアラートではなかった感があります。

GPS でのトラッキングがしっかりされていて、通知だけくればいいのだなぁと。

ちょくちょくアプリからみえなくなってたのもふむ。

落として壊れたのでさようなら。

Mamorio

Qrio とほぼ同様です。

こちらも落として壊れたのでさようなら。

Qrio も MAmorio も、普段開かないけど開いた時に位置がロストしていたりログアウトされてるので致命的に感じます。

Google Play Music

Google Home で音楽をお願いするときに、Spotify か Google Play Music なのですが、結局音楽をGoogle Home でお願いしないのでやめました。

使ってたのですが、音楽ストリーミング自体あまり好んでおらず、たいがい辞めています。

Grammarly

ほぼ機能しなくなったのでさようなら。またどこかで使いたいです。

今後使う可能性があるサービス

サービス名 価格 種類 タイプ 用途
Bitwarden 無料 パスワード管理 切り替え マルチプラットフォームでの動作
Kyash 無料 ウオレット 新規 新規 | 割り勘時かな
Lingvist 無料 英語 新規 リハビリ
paypay 無料 QR決済 新規 WeChat的な決済だけのやつ

Bitwarden

2018年12月にみてて切り替えを検討し、2019年早々に切り替え完了しています。

tech.guitarrapc.com

Kyash

割り勘の機会が少ないので、今のところ必要性にかられないのですがあってもいい感じがします。

Lingvist

英語は使わないと忘れるので。 この間、いろいろ出てこなくて焦りました。

paypay

触らずに避けています。どうしようかな?

検討して使わなかったサービス

サービス名 価格 種類 タイプ 用途
Flow $4.79 タスク管理 新規 タスク管理
Microsoft Flow グルー 無料 新規 IFTTT + Integromat で十分
Polca 無料 ファンディング 新規 フレンド間のファンディング
DropBox Paper \1000/month Wiki 新規 Paper だけ使いたい
ScrapBox 無料 Wiki 新規 とっちらかりすぎた

Flow

GitHub Projects で十分なので、今はいらない感。

Microsoft Flow

むやみと複雑なので好みではないです。

Polca

こういうのとりあえず触ってみるんですが、ちょっと身内感すぎて微妙でした。

DropBox Paper

触り心地は良いのですが、Drop Box を避けている (G Suites と重複してる) ので、巻き込まれるのがいやです。 単品サービスなら使うのですが。

Paper を使うたいという欲求に対して高いと感じます。

www.dropbox.com

ScrapBox

Wikiというよりは、感覚的に落書きやホワイトボードのため込みです。

使い勝手いいのですが、まとめたい、関連したものを集約したいというときにちょっと悩ましいというか、考えることになりどうしようかと考えています。

物自体は同時編集できるので理想的なので、また使うかも。

.NET Core の Generic Host で WebJobs を利用する

Generic Host を使った場合でも、これまで .NET Core で書いてきた処理は問題なく組み込むことができます。 最近 Azure WebJobs を Generic Host で使う機会があったので見てみましょう。

※ 社内向けブログの転載なのでシリーズ化します。

目次

TL;DR;

Azure Storage Blob/Queue/Table などの更新を受けて処理を書くために自前でポーリングを書くのはそれなりに大変です。 これを担保するWebJobs という仕組みがすでにあります。これを使ってAzure Functions を書くのと同じ感覚で実装が追加できます。

ここでは.NET Core のコンソールアプリでWeb Jobs を追加して実装する方法を見てみましょう。

事前に読んでおきたい

tech.guitarrapc.com

WebJobs の追加

HostBuilder にWebJobsを追加してみましょう。

nuget パッケージの追加

HostBuilder にWebJobs 処理を追加するため、次のnugetパッケージを追加しておきます。

    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.2" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Logging.ApplicationInsights" Version="3.0.3" />

HostBuilder を組む

nugetパッケージを追加したことで、これで次のように AzureStorage をトリガーとした処理をかけます。

class Program
{
    static async Task Main(string[] args)
    {
        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureServices(services =>
            {
               // サービス処理のDI
                services.AddSingleton<IFooService, FooService>();
            })
            .ConfigureLogging((context, b) =>
            {
                b.SetMinimumLevel(LogLevel.Debug);

               // Console ロガーの追加
                b.AddConsole();

                // NLog や Log4Net、SeriLog などを追加

                // あるいはApplication Insight の追加
            })
            .RunConsoleAsync()
    }
}

StorageのConnection Strings を設定ファイルに組む

コード上にべた書きするとビルド環境ごとの切り替えや差し替えが面倒ですし、リポジトリにあげるのもまずいでしょう。 Generic Host では、appsettings.json や任意のjson/xml などを読み込むことができます。

もちろんMSI を使えば ConnectionStrings も不要になるので、検討するといいでしょう。

ここでは、appsettings.<環境>.json を読むように組んだので、appsettings.Development.json にコネクションを追加しておきましょう。

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net",
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net"
  }
}

WebJobs のトリガーを書く

感覚的には、AzureFunctions のトリガーを書くのと同じです。

IFooService がDIされているので、これをコンストラクタで受けるFooFunctionクラスを書いてみましょう。

    [ErrorHandler]
    public class FooFunction
    {
        private readonly IFooService service;

        public FooFunction(IFooService service)
        {
            this.service = service;
        }

        [Singleton]
        public async Task QueueTrigger([QueueTrigger("yourqueue")] string queueItem, ILogger log)
        {
            log.LogInformation($"C# Queue trigger function processed: {queueItem}");
            // 何か処理
        }
    }

どうでしょうか? ごくごく普通のAzureFunctions な感じで書けて、Queue のポーリングも不要です。

同様の感覚でcronもかけたりするので、Queue を受けてXXXな処理をするというケースでは、WebJobs をベースすると楽でしょう。

まとめ

Generic Host でも Webjobs を組めるということは、Azure Storage をはじめとした Azure Functionsでやるような処理を自前でハンドルしたいときに、ホスティング環境を問わないということになります。 もし今後 .NET Core + WebJobs をする場合、これがデファクトになるんじゃないでしょうか。

Tips

HostBuilderの初期化時に、注意があります。 必ず ConfigureWebJobsConfigureAppConfiguration の前に置きましょう。初期化できずに死にます。

ok

        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })

no

        await new HostBuilder()
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })

Ref

Introducing Windows Azure WebJobs WebJobs in Azure with .NET Core 2.1 Azure/azure-webjobs-sdk SanderRossel/netcore-webjob