vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

ASP.NET CoreでWebSocket

はじめに

以前 Spring boot で WebSocket を使う、てなことをやりましたが、今回は ASP.NET Core を使ってやってみます。

なぜ急に WebSocket か? お察しください

なおリアルタイムで処理を行う機能としては ASP.NET SignalR というものがあって、 ASP.NET Core では ver.2.1 から使えるとのことなので、こちらも近いタイミングで試してみたいと思います。

なおクライアント側は下記と同じく websocket-sharp を使うことにします。

http://mslgt.hatenablog.com/entry/2017/05/11/073758

インストール

ASP.NET Core では Microsoft による WebSocket パッケージがありますのでそれを使用します。

NuGet で Microsoft.AspNetCore.WebSockets パッケージをインストールすれば OK です。

簡単ですね。

サンプルコードを試してみる

まずはサンプルコードを試してみることにします。

docs.microsoft.com

github.com

↑を試してみると、確かに WebSocket で接続することができました。

ただ、(サンプルコードとしては素晴らしいのですが) Startup クラスが少々長くなってしまうのが気になりました。

Configure() の中身を見てみると、関連するコードは(オプションを除くと)以下のようでした。

Startup.cs

~省略~
  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
    ~省略~
    app.UseWebSockets();
    ~省略~
    app.Use(async (context, next) => {
        if (context.Request.Path == "/ws") {
            if (context.WebSockets.IsWebSocketRequest) {
                WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                await Echo(context, webSocket);
            }
            else {
                context.Response.StatusCode = 400;
            }
        }
        else {
            await next();
        }
    });
  }
~省略~

WebSocket を有効にする UseWebSockets() はともかく、接続するところは Controller クラスでもできるんじゃね?

と思ってコピペしたらエラーになりました。

ですよね~/(^o^)\

HomeController.cs

~省略~
  [Route("ws")]
  public async void RouteWebsocketAccess() {
      if (this.HttpContext.WebSockets.IsWebSocketRequest) {
          WebSocket webSocket = await this.HttpContext.WebSockets.AcceptWebSocketAsync();
          await Echo(this.HttpContext,  await this.HttpContext.WebSockets.AcceptWebSocketAsync());
      }
      else {
          this.HttpContext.Response.StatusCode = 400;
      }
  }
~省略~

WebSocket のリクエストかどうかを確認する IsWebSocketRequest は正しく動作していましたが、実際にデータの送受信をする Echo の部分でエラーが発生していました。

The remote party closed the WebSocket connection without completing the close handshake.

また、 WebSocket のリクエストではなかった場合の、 this.HttpContext.Response.StatusCode も正しく動作していませんでした。
(Startup.cs で実行した場合は前回の 404 と同じように処理されます)

Controller クラスではできないのか、見様見真似で書いたものの間違いがあってできないのかはわからないのですが、とりあえずこのままではダメ、ということがわかりました。

とりあえず今回は別の方法を選ぶことにします。

app.Use()とapp.Map()

tamafuyou.hatenablog.com

この記事を見てみると、 Startup では app.Map() を使ってマッピングを行い、先ほどの app.Use() の部分をそのまま chatServer.Map に移していました。

 app.Map("/ws", chatServer.Map);

では、この app.Map() や app.Use() って何なのでしょうか。

というのを少しだけ見てみます。

docs.microsoft.com

まだわかるようなわからないようなというところですが、

そもそもまずこの Configure というメソッドは、

  1. HTTP アクセスがあった場合に毎回呼び出される
  2. DB 、ログ出力、ルーティングなどを行うミドルウェアを利用している場合に、それらの処理を呼び出す

というものであり、 app.Use() は 2. を実行するためのもの、ということのようです。

ルーティングのようにデフォルトで用意されているものは、 app.UseMvc() のようなメソッドが用意されているのでそれを使う、と。

で、 app.Map() というのは特定のパスに対して処理を割り当てるもの、ということのようです。

このミドルウェアは複数扱うことができ、 app.Use() などの中から app.Next() を呼ぶと次のミドルウェアの処理に移すことができます。

先ほどの WebSocket の処理で、 URL が ~/ws ではなかった場合に app.Next() を呼び忘れると該当のページが表示されないため注意が必要です。

今回登場しませんでしたが、 app.Run() を実行すると処理がそこで終わってしまうため、最後に実行する必要があります。

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
  if (env.IsDevelopment()) {
      app.UseBrowserLink();
      app.UseDeveloperExceptionPage();
  }
  app.Run(async (context) => {
    await Task.Run(() => {
          Console.WriteLine("hello goodbye");
    });
  });
  // ここから先の処理が実行されない/(^o^)\.
  app.UseStaticFiles();

  ~省略~

}

WebSocketを使うコードを見る

ようやく今回のテーマである(寄り道しすぎ)、 WebSocket を利用するコードを見てみることにします。

先ほどから登場しまくりである下記のサンプルコードを参考に(ほぼコピペ)、 WebSocket で接続し、メッセージを受信したら接続先に返す、という内容となっています。

ASP.NET Core での Websocket のサポート | Microsoft Docs

ASP.NET CoreでWebSocketを使用するサンプルを作ってみたよ( `ー´)ノ - 眠いしお腹すいたし(´・ω・`)

WebSocketController.cs

using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace WebApplication1.Controllers {
    public class WebsocketController {
        public void Map(IApplicationBuilder app) {
            app.UseWebSockets();
            app.Use(async (context, next) => {
                if (context.WebSockets.IsWebSocketRequest) {
                    // HTTPリクエストの内容が WebSocket のものであればAcceptし、メッセージの受信・送信を行うメソッドに渡す.
                    WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                    await Echo(context, webSocket);
                }
                else {
                    // HTTPリクエストの内容が WebSocket のものでなければエラー.
                    context.Response.StatusCode = 400;
                }
            });
            // app.Map("/ws", websocket.Map); でマッピングしてるので他のパスに対する処理はしない.
        }

        // 引数のHttpContextは今回未使用. やりたいことがこれだけなら不要かも.
        private async Task Echo(HttpContext context, WebSocket webSocket) {

            // 送受信するデータ(buffer)のサイズ指定.
            var buffer = new byte[1024 * 4];

            WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);

            // 接続がCloseされるまでメッセージの送受信を繰り返し.
            while (result.CloseStatus.HasValue == false) {
                await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

                result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
            }
            // Whileを抜けたらClose処理.
            // 接続が Close されると、result.CloseStatusに「NormalClosure」のような値が入ってくる.
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        }
    }
}

