読者です 読者をやめる 読者になる 読者になる

C#7のローカル関数(Local Function)とは何か

C#

C# 7には「ローカル関数(Local Function)」という機能が追加されました。

関数(メソッド)の中に関数を定義できるというものです。

以下は(処理的には何の意味も持ちませんが)ローカル関数を使った例です(リスト1)。
TestClass1クラスのTestMethod1()メソッド内に定義した「innerFunc(int n)」がローカル関数です。

// リスト1
// ローカル関数を利用
namespace TestNs
{
  public class TestClass1
  {
    public int TestMethod1(int num)
    {
      // ローカル関数
      int innerFunc(int n) {
        int i = n * 10;
        return i;
      };
      
      var result = innerFunc(num);
      return result;
    }
  }
}

ローカル関数を利用する1つのケースは、「特定のメソッド内からしか呼び出されない処理をローカル関数として定義する」ことです。
privateメソッドとして切り出しても良いのですが、同クラス内の別メソッドから予期せず呼び出されるリスクを持ちます。
ローカル関数を使用しない(privateメソッドに切り出す)実装は以下の通りです(リスト2)。

// リスト2
// 従来の実装
namespace TestNs
{
  public class TestClass2
  {
    public int TestMethod2(int num)
    {
      var result = this.InnerFunc(num);
      return result;
    }

    // TestMethod2からしか呼び出されないメソッド
    private int InnerFunc(int n)
    {
      int i = n * 10;
      return i;
    }
  }
}

MSILはどうなっているのか?

では、視点を変えまして・・・
リスト1をコンパイルした「MSIL(Microsoft Intermediate Language)」は一体どういったものでしょうか?
ildasm.exeを使って、リスト1をコンパイルしたアセンブリ(DLL)を見てみましょう。
TestMethod1()のMSILは以下の通りです(リスト3)。

// リスト3
.method public hidebysig instance int32  TestMethod1(int32 num) cil managed
{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init ([0] int32 result,
           [1] int32 V_1)
  IL_0000:  nop
  IL_0001:  nop
  IL_0002:  nop
  IL_0003:  ldarg.1
  IL_0004:  call       int32 TestNs.TestClass1::'<TestMethod1>g__innerFunc1_0'(int32)
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  stloc.1
  IL_000c:  br.s       IL_000e
  IL_000e:  ldloc.1
  IL_000f:  ret
} // end of method TestClass1::TestMethod1

ローカル関数を「TestNs.TestClass1::‘ginnerFunc1_0'」として呼び出しています。
「TestNs.TestClass1::’g
innerFunc1_0'」実装のILは、以下の通りです(リスト4)。

// リスト4
.method assembly hidebysig static int32  '<TestMethod1>g__innerFunc1_0'(int32 n) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       12 (0xc)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] int32 V_1)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  mul
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  stloc.1
  IL_0008:  br.s       IL_000a
  IL_000a:  ldloc.1
  IL_000b:  ret
} // end of method TestClass1::'<TestMethod1>g__innerFunc1_0'

staticメソッドとして定義されています。またpublic / private等のアクセス修飾子がついていません。つまり internal なメソッドということです。
上記より、リスト4は 以下のC#実装と同意となります(リスト5)。

// リスト5
namespace TestNs
{
  public class TestClass1
  {
    public int TestMethod1(int num)
    { 
      var result = TestClass1.innerFunc(num);
      return result;
    }

    internal static int innerFunc(int n)
    {
      int i = n * 10;
      return i;
    }
  }
}

ローカル関数は「internal staticメソッド」と解釈された

つまりC#コード上での ローカル関数 は、C#コンパイラを通してMSILとなる段階で「internal staticメソッド」として解釈されていました。

が!!!!!

必ずしも「internal staticメソッド」となるわけではありません。

ローカル関数がインスタンスプロパティを参照している場合

はい、ということで以下のようなケースではどうでしょうか?(リスト6)

// リスト6
namespace TestNs
{
  public class TestClass1
  {
    private int propValue = 111;

    public int TestMethod1(int num)
    {
      // ローカル関数
      int innerFunc(int n) {
        // インスタンスプロパティを参照している
        int i = n * this.propValue;
        return i;
      };
      
      var result = innerFunc(num);
      return result;
    }
  }
}

先程と同様にリスト6をコンパイルしたアセンブリ(DLL)のinnerFunc()部のMSILを確認してみます(リスト7)。

// リスト7
.method private hidebysig instance int32 
        '<TestMethod1>g__innerFunc1_0'(int32 n) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       16 (0x10)
  .maxstack  2
  .locals init ([0] int32 i,
           [1] int32 V_1)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldarg.0
  IL_0003:  ldfld      int32 TestNs.TestClass1::propValue
  IL_0008:  mul
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  stloc.1
  IL_000c:  br.s       IL_000e
  IL_000e:  ldloc.1
  IL_000f:  ret
} // end of method TestClass1::'<TestMethod1>g__innerFunc1_0'

メソッドの実装が「internal static」から「private」に変わりました。
そう、ローカル関数内の実装が「クラスのインスタンスプロパティを参照する実装」に変わった為、ローカル関数はインスタンスメソッドとして解釈されるようになりました。

まとめ

C# 7のローカル関数とは「C#言語の仕様 」あり「MSILレベルの仕様ではない」ということです。
C#コンパイラによって解釈され、MSILでは従来通りの.NETの実装となる。
なんか、ネガティブっぽい言い方になっていますが、最近のC#言語仕様の多くは(ほとんどは?)、C#コンパイラが解釈する仕様となっています。dynamicなんかはすごいMSILに解釈されますしね。