接続は Map() の context.WebSockets.AcceptWebSocketAsync() で行われて、メッセージの送受信は Echo() で行われるようですね。

メッセージの送受信

先ほどのコードで触れていなかったメッセージの送受信について追いかけてみます。

メッセージの受信

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);

送信の引数にもなっていますが、 ArraySegment は配列の一部を持つもので、渡している buffer( byte の配列)にはクライアントから送信されたデータが格納されます。

第二引数には処理をキャンセルする必要がある場合にその通知を渡すもの( CancellationToken )ということなのですが、空で渡しています。

送受信をキャンセルするようなこともできる、ということなのでしょうか。

戻り値となる WebSocketReceiveResult は、受信したデータそのものは持っておらず、受信データの型( MessageType )や接続が Close されたか( CloseStatus )などの情報を持っています。

試しに下記のようにログを出してみました。

WebSocketController.cs

var buffer = new byte[1024 * 4];

Console.WriteLine("before bLength " + buffer.Length + " b0 " + buffer[0]);

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);

Console.WriteLine("bLength " + buffer.Length + " b0 " + buffer[0] + " rCount " + result.Count + " rEnd " + result.EndOfMessage + " rType " + result.MessageType);

while (result.CloseStatus.HasValue == false) {
    await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

    Console.WriteLine("while bLength " + buffer.Length + " b0 " + buffer[0] + " rCount " + result.Count + " rEnd " + result.EndOfMessage + " rType " + result.MessageType);

    result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None);
}

で、クライアントから「世界さん、チーッす!」という文字列を1回送信したところ、結果は下記のようになりました。

before bLength 4096 b0 0
bLength 4096 b0 228 rCount 30 rEnd True rType Text
while bLength 4096 b0 228 rCount 30 rEnd True rType Text

result.Count

result.Count で取得できる 30 という数値。

これは、 buffer にあらかじめ設定している 4096 という要素数のうち、実際にデータが格納されている(つまり値が 0 でない)要素数を示しているようです。

メッセージの送信

await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

送信側も引数は受信とあまり変わらない(今回は受け取ったデータをそのまま渡しているため)ようです。

buffer

0 から buffer の中のデータが格納された要素数分を保持する ArraySegment を渡しています。

result.EndOfMessage

( buffer の要素数などの静減から)メッセージを複数に分割して送信したい場合は false を渡してやれば良さそうです。

サンプルでは受信したデータをそのまま送信していますが、例えば固定で文字列を追加して送信したい場合、下記のようにすれば実現できます。

// 追加する文字列(byteの配列として持つ).
byte[] additionalBytes = System.Text.Encoding.UTF8.GetBytes("hello: ");
// 追加する文字列を受信したデータとマージする.
byte[] newBytes = new byte[additionalBytes.Length + result.Count];
additionalBytes.CopyTo(newBytes, 0);
ArraySegment receivedDataSegment = new ArraySegment(buffer, 0, result.Count);
receivedDataSegment.CopyTo(newBytes, additionalBytes.Length);

// データを送信する.
await webSocket.SendAsync(newBytes, result.MessageType, result.EndOfMessage, CancellationToken.None);    

配列のマージについてはもう少しうまいやり方があるかもしれませんが。

おわりに

今回はひたすらサンプルを追っかける回になってしまいました。

今回に限らずですが、サンプルコードがちゃんと動くってのはありがたいですねぇ(..)_

もうちょっとだけ続くんじゃ、ということで、次回はもう少しこれをベースに遊んでみたいと思います。

参照

ArraySegment

ASP.NET Coreに触れてみる 2

はじめに

次はモデルの話に行く予定でしたが、読んでる本MVC のコントローラーのルーティングの話に行ったのでその話を。

依存の追加について

それはそれとして、前回 Dependency Injection の話で、サービスに Inject する方法を以下のように書きました。

~省略~
public class Startup {
    public void ConfigureServices(IServiceCollection services) {

        services.AddSingleton< IDiSample, DiSample>();

        services.AddMvc();
    }
    ~省略~
  }
~省略~

これは下記のようにも書くことができるようです。

~省略~
public class Startup {
    public void ConfigureServices(IServiceCollection services) {

        これも OK
        services.AddSingleton< IDiSample>(new DiSample());

        services.AddMvc();
    }
    ~省略~
  }
~省略~

ルーティングについて

ASP.NET Core でのルーティングには、規約ベースと属性ベースが存在します。

規約ベースのルーティング

規約ベースのルーティングは、 Startup の Configure で MVC を有効にする( UseMvc() を実行する)時にコントローラーなどへのマッピングを名前指定で行います。

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseMvc(routes => {
                routes.MapRoute(
                    name: "default",
                    // controllerとして指定している値+ Controllerという名前のクラスの,
                    // actionで指定している名前のメソッドが実行される.
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            
        }
    }
}

これで localhost:5XXX や localhost:5XXX/Home 、 localhost:5XXX/Home/Index にアクセスしたとき Controllers フォルダに作成した HomeController.cs の Index() が実行されるようになります。

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        public IActionResult Index() {
            // Views/Home に作成した Index.cstml を表示する.
            return View();
        }        
    }
}

なお上記 Startup でのマッピングは、代わりに app.UseMvcWithDefaultRoute() を実行しても同じ動作となります。 (controller や action 名を変更する場合は明示的に指定する必要があります)

前回登場した Razor View では、(自作の)コントローラーを使用していなかったので app.UseMvc() (引数なし)を実行していました。 なおこれは app.UseMvc(routes => { }); と同じ動作をします。

また前回の Razor View は Pages フォルダにあったのに対し、今回使用するビューは Views/Home にあり、そのまま移動させてしまうとエラーとなるため、再度作り直したほうが良さそうです。

上記ではコントローラー名、アクション名を URL と合致させていますが、別々のものを指定することもできます。

Startup.cs

app.UseMvc(routes => {
        routes.MapRoute(
            name: "default",
            template: "{id?}",
            defaults: new { controller = "Home", action = "Index" });
    });

これで localhost:5XXX または localhost:5XXX/0 といった URL でのみ指定のページが表示されるようになります。

属性ベースのルーティング

Spring boot だと @Controller などのアノテーションをつけて指定する方法がこちらです。 (見た感じ、というだけで実際に同じ動作かと言われると自信がありませんが)

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseMvc();
            
        }
    }
}

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        public IActionResult Index() {
            return View();
        }        
    }
}

これで localhost:5XXX や localhost:5XXX/Home 、 localhost:5XXX/Home/Index にアクセスしたとき Index() が実行されます。

コントローラーのクラス名を HomeController としていますが、マッピングする URL と合致はしていなくても OK です。

ただし、ビューのフォルダ名とは合致していないとエラーになるので、直接指定する必要があります。

HomeController.cs

public IActionResult Index() {
    return View("~/Views/Home/Index.cshtml");
}

もう少し使ってみないといけないとは思いますが、個人的には属性ベースの方が好みです。
マッピングする URL と呼ばれるメソッドやコントローラーがそばにあるあたりが。

キャッチオールルートパラメーター

例えば localhost:5XXX/Home/ 以下のすべての URL にアクセスされた場合に一括で処理をしたい場合。

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        [Route("")]
        [Route("Home")]
        [Route("Home/{*article}")]
        public IActionResult Index() {
            return View();
        }        
    }
}

{*article} を指定することで、例えば例えば localhost:5XXX/Home/Index であろうと localhost:5XXX/Home/Sample であろうとすべて Index() が呼ばれるようになります。

これをキャッチオールルートパラメーターというそうです。
安全保障貿易管理に出てくるキャッチオール規制を思い出す名前ですが。

HTTPリクエス

GET、POST などの HTTP リクエストの指定をすることもできます。

HomeController.cs

public class HomeController : Controller {
    [HttpGet]
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    public IActionResult Index() {
        return View();
    }        
}

これで該当の URL に GET リクエストが渡された場合のみ Index() が実行されるようになります。

※規約ベースのルーティングで、上記を実現する方法は見つけられませんでした。。
IRouteBuilder の MapGet などで HTTP リクエストを指定する方法はあるのですが、ビューをどう表示するかが見つけられずorz

また Web API を作りたい場合など、 URL マッピングをクラス全体に対してつけることも可能です。

以下は Visual Studio でコントローラークラスをジェネレートするとできるものです(名前は任意)。

MessageController.cs

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    [Produces("application/json")]
    [Route("api/Message")]
    public class MessageController : Controller {
        // GET: api/Message
        [HttpGet]
        public IEnumerable Get() {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Message/5
        [HttpGet("{id}", Name = "Get")] 
        public string Get(int id) {
            return "value";
        }
        
        // POST: api/Message
        [HttpPost]
        public void Post([FromBody]string value) {
        }
        
        // PUT: api/Message/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value) {
        }
        
        // DELETE: api/ApiWithActions/5
        [HttpDelete("{id}")]
        public void Delete(int id) {
        }
    }
}

これで localhost:5XXX/api/Message に例えば Web ブラウザでアクセスすれば、"value1"、"value2" という値が JSON で表示されます。

エラーページ

ここまでで URL のマッピングや表示するビューについては少しわかった気がするのですが、このままだと 404 などのエラーページが表示されません。

これを解決するには、 Startup.cs の Configure で設定が必要です。

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseStatusCodePages();
            
        }
    }
}

これで例えば存在しない URL にアクセスすると、 Status Code: 404; Not Found のようなテキストが表示されるようになります。

ここで例えばビューとして用意したページを見せたい場合、下記のように指定することができます。

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseStatusCodePagesWithRedirects("/Error/{0}");
            
        }
    }
}

この {0} には例えば 404 エラーなら「404」という値が入るので、

HomeController.cs

~省略~
[Route("/Error/404")]
public IActionResult Error404() {
    return View("~/Views/Home/ErrorPageNotFound.cshtml");
}
~省略~

あとは(今回の場合) Views/Home に ErrorPageNotFound.cshtml を作れば完了です。

ErrorPageNotFound.cshtml

@{
    ViewData["Title"] = "ErrorPageNotFound";
}

< h2>Page Not Found< /h2>
< div>おめーに見せるページはねぇ!< /div>

おわりに

今回解決できなかった(自分の中でまとまらなかった)点として、コントローラーの分割があります。

途中で触れた通りコントローラーで実行されるメソッドを URL で指定するわけですが、例えばビューを表示する処理のみをコントローラー1で行い、CRUD 処理をコントローラー2で行う、といった分割をするべきかどうか。

まぁ作るものの規模にもよるでしょうし、もう少し実際に作ってみながらその辺りは確かめてみたいと思います。

次回こそモデルの話。。。のハズ。

参照

ASP.NET Coreに触れてみる 1

はじめに

前から気にはなっていた、 ASP.NET Core を触ってみることにしました。

まずはチュートリアルから。。。と思ったのですが、Razor View にしろ MVC Web アプリにしろ、プロジェクトを Web Application のテンプレートで作った時点で複数ページ分ガッツリ作られてしまいます。

docs.microsoft.com

で、チュートリアルの内容としてもこれをベースに Model を追加したりすることになるため、おねがい、チョ待って!チョ待って!となりました(これはこれで進めたいのですが)。

折よく手に入れた本がプロジェクトを Empty で作って一つずつ見ていく、という内容だったこともあり、もうちょっと基本的なところから見てみたいと思い、その内容を気力が続く限り書き残しておきたいと思いました。

Programming ASP.NET Core

生成されたフォルダ・ファイル

まずはプロジェクトを Empty で作って、生成されるフォルダとファイルを見てみます。

  • wwwroot: フォルダ。CSS や画像などの静的ファイルを置く(有効にした場合)。 Web アプリのルートディレクトリとなる。
  • Program.cs: メインクラス。使用するサーバーや Startup として使用するクラスの指定などを行う。
  • Startup.cs: Startup クラス。DI の Inject 対象クラスの登録や Razor View の有効化などを行う。

プロジェクトを Web Application で生成すると、これに加えて Pages というフォルダが作られ、そこに Razor View のファイルが置かれます。

Razor View

先ほどから登場している Razor View 。