ということで、「知っても知らなくてもアプリは作れるよ!」な内容の投稿でした^^;

※本投稿では ildasm.exe を使って MSIL を直接確認しましたが、Reflectorなんかを使って C#コードに逆コンパイルしたコードを確認するとより読みやすいと思います。

Azure FunctionsをVS2017でローカル実行&デバッグしてパブリッシュする

Azure

MSDNブログ(↓↓↓)で Azure Functions 関連の記事を見たので自分でやってみました。

blogs.msdn.microsoft.com

上記ブログに記述されている事、そして今回自分で試した事を要約すると以下の内容です。

  1. Azure FunctionsをVisual Studio 2017で開発&デバッグする(つまりローカル実行するということ)
  2. Azure Functionsをクラスライブラリとしてビルド&パブリッシュする

1 は、まあ 開発効率を上げるということですね。
Azure Functions自体はAzureポータルでコーディングできますが、本格的実装はローカルで行いたいですよね。

2 は、クラスライブラリとしてアセンブリ(dll)にしてデプロイすることで実行パフォーマンスを上げる意味を持ちます。
Azure Functionsは5分間非アクティブであるとアイドル状態となり、次の実行時には再度メモリ上に展開されます。C# / JavaScriptコードがデプロイされている場合、アセンブリへのコンパイル作業から再実行され起動パフォーマンスが落ちてしまいます。

では、以下に手順を・・・

ちなみに、Visual Studioとの統合ツール「Visual Studio Tools for Azure Functions」がまだ 2017 対応できていないらしく、そのため以下のような手動による操作がいくつか発生します。でも、あと数週間もすればツールがリリースされるみたいです。

Azure Functions CLI のインストール

Azure Functions CLI をインストールします。
以下のnpmコマンドでグローバル領域にインストールします。

npm i -g azure-functions-cli

www.npmjs.com

Visual Studio 2017 でWebプロジェクトを作成

Visual Studio 2017を起動します。
メニュー「ファイル → 新規作成 → プロジェクト」を選択します。
プロジェクトテンプレートは「Web → ASP.NET Web アプリケーション(.NET Framework)」、プロジェクト名はここでは「AzureFunctionsWeb」としました。

f:id:daigo-knowlbo:20170323130918p:plain

「空」テンプレートを選択します。

f:id:daigo-knowlbo:20170323130947p:plain

プロジェクトのプロパティを表示します(ソリューションエクスプローラからプロジェクトをマウス右ボタンクリックメニューで「プロパティ」を選択)。

f:id:daigo-knowlbo:20170323131619p:plain

Webタブを選択し、以下の設定を行います。
* 「外部プログラムを起動する」を選択。
* パスは「C:\Users\「【ユーザー名】\AppData\Roaming\npm\node_modules\azure-functions-cli\bin\func.exe」とする。
* 「コマンドライン引数」は「host start」とする。
* 「作業ディレクトリ」は【プロジェクトフォルダ】とする。

設定を行った画面は以下の通り。

f:id:daigo-knowlbo:20170323132050p:plain

Azure Portal上で Azure Functions を作成

Visual Studio Tools for Azure Functionsがリリースされれば、VSからプロジェクトを生成できると思いますが、それが今はないのでポータルで作成して、ローカルVSにベースとなるソースを落としてきます)

Azure Functions 作成画面を表示

https://functions.azure.com/signinをブラウザで開きます。
「New function app」の「Name」に作成するファンクション名を入力します。ここでは「RyuichiFunction0916」としました。
「Region」は「Japan East」としました。
「Create + get started」をクリックします。

f:id:daigo-knowlbo:20170323192122p:plain

「WebHook + API」「C#」を選択し、「この関数を作成する」ボタンをクリックします。

f:id:daigo-knowlbo:20170323192411p:plain

作成完了。

f:id:daigo-knowlbo:20170323192506p:plain

Azure Functions ソースのダウンロード

左下の「Function App の設定」をクリックします。

f:id:daigo-knowlbo:20170323192544p:plain

「Kuduに移動」をクリックします。

f:id:daigo-knowlbo:20170323192731p:plain

「site」を選択します。

f:id:daigo-knowlbo:20170323192817p:plain

「wwwroot」のダウンロードアイコンをクリックします。

f:id:daigo-knowlbo:20170323193106p:plain

ダウンロードしたzipのイメージは以下の通り。

f:id:daigo-knowlbo:20170323193250p:plain

WebプロジェクトにFunctionsソースを追加

先程作成したWebプロジェクト(AzureFunctionsWeb)フォルダにzipの内容を解凍します。

f:id:daigo-knowlbo:20170323193453p:plain

追加のファイルをプロジェクトに含めます。
また「run.csx」ファイルを「run.cs」にリネームします。

f:id:daigo-knowlbo:20170323193742p:plain

run.csを以下のように修正します。
run.csxでは単純に run() だけが定義されていますが、csコードにする為「名前空間・クラス」でくくります。
また必要な using を追加します。
レスポンス文字列も若干修正を加えました。

// run.cs
using Microsoft.Azure.WebJobs.Host;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace AzureFunctionsWeb
{
  public class ExampleFunction
  {
    public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
    {
      log.Info("C# HTTP trigger function processed a request.");

      // parse query parameter
      string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

      // Get request body
      dynamic data = await req.Content.ReadAsAsync<object>();

      // Set name to query string or body data
      name = name ?? data?.name;

      return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, 
                             "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK,
                             "Hello " + 
                              name + 
                              ". I'm assembly!!");
    }
  }
}