これは、 jspasp のように、 HTML にサーバーサイドの言語(ここでは C#VB.NET )で処理が書ける、といったもののようです。

Xamarin や WPF でいう XAML とコードビハインドのように、Index.cshtml <-> Index.cshtml.cs というセットで処理を書きます。

docs.microsoft.com

後述しますが、 Startup クラスで MVC が有効になっている場合、アクセスした URL のパスに合わせて Razor View が表示されます。

例えば Pages/About.cshtml がある場合、 localhost:5XXX/About にアクセスすると About.cshtml が表示されます。

またコードビハインドには OnGet メソッドがデフォルトで作成されており、ページアクセス時に呼ばれます。

ここから、(少なくともデフォルトでは) Routing の役割を担う Controller クラスは自作せず、自動で Routing されるらしいことがわかります。

また各ページごとに GET や POST の処理を行う、と。

特殊な Razor View

基本的な動作は上記の通りですが、いくつか特別な動きをするものがあります。

Index.cshtml

Index と名付けられたページは、トップドメインで表示されます( localhost:5XXX )。

ViewStart.cshtml と Layout.cshtml

head・body タグをはじめ、各ページ共通で表示したい要素がある場合 Layout にまとめることができます。

この Layout 、デフォルトでは _Layout.cshtml となっていますが、変更することができます。

どのファイルを Layout とするかは _ViewStart.cshtml で指定されており、これを変更することで別名のファイルが指定できます。

_ViewStart.cshtml

@{
    Layout = "_Layout";
}

ただし上記の場合では _Layout.cshtml というファイルがないと実行時に例外が発生します。

なおコンパイルエラーにはならないものの、 Visual Studio (または ReSharper )が赤く表示してくれるので事前の確認も可能ではあります。

また Layout ファイルでは、各ページで定義している表示内容をどこに表示するかを、 @RenderBody() で指定します。

_Layout.cshtml

< !DOCTYPE html>
< html>
< head>
    < meta charset="utf-8" />
< /head>
< body>
    < h1>世界さん、ちーっす< /h1>
    @RenderBody()
< /body>
< /html>

Layout ファイルに @RenderBody() が含まれていないと、これまた実行時に例外が発生します。

こちらは赤く表示もされないため、より注意が必要です。

Program.cs

前述の通り、メインクラスです。

デフォルトでは WebHost.CreateDefaultBuilder を実行し、埋め込みのサーバー( Kestrel )の使用やルートディレクトリの指定、 Startup クラスの指定などを行い、アプリを実行します。

Startup.cs

Startup クラスではデフォルトで2つのクラスが生成されます。

  • ConfigureServices
  • Configure

ConfigureServices に対して DI コンテナに追加したいクラスや MVC の登録を行い、Configure で有効化する、という動きをするようです。

また Configure では Error の発生時に表示するページを指定することもできます。

これにより開発時には詳細なエラー内容がわかる開発者向けのページを表示したり、本番環境では不要な情報を見せない、といったことが可能になります。

Startup.cs

public void ConfigureServices(IServiceCollection services) {
    // DI コンテナへのクラス( interface )の追加(サービスに登録).
    services.AddSingleton();
    // MVC を追加する( Razor View を使うため).
    services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        // 開発環境においては開発者向けのエラーページを表示.
        app.UseDeveloperExceptionPage();
    }
    // wwwroot に置かれている静的ファイルを有効にする.
    app.UseStaticFiles();
    // MVC を有効にし、URL アクセス時に Razor View が表示されるようにする.
    app.UseMvc();
}
  • ConfigureServices で IServiceCollection に対して AddMvc() を実行せずに Configure で UseMvc() を実行すると InvalidOperationException が発生します。
  • Configure で UseMvc() を実行しないと、真っ白のページが表示されます( Razor View 以外に表示するものを用意していない場合)。

その他 Startup でできる(やるべき)ことはまだまだたくさんあるようですが、おいおい調べていきたいと思います。

docs.microsoft.com

Dependency Injection

ASP.NET Core では Dependency Injection を、

  1. Startup.cs の ConfigureServices で対象クラス( interface )をコンテナに追加(サービスに登録)
  2. 1.のクラスを利用するクラスのコンストラクタで受け取る

という流れで実現しています。

今回は任意で interface とその実装クラスを作り、それを Index.cshtml.cs で受け取ってみます。

IDiSample.cs

  • DI で Inject する interface
namespace WebApplication1
{
    public interface IDiSample {
        void Say();
    }
}

DiSample

  • IDiSample.cs の実装クラス
using System;

namespace WebApplication1 {
    public class DiSample: IDiSample {
        public void Say() {
            Console.WriteLine("Hello DI");
        }
    }
}

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace WebApplication1 {
  public class Startup {
    public void ConfigureServices(IServiceCollection services) {

        // サービスに Inject する interface, 実装クラスを(ここではシングルトンとして)追加.
        services.AddSingleton< IDiSample, DiSample>();

        services.AddMvc();
    }
    ~省略~
  }
}

Index.cshtml.cs

using System;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebApplication1.Pages
{
    public class IndexModel : PageModel {

        // コンストラクタで登録されたクラスを受け取り.
        public IndexModel(IDiSample s) {
            s.Say();
        }
        
        public void OnGet() {
            Console.WriteLine("hello");
        } 
    }
}

おわりに

プロジェクト生成時に Web Application を選ぶと、数ページ分ガッツリコードが出てくるのには面喰いましたが、一つ一つたどっていくとちょっとは理解できたような気がします。

次回は Model を Scaffold で生成する辺りの話。。。のはず。

参照

【C#】ClosedXMLでExcelのデータ読み込み

はじめに

みなさん Excel 使ってますか Excel

今日も(多分)外道な使い方をしている増井です。どうもこんにちは。

今回は ClosedXML を使って、 Excel(Spreadsheet) のデータを読み込んでみることにします。

https://github.com/ClosedXML/ClosedXML

基本的な使い方は Wiki に書かれていますが、いくつかやりたかったことが見つけられなかったため、備忘録的に書き残しておきます。

https://github.com/ClosedXML/ClosedXML/wiki

コード

今回は WPF を使って、ボタンを押したら指定のファイルを読み込む、ということにします。

MainWindow.xaml

< Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    < Grid>
        < Button Name="LoadFile1" Content="File1" HorizontalAlignment="Left" Margin="155,86,0,0" VerticalAlignment="Top" Width="75" Click="LoadFile1_Click"/>
    < /Grid>
< /Window>

デフォルトでできる XAML にボタンを追加しただけです。

MainWindow.xaml.cs

using System.Windows;
using WpfApp1.FileAccessor;

namespace WpfApp1 {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
        private void LoadFile1_Click(object sender, RoutedEventArgs e) {
            FirstExcelAccessor accessor = new FirstExcelAccessor();
            accessor.Load();
        }
    }
}

ボタンイベントを追加しました。

FirstExcelAccessor.cs

using System;
using System.Linq;
using ClosedXML.Excel;

namespace WpfApp1.FileAccessor {
    class FirstExcelAccessor {
        public void Load() {
            string path = @"ファイルのパス";
            // Excel(Book を開く)
            using (XLWorkbook workbook = new XLWorkbook(path)) {
              // シート、セルにアクセスして必要な処理を行う.
            }
        }
    }
}

今回はファイルのパスを固定で持たせています。
必要に応じて Explorer から取得するなどしてください。

特定のセルの列・行番号を取得する

workbook.Worksheet("シート名") でシートを、 worksheet.Cell("セル名") で取得できます。

また、取得したシートの列数、行数を取得するには下記のようにします。

using (XLWorkbook workbook = new XLWorkbook(path)) {
    using (IXLWorksheet worksheet = workbook.Worksheet("Sheet1")) {
        IXLCell cell = worksheet.Cell("calc");
        int rowNum = cell.Address.RowNumber;
        int columnNum = cell.Address.ColumnNumber;

        // セルの値を string として取得.
        var value = cell.GetString();
    }
}

セルに計算式が入っている場合、 cell.GetString() で取得できるのは計算後の値です。

セルの名前からシートを探す

セルの名前だけがわかっていて、どのシートにそのセルがあるかはわからない場合。

workbook.Cell や workbook.Range でいけそうな気がしたのですが、うまく見つけられず null になってしまうので下記のようにしました。

https://github.com/ClosedXML/ClosedXML/wiki/Accessing-Named-Ranges

var foundSheet = workbook.Worksheets
    .FirstOrDefault(sheet => sheet.Cell("world") != null);

if (foundSheet != null) {
    Console.WriteLine(foundSheet.Name);
}

コードとしてはシンプル(な気がする)ですが、全シートを見ることになるので特にシート数が多かったり、頻繁に検索する必要がある場合などは他の方法を考えたほうが良いかもしれません。

java.lang.reflect.Proxy に触れてみる

はじめに

Spring Data JPA プログラミング入門」を読み始めたのですが (n 回目)、その中にこのような話がでてきます。

DB へのアクセスにはリポジトリが必要 -> そのリポジトリは class として実装を直接書くのではなくのではなく、 interface を用意する -> Spring Framework 側で proxy を使ってその interface を実装したクラスを生成する

あーそーゆーことね。完全に理解した(わかってない)。

ということで、 java.lang.reflect.Proxy について調べてみることにしました。

なおタイトルなどで 「java.lang.reflect.Proxy」 とわざわざ言っているのは、会社で悩まされがちなプロキシサーバーとの区別をつけるためです。

ただし面倒なのでこれより下は Proxy と書くことにします。

Proxy について

まずは Proxy について、ドキュメントを見てみましょう。

あーry

気を取り直して、 Proxy の動きを一言にまとめると、対象の class が持つメソッドが実行されたことを、その class が継承している interface を通して取得できるようにする、といったものです。
(私の理解では)

これによって、メソッドが実行される前後に別の処理を追加することが可能になります。

Sample

まずは動作を確認するため、 改訂2版 パーフェクトJava のサンプルを参考にコードを書いてみます。

まずは元の interface と class から。

IProxySample.java

package jp.masanori;

public interface IProxySample {
    void say();
    String getName();
    void setName(String name);
    private void sayInPrivate(){
        System.out.println("private");
    }
    default void callName(){
        System.out.println("default");
        sayInPrivate();
    }
}

ImplementProxySample.java

package jp.masanori;

public class ImplementProxySample implements IProxySample {
    @Override
    public void say(){
        System.out.println("hello world");
    }
    @Override
    public String getName(){
        return "masanori";
    }
    @Override
    public void setName(String name){
        this.name = name;
    }
}

で、これが今回の話の中心となる Proxy を取得するクラスです。

ProxySampleClass.java

package jp.masanori;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxySampleClass implements InvocationHandler {
    private final Object targetClass;
    public ProxySampleClass(Object targetClass) {
        this.targetClass = targetClass;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 対象のメソッドを実行する前に処理を追加.
        System.out.println("before invoking method.");
        // 対象のメソッド.
        Object o = method.invoke(targetClass, args);
        // 対象のメソッドを実行した後に処理を追加.
        System.out.println("after invoking method.");
        return o;
    }
}

Proxy を呼び出して利用するコードです。

App.java

package jp.masanori;

import java.lang.reflect.Proxy;

public class App {
    public static void main( String[] args ) {
        // Proxy を取得して IProxySample にキャストする.
        var proxy = (IProxySample)Proxy.newProxyInstance(
                App.class.getClassLoader(),
                new Class[]{ IProxySample.class },
                new ProxySampleClass(new ImplementProxySample()));

        System.out.println("start");
        proxy.say();
        System.out.println("after say");

        proxy.setName("masanori");
        System.out.println("after setName");

        System.out.println("My name is " + proxy.getName());

        proxy.callName();
        System.out.println("after callName");
    }
}

実行結果は下記の通りです。

start
before invoking method.
hello world
after invoking method.

after say

before invoking method.
after invoking method.
after setName

before invoking method.
after invoking method.
My name is masanori

before invoking method.
default
private
after invoking method.

after callName

結果からは下記のようなことがわかります。

  • 対象 interface の全 public メソッドが実行されるときに ProxySampleClass.javainvoke が呼ばれる(含 default)。
  • privateメソッドでは invoke が呼ばれない。
  • 戻り値を使って何か行う処理は、 invoke で追加された処理の実行後に実行される。

invoke について

ProxySampleClass.javainvoke で渡される引数についてです。

第二引数の method について、一応 method.getName() で Invoke されたメソッド名が取れるので、条件分岐して特定のメソッドのみ処理を追加する、といったことも可能ですが、複雑になるので特定の場合のみ実行したいなら呼び出し元で対応したほうが良いような気はします。

また method.invoke の第一引数は、Proxy で処理を追加したい interface を実装する class である必要があります。

第三引数は メソッドの引数がそのまま渡され、引数がない場合は空の配列が渡されます。

Proxy.newProxyInstance について

  • Proxy.newProxyInstance の第一引数となる getClassLoader() は、 Main でも Proxy 対象となる class でも OK のようです。
  • newProxyInstance の戻り値で getClass をすると、キャスト後でも com.sun.proxy.$Proxy0 が返ります。
  • 第二引数の class 配列には interface.class を指定する必要があり、例えば実装 class を使おうとすると IllegalArgumentException が発生します。
  • 第三引数には InvocationHandler を継承した class を渡し、そのコンストラクタには Proxy 対象となる class を渡します(メソッドが呼ばれたときに、元のメソッドを実行するのに使用)。
  • 第二引数に渡す interface は、 Proxy 対象となる class が継承していないものを渡すこともできます。
  • またキャストしないのであれば第二引数に空配列を渡しても OK です。あまり意味はないと思いますが。

Proxy が継承した interface (第二引数で渡している interface )は getInterfaces で取得できます。

for(var c : proxy.getClass().getInterfaces()){
    System.out.println(c);
}

Spring Data JPA の repository のコードを見てみる