プロジェクトにNugetパッケージを追加

Nugetで以下のパッケージを追加します。

function.jsonの調整

function.jsonに「scriptFile」「entryPoint」キーを追加します。
scriptFileにはアセンブリ名を、entryPointには名前空間/クラス付きのRun()メソッドの完全名を記述します。

// function.json
{
  "scriptFile": "..\\bin\\AzureFunctionsWeb.dll",
  "entryPoint": "AzureFunctionsWeb.ExampleFunction.Run",
  "disabled": false,
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ]
}

ローカル実行

Visual StudioでF5クリックにより実行を行います。
以下のようなコマンドプロンプトが表示され Function がリクエスト待ち(実行待ち)状態となります。

f:id:daigo-knowlbo:20170323195843p:plain

コマンドプロンプト表示上に、以下の記述を確認することができます。

Job host started
Http Function HttpTriggerCSharp1: http://localhost:7071/api/HttpTriggerCSharp1

このURLがFunctionの受け口のURLになります。

サンプル(ExampleFunction)は「name」というキーをパラメータとして受け取ります。その為、以下のURLをブラウザでアクセスしてみましょう。

http://localhost:7071/api/HttpTriggerCSharp1?name=daigo

Functionが実行され以下のようなレスポンスが得られます。

f:id:daigo-knowlbo:20170323200130p:plain

ブレークポイントもきちんと止まります。

f:id:daigo-knowlbo:20170323200300p:plain

Azure FunctionsへのPublish

次に、ローカルで実装を行った Function を AzureにPublishします。
コマンドプロンプトを起動します。

作成したWebプロジェクトフォルダ(F:\Projects\Research\AzureFunctionsWeb\AzureFunctionsWeb)に移動します。

以下のコマンドを実行してCLIからAzureにログインします。

func azure login  

Azureへのログイン画面が表示されるので、ログインを行います。

f:id:daigo-knowlbo:20170323200613p:plain

以下のコマンドで、現在PublishされているFunctionの一覧を確認します。

func azure functionapp list

現在の私の環境では2つのFunctionがPublishされていました。
RyuichiFunction0916の方が、今回作成したものです。

f:id:daigo-knowlbo:20170323200744p:plain

以下のコマンドによりFunctionをローカルからAzureへPublishします。
(RyuichiFunction0916部分は適時 Function 名に置き換えます)

func azure functionapp publish RyuichiFunction0916

正常にPublishされた結果画面が以下になります。

f:id:daigo-knowlbo:20170323200919p:plain

Azure Functionsを実行

Azureポータルで「RyuichiFunction0916」を表示。
「開発」タブを選択、「テスト」タブを選択、「要求本文」をテスト文字列に修正、「実行ボタン」クリック、を行います。
以下のように「出力」に「"Hello Daigo. I’m assembly!!“」が表示されました。
VS2017上で実装したロジックが実行されていることが確認できます。

f:id:daigo-knowlbo:20170323202035p:plain

まとめ

VS2017用「Visual Studio Tools for Azure Functions」がリリースされれば、上記のようなゴニョゴニョした作業はバックエンドで自動化されると思います。ただ、CLIなんかで、裏の動きを知っておくのは良いことだと思っています。
また、Azure functionsやマイクロサービスについては、私自身はまだ実運用の場面では使ったことがないので、色々な知見を経験者の方から学びたいと思っております。
WebHookを絡めてGithubだったりSlackだったりと連携する実装なんかも比較的容易に出来るようなので、色々試してみたいと思います。

Azure VM 2台 を超簡単にLoad Balancer構成してみた

Azure

先日、Azureの障害祭りがあったということで・・・というわけではないですが(笑)
2台構成の Azure Virtual Machine(高可用性)を、「超簡単に」組むというのをやってみました。

また、今更どうこう言うまでもなく ResourceManager ベースになってから、Azureの構成は、よりオートメーション化が可能になっています。

Azure QuickStart Template

GithubのAzure QuickStart Templateというところに、かなり使えるテンプレート(json構成ファイル)がたくさん落ちています。

github.com

もう山のようにいろんなテンプレートが落ちていますね。
これらをベースにjsonファイルいじって、自分の必要とする構成をカスタムに構成すれば工数の削減につながりますね。
また、ドキュメント読んだだけじゃ、よくわからなかったなぁ・・というようなものも、実際に動くテンプレートサンプルを使うと非常に理解しやすいですね。

2台構成&LoadBalancerのテンプレート

上記 Azure QuickStart Template の中から今回使うのは「2台構成&LoadBalancerのテンプレート」ということで以下になります。

github.com

上記URLを開くと、親切にも以下の赤枠にあるように「Deploy to Azure」というリンクが用意されています。

f:id:daigo-knowlbo:20170321012755p:plain

リンク先は以下の通りで、Githubに置いてある azuredeploy.json を Azureポータルに食わせるリンクになっています。

https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F201-2-vms-loadbalancer-lbrules%2Fazuredeploy.json

デプロイ

「Deploy to Azure」リンクをクリックすると、Azure Portalに移動し、構成を組むためのパラメータの入力画面になります。
パラメータを適当に埋めます。

f:id:daigo-knowlbo:20170321013359p:plain

ここではリージョンは西日本にしました。
また、VMサイズは、このテンプレートではデフォルトで Standard_D1 になっています。
「購入」ボタンをクリックしましょう。