最初の話に戻って、Spring Data JPA の repository のコードを見てみることにします。

Spring Framework の GUIDES のサンプルを見て、 Proxy がどのように使われているのか追ってみることにします。

https://spring.io/guides/gs/accessing-data-jpa/

Customer という Entity クラス(ドメインオブジェクト)に対して CustomerRepository という Repository (interface) が用意されています。

で、メインクラスである Application.java で CustomerRepository を継承した Proxy が渡される、という内容になっています。

Application.java

package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
    ~省略~
    @Bean
    public CommandLineRunner demo(CustomerRepository repository) {
    return (args) -> {
    ~省略~
    }
  }
}

この Proxy 、デバッガのブレークポイントを止めて見てみると、 org.springframework.data.jpa.repository.support.SimpleJpaRepository という class に対して生成されているようです。

https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

このクラスから EntityManager にアクセスしているわけですが、 Spring徹底入門 によると、処理の流れは下記のようなものとのことです。

  1. Spring Data JPA が CustomerRepository を継承した Proxy を生成する。
  2. SimpleJpaRepository に 1. の処理を委譲する。

で、実際の動きを見てみると、 SimpleJpaRepository に CustomerRepository のメソッドが追加されたような状態になっていて、これまで触れてきた Proxy の動きとは少し違っているようですね。

Mixin

Proxy を使って実現可能なこととして、 InvocationHandler の対象クラスが実装していないメソッドを追加する (Mixin) 、というものがあるそうです。

  • Javaにおける動的Mixin - Qiita

  • 追加したいメソッドを、 interface でデフォルトメソッドとして書いておく。

    1. を Proxy に継承させる。
  • そのまま呼び出すと実装クラスに該当メソッドがなくエラーになるため、 (InvocationHandler の) invoke でバインドする。

※下記のコードはあくまで特定の条件においてうまく動作することだけを確認したもので、エラー処理などが抜けています。
正しくは下記リンクをご覧ください。

https://qiita.com/kawasima/items/f735ef0c0a9fa96f6eb4

https://github.com/kawasima/enkan/blob/master/enkan-core%2Fsrc%2Fmain%2Fjava%2Fenkan%2Futil%2FMixinUtils.java

App.java

package jp.masanori;

import java.lang.reflect.Proxy;

public class App { public static void main( String args ) { // ImplementProxySample で継承していない IOtherSample を持った Proxyを生成する. var proxy = (IOtherSample)Proxy.newProxyInstance( App.class.getClassLoader(), new Class<?>{ IProxySample.class, IOtherSample.class}, new ProxySampleClass(new ImplementProxySample())); // IOtherSample のデフォルト実装を呼び出す. proxy.sayMessage(); } }

IOtherSample.java

package jp.masanori;

public interface IOtherSample {
    default void sayMessage(){
        System.out.println("hello everyone");
    }
}

ProxySampleClass.java

package jp.masanori;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Field;

public class ProxySampleClass implements InvocationHandler {
    ~省略~
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // method実行の後に処理を挟まないなら直接ReturnしてもOK.
        Object result;
        if (method.getDeclaringClass().isAssignableFrom(targetClass.getClass())) {
            result = method.invoke(targetClass, args);
        }
        else{
            // methodを定義しているクラス(IOtherSample)を取得.
            final Class declaringClass = method.getDeclaringClass();
            // methodの参照(MethodHandle)のファクトリであるLookupの取得.
            MethodHandles.Lookup lookup = MethodHandles.publicLookup()
                    .in(declaringClass);

            // そのままlookup.unreflectSpecialで対象methodのMethodHandleを取ろうとすると,
            // IllegalAccessExceptionが発生するのでallowedModesの値を変更.
            if (Modifier.isFinal(modifiers)) {
                  final Field modifiersField = Field.class.getDeclaredField("modifiers");
                  modifiersField.setAccessible(true);
                  modifiersField.setInt(f, modifiers & ~Modifier.FINAL);
                  f.setAccessible(true);
                  f.set(lookup, MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE);
            }
            // アクセス可能になったらProxyにMethodHandleをバインドしてmethodを実行する.
            MethodHandle methodhandle = lookup.unreflectSpecial(method, declaringClass);
            result = methodhandle.bindTo(proxy)
                    .invokeWithArguments(args);
        }
        return result;
    }
}

  • Proxy 対象の class が該当の method を定義している場合、 Mixin の処理を実行するとエラーになるため、isAssignableFrom で確認しています。
  • method にデフォルト実装がない場合は java.lang.AbstractMethodError が発生します。
  • ProxySampleClass のコメントにも書きましたが、 MethodHandle を取得して Proxy にバインドするためには Private アクセス特権が必要で、そのままだとエラーが発生します。
  • 上記の通りエラー処理などは含まれていないため、実際にはリンク先を確認するなどして正しくエラーが処理されるようにしてください。

正直ほとんどよくわからないままではあるのですが、 SimpleJpaRepository でもこれと同じような処理が行われているものと考えられます。

Java に限らず Reflection や Proxy を触りまくる、ということは普段はないかもしれませんが、また新たな世界が垣間見れたような気がします。

参照

Proxy

Mixin

英語でLightning Talk

はじめに

5/14に行われた Oracle Dev Tour Japan in Osaka で LT をさせていただきました。

kanjava.connpass.com

speakerdeck.com

初の英語での LT ということで、日本語とは違うところがあったので準備の話などとともに書き残しておくことにします。

なお一応書きますが、英語で LT やるコツとか出てきません。

僕が知りたいくらいです。

発表について

まぁ完全に準備不足であったと思います。

大きく2点あって、原稿は作ったもののそれを声に出して読む、という練習が足りなかったと思います。

原稿を忘れてしまっても、その場で作りながら話をする、ということが理想ではあるのですが、まだそこまで消化できていなかったのだと思います。

家だとどうしても子どもたちが寝ていたり横やりが入ったりと難しいので、広い公園やカラオケボックスなど練習できる場所の確保が必要ですね。

もう一つがスライドで、内容自体は自分なりにわかりやすくなるよう作ったつもりですが、発表のときに(ディスプレイが複製モードになっていなかったため)自分のPCのモニタから見えず、カンペ代わりにできなかったという問題がありました。

Office というかパワポについては、やっぱり MS Office 良いなぁ、という感じがします。
(スクリーン側に全画面でスライドを表示して、PC側でメモや経過時間などを見ることができるため)

家のディスプレイを繋いでの確認もやっておかないといけませんね。

なお Ubuntu でディスプレイの設定を変更するには、 Settings > Devices > Displays からできる。。。ハズ。

ディスプレイは Mac Mini 用に一台あるのですが、ケーブルが HDMI と DVI ケーブルしかなくて VGA が必要な PC につなげませんでしたorz

明日か明後日に買おっと。

英語で原稿を書くことについて

日本語で書くようにスラスラと英語が出てきてくれるのが理想ですが、これも現状では難しいので、 Google 先生に頼ることにしました。

前職で韓国語に翻訳する必要があったりしたときに使っていた方法ですが、日本語を韓国語に翻訳 -> 翻訳結果を日本語に再翻訳して意味が通るかを確認してました。

英語は多少は書ける(気がする)ので、単語は調べつつ英文を作る -> 日本語に翻訳して、おかしな日本語になっていたらやり直ししたりしていました。

あと発音がわからないときに、これまた Google 翻訳の発声機能を使って確認できたのも助かりました。

この方法で、英語のブログに挑戦してみるのも良いかもしれませんね。

原稿

せっかくなので用意していた原稿も載せておきます。


Start

Ok, Let's start.

I'm Masui Masanori.

Today I'll talk about "var".

Have you already used it?

about Java's 'var'

  • The official name is 'Local-Variable Type Inference'.
  • It infers the type from the right side value.
  • If the type can't be inferred, the compiling error is occured.
  • You should take care what type is inferred. Because the type sometimes differ from your expected.
  • It can declare the variable of anonymous class.

about C#'s 'var'

  • C# also has 'var'.
  • Most of the features are as same as Java's.

Question

  • I felt C# programmers are more actively seeking to use it than Java programmers.
  • Where does the difference come from? and should I use it?

Merit of using 'var'

First, I talk about the merits of 'var'.

  • It improve readability. Because it make the code simpler.
  • It induces better naming. Because the type name is omitted from left side, so the programmer will seek to compensate the infomations with other parts.
  • If the precise type isn't so important, You can treat the type ambiguous. For example in this code. Even if the returning value type is changed to array of string, you don't need any modifications.

Demerit of using 'var'

Next, I talk about the demerits. * It reduces readability. For example in this code, I don't know what type will be returned. And even if I can understand the type, you shouldn't name like this.

  • It risks type mismatch. The biggest problem is I may not be able to know the type mismatch because the compiling error isn't occured.

Where does the difference come from?

  • This situation is not much different between Java and C#.
  • One of the big difference is that C#'s 'var' was implemented since over 10 years ago, so the programmers have been used to using it.
  • I think maybe this is the reason.
  • I don’t know if the situation of Java also will be as same as C#'s.

Should we use ‘var’ ?

I move another question.

I think we should use ‘var’ if there is no special reason.Like the examples what I talked.

Because keeping code simple makes us to think other important parts of code.

And the most important reason of my opinion is my favorit IDEs of C# suggest using it.

Summary

  • Because ‘var’ has some merits and demerits, so please use it properly.​
  • If the variable needs the precise type, please write the type explicitly.​
  • If your team has coding rules, please follow them or update them first.​

Rerences

End

Thank you for listening.


おわりに

正直なところもっと練習しておけば。。。と思い頭をかきむしりたくなる気持ちを抑えつつこの記事を書いているわけなのですが、挑戦したこと自体はとても良かったし、貴重な経験だったと思っています。

ということで、そんな貴重な場を提供していただいた関ジャバの方々と Oracle Dev Tour Japan の方々、何より温かく見守ってくださった皆様、本当にありがとうございました(..)_

いつかどこかでリベンジも狙っていきます(๑•̀ㅂ•́)و✧

Java と C# の var をぼんやり比較

はじめに

みなさん Java10 使ってますか〜?(何)

Java10 で特に注目された(多分)新機能に、 var の導入があります。

var については後述しますが C♯ では ver.3.0 から導入されていたりします。

ただ C♯ では使えるところでは使う、位の扱いになっている(個人の感想です)のに対し、Javaではもう少し控えめに使おう、 という感じを受けました。

まぁJavaでは登場したばっかりだったり、別の型推論があったりと状況が違うところはあるためあくまで現状、 ということにはなりますが。

とはいえ、この違いはどこから来るのだろう?と思ったので調べてみることにしました。

var について

まずは var についてですが、ローカル変数の定義において、左辺に型を明示しなくても右辺の値から型を推論してくれる、というものです。

var id = 0; // int 型として扱われる.

int や string くらいならともかく、10 文字 20 文字と長くなってくると見る方も辛くなってきますね。

そのような場合でもスッキリ書ける、という利点があります。

  • JavaScript などとは異なり、 var を使うからといって定義後に別の型に変更することはできません。
  • コンパイル時に推論された型に置き換わります。
  • 右辺の型から推論するため、「var e;」 や 「var n = null;」 のようにするとエラーになります。

匿名クラス

また var が必須となる場合として、匿名クラスがあります。

Java

var person = new Object() {
    private String name;
    private int id;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getId(){
        return id;
    }
    public void setId(int id){
        this.id = id;
    }
};
person.setName("masanori");
person.setId(0);

System.out.println("name: " + person.getName() + " id: " + person.getId());

C♯

var person = new
{
    Name = "masanori", Id = 0,
};
Console.WriteLine("name: " + person.Name + " id: " + person.Id);

Java ではメソッドの定義が可能だったり、定義後の変数の変更が可能だったりと、言語仕様上の違いはありますが、いずれも var を使わずに定義しようとするとエラーになります。

Java は 「new Object」 となっているので一見 Object 型かな?とも思ってしまいますが。

var が使えない場合

ローカル変数であれば常に var が使えるかというと、そうではありません。
いくつかの状況ではコンパイルエラーになります。

ラムダ式 (Java, C♯)

下記のようなラムダ式では右辺の方が確定しないので var を使うことはできません。

// Compile error.
var lambdasample = () => {
   // Do something.
};

Java

var lambdasample = (Runnable)(() -> {
   // Do something   
});
Runnable lambdasample2 = ()->{
   // Do something
};

C♯

// OK
var lambdasample = (Action)(() => {
    // Do something   
});

// OK
Action lambdasample = () => {
   // Do something
};

配列の定義 (Java, C♯)

配列を定義する場合、下記のように書くとコンパイルエラーとなります。

// Compile error.
var arraySample = {0, 2};

理由は型が int[] に限定できないため、ということのようです。

Java

// OK.
var arraySample1 = new int[]{0, 2};
// OK.
int[] arraySample2 = {0, 2};

C♯

// OK. "int"は書かなくてもOK.
var arraySample1 = new []{0, 2};
// OK.
int[] arraySample2 = {0, 2};

C♯の arraySample2 で int が推論されるなら、 arraySample も良さそうな気がしないでもないですが。

継承しているクラス・インターフェースとして定義する (C♯)

例えば定義しようとしている変数が、このようなインターフェースを継承したクラスであった場合。

ISample.java

public interface ISample {
    String getName();
}

SampleClass.java

public class SampleClass implements ISample{
    private String name;