「リソース グループ」を確認すると、「Win2VM(先程のパラメータで入力したリソースグループ名)」が作成されています。

f:id:daigo-knowlbo:20170321014136p:plain

VMが作成されるまで数分かかると思うので、作成が完了するまで待ちましょう。

構成を確認する

Win2VMリソースグループは以下のような構成です。

f:id:daigo-knowlbo:20170321014104p:plain

以下のような、2台のVMロードバランサーする最低限の構成がしっかり組まれています。

  • 可用性セット
  • ロードバランサー
  • パブリックIP
  • Virtual Machine + Disk + NIC 2つ
  • 仮想ネットワーク
  • ストレージアカウント

myAvSet(可用性セット)

myAvSet(可用性セット)は、以下のような構成になっています。

f:id:daigo-knowlbo:20170321014307p:plain

障害ドメインは2つ、更新ドメインも2つ、それぞれのドメインに対してVMが割り当てられています。
AzureポータルのGUI上から可用性セットを作成すると「障害ドメイン 2つ、更新ドメイン 5つ」がデフォルトになっています。
この構成JSONをベースにして3台以上の構成をとる場合には適時 障害ドメイン及び更新ドメインを拡張する必要があります。
更新ドメインが2つのまま3台目のVMを追加すると、1つ目のVM(myVM0)と同一更新ドメインに配置されてしまいます。つまり、メンテナンス時に2つのVMが同時に再起動される可能性が発生してしまいます。

ロードバランサーのプローブ

ロードバランサーのプローブは、TCPでポート80、5秒ごとのチェック、以上閾値は2、で構成されています。

f:id:daigo-knowlbo:20170321014856p:plain

Virtual Machine のIISを構成

作成された Virtual MachineにはまだIISが構成されていません。
2つのVMにそれぞれIISを構成しましょう。
myVM0 を選択し、「接続」をクリックするとリモートデスクトップ接続ファイルがダウンロードされます。

f:id:daigo-knowlbo:20170321015116p:plain

通常のWindows Serverへのリモートデスクトップになりますので、IISを構成しましょう。

まあ、例のコレですね・・・(途中端折ります)

f:id:daigo-knowlbo:20170321015434p:plain
f:id:daigo-knowlbo:20170321015544p:plain
f:id:daigo-knowlbo:20170321015552p:plain
f:id:daigo-knowlbo:20170321015602p:plain

※myVM1にも同様にIISを構成しましょう。

LoadBalancerの動きを確認する

これでクライアントからのHTTPリクエストに対して、「2台に負荷分散&1台に障害が発生したらもう1台に振り分け」という高可用性なサーバー構成が完了しました。

本当に障害発生時にもう片方にリクエストが振り分けられるかどうか確認してみます。
myVM0 の c:\inetpub\wwwroot に、以下の index.html を配置します。

Hello. I'm myVm0.

myVM1 の c:\inetpub\wwwroot には、以下の index.html を配置します。

Hello. I'm myVm1.

URL(index.html)にアクセスるする

では、作成した index.html にアクセスしてみましょう。
ブラウザで、URLにアクセスします。
URLのIPアドレスロードバランサー(myLB)の「パブリックIPアドレス」で確認することができます。
ここでは「52.175.152.10」でした。

f:id:daigo-knowlbo:20170321020634p:plain

http://52.175.152.10」にブラウザでアクセスすると・・・私の環境では myVM0 につながりました。

f:id:daigo-knowlbo:20170321020711p:plain

では myVM0 のIISを停止してみます。

f:id:daigo-knowlbo:20170321020901p:plain

再び「http://52.175.152.10」をアクセスすると・・・

f:id:daigo-knowlbo:20170321020957p:plain

myVM1 につながりました!OKです!

まとめ

一昔前はクラスタリング構成のサーバーを組む(もしくは実証試験する)なんて、物理サーバー2台用意して、ケーブルつないで、OSメディア用意して・・・なんて大ごとでしたが、Azureのようなクラウド環境を利用するとサクサクっと社会人1年目でも簡単に組めちゃう素晴らしき時代ですね!
ただ、そのベースにある技術を理解しておくことが重要だと思います!(って人に言える程、私自身が有識者ではないが・・・)

C# 7 の Tuple は戻り値が複数の時に便利

Visual Studio 2017 C#

2017/3/11、「Visual Studio 2017 リリース記念勉強会 & まどすた #2」に参加してきました。
1コマ目のセッションとして、岩永 信之(@ufcpp)さんの「C# 7」セッションを聞かせていただきました。
C# 7の新機能について非常に細かく説明していただきましたが、その中で Tuple についてフォーカスしてブログっておきます。

※以下は、セッションでの解説内容ではなく、それを受けて私が調査した内容になります。

Tuple は戻り値が複数の時に便利

ということで、C# 7 で使い勝手がよくなった Tuple ですが、複数の戻り値があるメソッドで使うのが最も美しい使い方だと思いました。
(Tupleは乱用すると見通しの悪い、属人 巧 のコードが生成される可能性があると思っているので)

では、以下のCompanyクラスをベースに説明を進めます。

// 会社クラス
public class Company
{
  ... 正社員数とパートナー社員数を取得するメソッドを追加していくよ!
}

2つの戻り値を持つ同期メソッドの場合

Companyクラスに以下のメソッドを追加します。

[リスト1]
// Companyクラスに追加するメソッド
// outパラメータを使用して2つの戻り値を返す同期メソッド
static public void GetEmployeeCountSync(
  out int properCount, 
  out int partnerCount)
{
  properCount = 100;
  partnerCount = 30;
}
// リスト1の呼び出し側
Company.GetEmployeeCountSync(out int properCount, out int partnerCount);
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);

// 出力結果:proper:100  partner:30

2つの戻り値があるので、outパラメータを使うというよくある実装です。
※呼び出し側実装における、outパラメータを事前宣言せず、メソッド呼び出し時に宣言する手法はC# 7の新機能です。

次にリスト1を Tuple戻り値 を使って書き換えます。

[リスト2]
// Companyクラスに追加するメソッド
// Tupleを使用して2つの戻り値を返す同期メソッド
static public (int properCount, int partnerCount) GetEmployeeCountSyncTuple()
{
  int properCount = 100;
  int partnerCount = 30;

  return (properCount, partnerCount);
}
// リスト2の呼び出し側
(int properCount, int partnerCount) = Company.GetEmployeeCountSyncTuple();
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);
// 出力結果:proper:100  partner:30

または、以下のように呼び出すこともできます。

// リスト2の呼び出し側
var result = Company.GetEmployeeCountSyncTuple();
Console.WriteLine("proper:{0}  partner:{1}", result.properCount, result.partnerCount);
// 出力結果:proper:100  partner:30

戻り値の Tupleオブジェクト を分割して各々の変数に受け取るか、まとめて受け取るかの違いになります。

2つの戻り値を持つ非同期メソッドの場合

この非同期メソッドのケースで Tuple戻り値 の威力がより発揮されると思います。
非同期メソッド、つまり asyncキーワードの付いたメソッドでは、「引数に out / ref キーワードをつけることができません」。これは言語仕様としてそのようになっています。

[リスト3]
// これはコンパイルエラー
static public async Task GetEmployeeCountAsync(
  out int properCount,  // asyncなのにoutはNG
  out int partnerCount)
{
  properCount = 100;
  partnerCount = 30;

  return;
}

その為、複数の戻り値を持つ必要がある場合、それらを集約したクラスを定義する必要があります。
例えば以下のような感じ。

[リスト4]
public class CountResult
{
  public int ProperCount { get; set; }
  public int PartnerCount { get; set; }
}
...
static public async Task<CountResult> GetEmployeeCountAsync()
{
  CountResult result = new CountResult() { properCount = 100, partnerCount = 30 };
  // ...
  return result;
}

しかし、Tupleを利用すれば同期メソッドと同様の実装方式をとることができます。

[リスト5]
// Companyクラスに追加するメソッド
// Tupleを使用して2つの戻り値を返す非同期メソッド
static public async Task<(int properCount, int partnerCount)> GetEmployeeCountAsyncTuple()
{
  int properCount = 100;
  int partnerCount = 30;

  await Task.Delay(1000);

  return (properCount, partnerCount);
}
// リスト5の呼び出し側
(int properCount, int partnerCount) = Company.GetEmployeeCountAsyncTuple().Result;
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);

// 出力結果:proper:100  partner:30

戻り値がオブジェクト型の場合