    SampleClass(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}

var を使うと、インターフェースではなくそれを実装している SampleClass として定義されます。

var sample = new SampleClass("hello");

ISample i = sample;
SampleClass s = sample;

// SampleClassとして定義される.
var s2 = sample;

この例で見るとそんなに大きな問題はないのですが、(C♯の) foreach で var を使った場合に思ったメソッドが出てこなくて???となる、といったことがまれによくありますね。

また C♯ で、(おそらく)上記の理由により、下記で SampleClass 、AnotherSampleClass がともに同じインターフェース (ISample) を継承していても下記のように書くことはできません。

// CompileError.
var samples = new[] {
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

// OK
ISample[] samples2 = {
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

// OK
var samples3 = new ISample[]{
    new SampleClass(1, "1"),
    new AnotherSampleClass(2, "2"),
};

ダイアモンド演算子 (Java)

Java7 で導入されたダイアモンド演算子と var を一緒に使うことはできません。

正確には使うこと自体は可能ですが、下記の場合だと型が ArrayList< Object> になってしまいます。

// ArrayList< Object>
var sampleList = new ArrayList< >();

// List< String>
List< String> sampleList2 = new ArrayList< >();

まぁダイアモンド演算子が左辺から型を推論するものである以上当然とも言えますが。

var をいつ使うべきか

では、使えるところではすべて var を使うべきでしょうか。
これは C♯ でも議論が行われていたようですし、今 Java で行われていたりもします。

で、先に自分の考えを書いてしまうと、使えるところではどんどん var を使って良いと思っています。

var を使うメリット・デメリット

とはいえまず var を使うメリット・デメリットから。

varを使うメリット

  1. 特に型名が長い場合も簡潔に書ける
  2. 変数の型を変えたい場合の修正量が少なくなる
  3. メソッドの戻り値が分かっていなくても書ける
  4. コードを書く量が少なくなる

varを使うデメリット

  1. 型の情報が失われる (特に IDE ではなく Web 上でコードを見た場合に調べづらい)
  2. 呼び出しているメソッドの戻り値が変更された場合に気づきにくい

メリット 3. は特にあまり使ったことのないメソッドなどを調べたりするのに便利ですね。

メリット 4. は他の方の意見通り、 IDE を使っているなら旨味は少ないかもしれません。
ただ、メソッド名まで型を気にせずに書ける、という点は利点かもしれません。

ドキュメントでの扱い

ドキュメントでは var をどのように使うべきと書かれているかを見てみます。

Microsoft Docs では下記のように書かれています。

  • 変数の型が割り当ての右側から明らかである場合、または厳密な型が重要でない場合は、ローカル変数の暗黙の型指定を使用します。
  • 割り当ての右側から型が明らかではない場合、var を使用しないでください。
  • 変数の型を指定するときに変数名に頼らないでください。 変数名が正しくない場合があります。
  • dynamic の代わりに var を使用しないようにしてください。
  • for ループおよび foreach ループでループ変数の型を決定するときは、暗黙の型指定を使用します。

引用元: C# のコーディング規則 (C# プログラミング ガイド) - Microsoft Docs

一方の Java についてですが、 OpenJDK の Style Guidelines を見てみます。

なんちゃって翻訳のため、詳しい内容や原文などは下記をご覧ください(..)_

変数名

var を使うことで、その変数についての左辺の情報は減少します。

それだけに適切な変数名をつける、ということがより重要になってきます。
前述のドキュメントでも触れられていますし、 var を使うべきか、という話でほぼ必ず登場します。

良い変数名を付けることを強制できることがメリット、という意見もあれば

変数名などで変なルールを作るべきでない、という話もあります。

これについては後者に賛成しますが、 var を使う・使わないに限らずわかりやすい名前を付けることは大事ですね。

型の詳細

var を使う・使わないを考える上でもう一つ重要なことが、その変数を使うにあたって型の詳細が必要かどうか、ということだと思います。

例えば C♯ でメソッドを呼び出して、その戻り値を foreach で使いたい場合、それが IEnumerable でも List でも配列でも特に問題はないと思います。

public void DoSomething()
{
    // samplesがListや配列でもOK.
        var samples = GetSamples();
    foreach (var s in samples)
    {
        // 何かの処理.
    }
}
private IEnumerable GetSamples()
{
    return Enumerable.Range(0, 10)
        .Select(n => new SampleClass(n, "masanori " + n));
}

このような場合、 var を使ったとしても可読性は下がらないのではないでしょうか。
(もしくは型を明示したとしてもあまり可読性が向上しないように思います)

一方、 float だと思っていたものが int だった場合など、型が異なると問題が発生する場合もあります。

とりわけ Effective C♯ で指摘されている、メソッドの戻り値が変更されてしまう危険性がある場合は、型名を明示しておいた方が良いかもしれません。

ということでこの項の最初の話に戻りますが、私の考えとしては、基本的には var を使い、型が正確に一致していないと問題が発生する場合や、インターフェース型として扱いたいなど必要がある場合のみ型を明示する、という感じです。

こうすることで、厳密な型が必要な箇所とそうでもない箇所のメリハリもつく気もしますし。

あと、var を使う・使わないにかかわらずその変数がどのように振る舞うのかは正しく認識しておく必要がありますね。

おわりに

結局 C♯ と Java でなぜ捉え方が違っている(ように感じる)のか、という話については、仕様として導入されてからの時間によっているような気がします。

2011年とか昔の記事では、 C♯ でも var の使用に否定的な意見も見られたので。
(C♯ では ver.3.0 に導入された話なので、今更話題にもなりにくいというのはあると思いますが)

また個人的には、 JetBrains の ReSharper や Rider がデフォルトで積極的に var をおすすめしてくるのも大きな要素だとは思いますw

今後 IntelliJ IDEA がどうなるのか、 Java が C♯ と同じような状況になるかはわかりませんが。

途中にも出てきましたが、 Java にせよ C♯ にせよ、 var を使うことが必ずしも最良、というわけではありません。

ただ、シンプルにできるところはできるだけ簡素化し、重要なところに力を注げると良いなとは思います。

参照

C♯

Java