前述の例では戻り値の型がリテラル型でした。
オブジェクト型の場合は、非同期メソッドにおいても引数渡しで結果を受け取ることが出来ます(C# 7に関係なく従来の言語仕様的に)。
その部分の振る舞いを見てみたいと思います。

[リスト6]
static public async Task GetEmployeesAsync(
  List<Employee> properEmployees, 
  List<Employee> partnerEmployees)
{
  for (int i = 0; i < 10; i++)
  { // 1秒毎にEmployeeを追加
    await Task.Delay(1000);
    properEmployees.Add(
      new Employee() { Name = "proper" + i.ToString() });
  }

  for (int i = 0; i < 5; i++)
  { // 1秒毎にEmployeeを追加
    await Task.Delay(1000);
    partnerEmployees.Add(
      new Employee() { Name = "partner" + i.ToString() });
  }

  return;
}
// リスト6の呼び出し側
List<Employee> properEmployees = new List<Employee>();
List<Employee> partnerEmployees = new List<Employee>();
var task = Company.GetEmployeesAsync(properEmployees, partnerEmployees);
while (true)
{
  Console.WriteLine("{0} proper:{1}  partner{2}", 
    DateTime.Now.ToString("H:m:s"), 
    properEmployees.Count, 
    partnerEmployees.Count);

  System.Threading.Thread.Sleep(1000);

  if (task.IsCompleted) // Taskの終了を確認
    break;
}

// 出力結果:proper:100  partner:30
17:21:5 proper:0  partner0
17:21:6 proper:1  partner0
17:21:7 proper:2  partner0
17:21:8 proper:3  partner0
17:21:9 proper:4  partner0
17:21:10 proper:5  partner0
17:21:11 proper:6  partner0
17:21:12 proper:7  partner0
17:21:13 proper:8  partner0
17:21:14 proper:9  partner0
17:21:15 proper:10  partner0
17:21:16 proper:10  partner1
17:21:17 proper:10  partner2
17:21:18 proper:10  partner3
17:21:19 proper:10  partner4

非同期メソッド GetEmployeesAsync() を呼出し後、whileループで引き渡したEmployeeオブジェクトを監視&コンソール出力しています。
properEmployees.Count / partnerEmployees.Countが順次カウントアップされているのが確認できます。
GetEmployeesAsync()は1秒毎にEmployeeオブジェクトを追加し、呼び出し元は1秒毎にEmployeeリストオブジェクトの内容を確認しています。
つまり、同一のオブジェクトを別スレッドで互いに参照している状態になっています。これについては、プログラム上のデッドロック等が発生しないように考慮が必要なケースがあるでしょう。

リスト6の実装をTuple戻り値版に書き換えたものが以下になります。

[リスト7]
// リスト6をTuple戻り値版に書き換え
static public async Task<(List<Employee> properEmployees, List<Employee> partnerEmployees)> GetEmployeesAsyncTuple()
{
  List<Employee> properEmployees = new List<Employee>();
  List< Employee > partnerEmployees = new List<Employee>();

  for (int i = 0; i < 10; i++)
  {
    await Task.Delay(1000);
    properEmployees.Add(new Employee() { Name = "proper" + i.ToString() });
  }

  for (int i = 0; i < 5; i++)
  {
    await Task.Delay(1000);
    partnerEmployees.Add(new Employee() { Name = "partner" + i.ToString() });
  }

  return (properEmployees , partnerEmployees);
}
// リスト7の呼び出し側
var (properEmployees, partnerEmployees) = Company.GetEmployeesAsyncTuple().Result;

もしくは

// リスト7の呼び出し側
var task = Company.GetEmployeesAsyncTuple();
(properEmployees, partnerEmployees) = task.Result;

いずれにしても、2つの戻り値(結果)オブジェクトは、GetEmployeesAsyncTuple()非同期メソッド処理が完了してから得られる(処理中には対象オブジェクトへの参照が得られない)為、対象オブジェクトへのスレッドセーフが保証されます。

所感

本投稿の Tuple に限らず、C# 7は「言語仕様としての成熟期に入った」という印象です。
内部関数だったり、「=>」の強化による短縮記述だったり、より洗練された簡潔なコードの記述が可能になっています。
と、同時に思うことは、開発メンバーが非精鋭なチームの場合、洗練された簡潔なコードよりも「冗長だけど昔の技術知識で読み解けるコード」っていうのも重宝されるのかなぁ・・・なんていうネガティブな思いです(笑)。
でも、まあ、LINQなんかもそうだけど、しばらく時が経てば標準的に使われるようになるのかな、とも思います。

参考URL

MS公式 C# 7 の新機能紹介は以下
blogs.msdn.microsoft.com

岩永さんの C# 7 解説記事は以下
ufcpp.net

「2017/3/11 Visual Studio 2017 リリース記念勉強会」岩永さんセッションのスライドは以下
docs.com

VS2017よりもっと先取りしたい! ~ Visual Studio Preview

Visual Studio 2017 Visual Studio Preview

2017/3/7に Visual Studio 2017 がリリースされました。
多くの機能が盛り込まれ、そして改善されました。

私を含め皆さんも Visual Studio 2017 の新機能を試して、プロジェクトへの導入の検討をしていることでしょう。

しかし、ギーク達は更なる「おもちゃ」を追い求めることでしょう。

ということで「Visual Studio Preview」です!

Visual Studio Previewとは

Visual Studio Previewは、アーリーアダプターのために最新の機能をいち早く取り込んだ Visual Studio です。
つまり、Stableな製品版 Visual Studio 2017 にはまだ取り込まれていない段階の最新機能が Visual Studio Preview として提供されます。

Visual Studio 2017 と Visual Studio Preview は Side-by-Side 可能

これ、結構嬉しい事だと思います。
「VS2017」と「VS Preview」は、同一環境に並行してインストール可能です。
(とはいっても、仮想環境構築が容易な今の時代的には、VM立てちゃったほうが精神衛生的に良いかもしれないけど・・・でも、公式にサイドバイサイドOKとアナウンスされています。)

Visual Studio Previewをインストールする

では、Visual Studio Previewをインストールしてみましょう!
ここでは、Visual Studio 2017をインストール済みの環境に、Visual Studio Previewを追加でインストールしてみます。
(あっ、でも以下は、実は物理PCではなく、Hyper-V上のWindows10 + VS2017の環境にVS Previewを追加インストールしています(汗))

1.ダウンロード

以下のURLをブラウザで開きます。

www.visualstudio.com

「Download Visual Studio」にマウスを乗せるとエディション一覧がプルダウンされるので、ご希望のエディションをクリックしましょう。
私は Enterpriseを選択しました。
セットアップ用exeがダウンロードされます。

2.セットアップ実行

ダウンロードしたセットアップ用exeを実行します。

Visual Studio 2017セットアップで見慣れた画面が表示されます。
続行をクリックします。

f:id:daigo-knowlbo:20170310032357p:plain

インストール対象を選択する画面が表示されますが、「Python 開発」が表示されているのを確認することができます。
これ、StableなVisual Studio 2017にはまだ取り込まれていません(2017/3/10現在)。

f:id:daigo-knowlbo:20170310032442p:plain

インストールボタンをクリックすると、インストールが開始されます(ここでは「ワークロード」の機能をすべて選択しました)。

以下の画面をよく見ると、画面左側に「インストール済み Visual Studio 2017 Enterprise」と表示されています。
そして、その右側に「Visual Studio Enterprise 2017(2)」と表示されています(これが、今インストールしているVisual Studio Preview)。

f:id:daigo-knowlbo:20170310032721p:plain

3.起動

インストールが終わったら「起動」ボタンをクリックします。

f:id:daigo-knowlbo:20170310033058p:plain

バージョン情報を確認してみましょう(メニュー「ヘルプ → Microsoft Visual Studioのバージョン情報」)。
以下のように「Version 15.1(26304.0 Preview)」の文字を確認することが出来ます。

f:id:daigo-knowlbo:20170310033258p:plain

4.Pythonプロジェクトを作成してみる

Pythonサポートは、Preview版で先行リリースされている機能です。
これを使ってみようと思います。

メニュー「ファイル → 新規作成 → プロジェクト」を選択します。
以下のように、Pythonテンプレートが表示されました。Python Applicationを選択してOKをクリックします。

f:id:daigo-knowlbo:20170310033631p:plain

以下のようなプロジェクトが作成されます。

f:id:daigo-knowlbo:20170310034016p:plain

PythonApplication1.pyを適当に以下のように実装します。

print("Hello Python")

CTRL + F5 で実行してみましょう。

f:id:daigo-knowlbo:20170310034207p:plain

はい、実行されました。

まとめ

Visual Studio 2017自体がいろいろ盛りだくさんなのですが、Visual Studio Previewなんかの先行リリースも常に軽く眺めておくと時代についていくのが楽になるのではないかと思います。
まとめっちゅうまとめは特にないのですが、興味がある方は Visual Studio 2017 に加えて Visual Studio Preview も入れても面白いのではないかと思います。

参考URL:

blogs.msdn.microsoft.com

Docker Support で ASP.NET Core アプリを作る - Visual Studio 2017

Docker .NET Core ASP.NET Core Visual Studio 2017

Visual Studio 2017がいよいよ正式リリースされました。
個人的感想としたは「超目玉!!!」な機能は、感じられていないのですが、個々の技術要素は非常に興味深く思っています。
昔に比べて、各情報は小出しにリリースされるので上記のような感想を持つ形になるのでしょう。昔は大きな区切りで一気にいろんな機能がリリース、という流れだったので・・・

で、今回はVisual Studio 2017を利用し、ASP.NET Coreアプリ を Docker Support で開発実行してみようと思います。

Docker for Windowsのインストール

まずは Docker for Windows をインストールします。
以下のURLにアクセスし Stable channel 版をインストールすればOKです。

Install Docker for Windows - Docker Documentation

f:id:daigo-knowlbo:20170309015615p:plain

うまくインストールされれば、タスクトレイに例のクジラが現れます。

f:id:daigo-knowlbo:20170309015843p:plain

プロジェクトの作成

Visual Studio 2017を起動します。
メニューから「ファイル → 新規作成 → プロジェクト」を選択します。
テンプレートは「Visual C# → Web → ASP.NET Core Webアプリケーション(.NET Core)」を選択し、名前は「HelloAspNetCore」とします。ちなみに場所は「J:\Projects」としました。
OKをクリックします。

f:id:daigo-knowlbo:20170309020131p:plain

次に表示されたウィンドウで「.NET Coreのバージョンは1.1」「Web アプリケーション」を選択します。
そして今回最も重要なのは、左下の「Docker サポートを有効にする」にチェックをつけることです。
で、OKをクリックします。

f:id:daigo-knowlbo:20170309020210p:plain

自動生成されたソリューションは以下の通りです。
Docker構成(docker-compose)が自動生成されたことが確認できます。

f:id:daigo-knowlbo:20170309020424p:plain

Dcokerを設定する

VS上でそのまま実行(CTRL + F5)したいところですが、このままではエラーとなってしまいます。
Docker側の設定が必要です。
タスクトレイのDockerアイコン(クジラ)をマウス右ボタンクリックし「Settings…」を選択します。
Settingsウィンドウが表示されるので、左側のタブから「Shared Drives」を選択し、「システムドライブ(C)」及び「プロジェクトドライブ(J)」を Shared として、Applyボタンをクリックします。

f:id:daigo-knowlbo:20170309020848p:plain

更新&Docker再起動まで少し待ちましょう。
Settingsウィンドウの左下の表示が「Uodating Drives…」から「Docker is running」となったらOKです。

実行!

Visual Studio 2017に戻ってプロジェクトを実行しましょう!
(ビルド)ツールバーに「▶Docker」と表示されているので、これをクリックします(「CTRL + F5」でもOK)。

f:id:daigo-knowlbo:20170309021219p:plain

すばらしい!
Visual Studio 2017で作成した ASP.NET Coreアプリが、Docker上で実行されました!

f:id:daigo-knowlbo:20170309021236p:plain

本当にDockerで実行されたか確認する

あまりに簡単すぎて、これは本当にDokcerで実行されているの?
と、いうことで確認してみましょう。

コマンドプロンプトを開きます。
以下のdockerコマンドを実行してみましょう。

docker images
docker ps -all

結果は以下です。

f:id:daigo-knowlbo:20170309021546p:plain

うん、今作成したプロジェクトがdocker上に作成されています。

まとめ

VSのDockerサポートは.NET Coreのみの機能ですが、非常にシームレスに統合されているように感じました。
.NET Framework / .NET Core / Xamarinというマルチプラットフォームの統合は .NET Standard 2.0 であり、もう少し時間がかかりますが、.NET Core + Dcoker という点については、開発において色々な可能性を秘めているのではないでしょうか。

Portability Analyzerを使ってライブラリの.NET Standard準拠を調べよう

Visual Studio .NET

Visual Studio 2017のリリースを迎え(るにあたり)、(技術的には、直接的にそこに関連付いているわけではないけれど)「.NET Standard」というキーワードが強く聞こえてくるようになった気がしています。

そうそう、先日、 Xamarin Studio も .NET Standard Library 対応しましたね。

blog.xamarin.com

ということで、今回は既存資産のライブラリ実装が、.NET Standardに準拠しているかどうか調べることができる「Portability Analyzer」というものを使ってみたいと思います。

Githubリポジトリは↓↓↓

github.com

ちなみにGithubからソース取らなくても、以下のGUI操作だけで使えます。

※以下はVisual Studio 2015を使います。明日か明後日にVS2017版に差し替えるかも・・・

.NET Portability Analyzerをインストール

※2017.3.8追記
2017/3/8現在のVisual Studio 2017では「拡張機能と更新プログラム」からだと「.NET Portable Analyzer」が出てこないみたいです。
以下から.vsix ファイルをダウンロードしてダブルクリックでインストールするとVS2017でも使えるようになります。
でも、VS2017が不安定になる可能性があります、と警告文が出るので自己責任でご利用ください(正式対応じゃないってことですね)。私の環境では、特に何かが変になることなく動いていました。

.NET Portability Analyzer - Visual Studio Marketplace


Visual Studio 2015を起動。
メニュー「ツール → 拡張機能と更新プログラム」を選択。

f:id:daigo-knowlbo:20170307122708p:plain

「オンライン → .NET Portable Analyzer」を選択してインストール

f:id:daigo-knowlbo:20170307122719p:plain

VSの再起動を促されます。

調査対象のライブラリプロジェクトを開く

調査対象のライブラリプロジェクトをVisual Studioで開きます。
今回は以下のような「OmnipotentLibrary」というライブラリプロジェクトを調査対象としました。

f:id:daigo-knowlbo:20170307123000p:plain

プロジェクトに含まれるMyLogger.csは以下のような、Reflectionを使ったキビシイ実装です。

// MyLogger.cs
using System;
using System.Security;
using System.Reflection;
using System.Diagnostics;

namespace OmnipotentLibrary
{
  public class MyLogger
  {
    /// <summary>
    /// メソッドの開始時に呼び出します。
    /// </summary>
    /// <returns></returns>
    [DynamicSecurityMethod]
    public static DateTime Start()
    {
      DateTime now = DateTime.Now;

      const int callerFrameIndex = 1;
      StackFrame callerFrame = new StackFrame(callerFrameIndex);
      MethodBase callerMethod = callerFrame.GetMethod();
      Debug.WriteLine(string.Format("start {0}()", callerMethod.Name));

      return now;
    }

    /// <summary>
    /// メソッドの終了時に呼び出します。
    /// </summary>
    /// <param name="start"></param>
    [DynamicSecurityMethod]
    public static void End(DateTime start)
    {
      DateTime now = DateTime.Now;

      TimeSpan ts = now - start;

      const int callerFrameIndex = 1;
      StackFrame callerFrame = new StackFrame(callerFrameIndex);
      MethodBase callerMethod = callerFrame.GetMethod();
      Debug.WriteLine(string.Format("end {0}(): {1}ms", callerMethod.Name, ts.TotalMilliseconds));
    }
  }
}

namespace System.Security
{
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  internal sealed class DynamicSecurityMethodAttribute : Attribute
  {
  }
}

Portability Analyzerの設定を行う

ソリューションエクスプローラでプロジェクトを選択、マウス右ボタンクリック、メニュー「Prtable Analyzer Settings」を選択します。

f:id:daigo-knowlbo:20170307134814p:plain

表示された以下のウィンドウで「Target Platforms」を選択します。
「Target Platforms」とは、アナライズ対象のプラットフォームです。調査対象のプロジェクト(OmnipotentLibrary)が、「対象のプラットフォームに準拠しているか?」調べる対象になります。

f:id:daigo-knowlbo:20170307134827p:plain

ここでは、.NET Core 1.0 / 1.1 / 2.0、.NET Framework 4.5 / 4.5.1 / 4.5.2 / 4.6 / 4.6.1 / 4.6.2、.NET Standard 1.0 / 1.1 / 1.2 / 1.3 / 1.4 / 1.5 / 1.6 / 2.0、Xamarin Android 1.0.0、Xamarin.iOS 1.0.0.0に準拠しているか?を、アナライズする設定としました。

Portability Analyzerを実行する

ソリューションエクスプローラでプロジェクトを選択、マウス右ボタンクリック、メニュー「Analyzer Azzembly Portability」を選択します。

f:id:daigo-knowlbo:20170307135554p:plain

アナライズが完了すると、「Portability Analyze Results」ウィンドウに結果が表示されます。

f:id:daigo-knowlbo:20170307135652p:plain

「Open Report」をクリックすると結果のExcelファイルが起動されます。

結果を分析する

アナライズ結果のExcelは、2シート構成です。

1シート目は結果のサマリーになっています。
横長なので折り返し加工していますが、以下のような感じです。

f:id:daigo-knowlbo:20170307140356p:plain

表のヘッダ(青い部分)が「ターゲットプラットフォーム」、その下の数値が「分析結果のスコア」です。
スコア100(緑)が準拠OK、スコア100未満(オレンジ)が準拠NG、となります。
このライブラリはReflection系を使用しているので、.NET Core 1.0 / 1.1 や .NET Standard 1.xなどでは結果NGとなりました。

2つ目のシートには、スコアが100未満であった原因の詳細がリストされています。

f:id:daigo-knowlbo:20170307140524p:plain

T:System.Diagnostics.StackFrame とか T:System.Runtime.InteropServices.GuidAttributeを使ってるのが悪いよー、Supported: 2.0+じゃないと使えないよー、っていう結果が分かります。

まとめ

ということで

Let’s head to .NET Standard!

Introducing .NET Standard | .NET Blog

docs.microsoft.com