LINQPadで始めるC#

ログを抽出するサンプルプログラムを通して、LINQPadの簡単な使い方と、C#で下記の操作をする方法を紹介する。
- ファイルの読み書き
- フィルタ、射影 (LINQ)
- 文字列から数値・日時に変換

# 対象者
C#初心者。ただし、CやJavaなどほかの言語は経験し、下記については把握している前提で。

- プログラムの制御構造：条件分岐、ループ、サブルーチン、例外、など
- 型：文字列、数値、ブール、日時、など
- オブジェクト指向：クラス、インタフェース、など

# LINQPadとは

.NETプログラムを「手軽」に書けて、「素早く」実行できて、「見やすい」実行結果を表示してくれる素敵な開発/実行環境。Visual Studio とは比にならないくらいお手軽なので、学習にとても向いている。（と思う）

ただ、手軽さの反面、アプリケーションの生成や配布が必要な規模の開発には向かない。ちょっとした開発にはLINQPad, ちゃんとした開発には Visual Studio, というように使い分けるとよい。

リンク：[LINQPad - The .NET Programmer's Playground](http://www.linqpad.net/)

## LINQPadでできること

- LINQを書いて実行できる
- .NET(C#, VB, F#)プログラムを書いて実行できる
- SQL Server などデータベースにクエリを投げられる
- 実行結果をわかりやすく表示してくれる

なお、LINQPadは無償で使えるが、有償版もある。課金することで下記のような便利機能も使えるようになる。

- コードの入力補完・ヘルプ表示
- デバッグ(ブレークポイント、ステップ実行、変数表示、など)
- NuGet

特に入力補完とヘルプ表示があると学習が捗るので買おう！たったの $89 だ。(2017/4/25時点)

リンク：[LINQPad - Purchase](https://www.linqpad.net/Purchase.aspx)

# Hello World

とりあえず動かしてみる。

## LINQPad の起動

起動すると下図のような画面が表示される。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/118451/c5ab427e-4794-0382-5f5b-72c83dab74e7.png)


左側の "Add connection" や "My Queries" はデータベースへの接続や、書いたプログラムの保存・再利用に使用するが、今はまだ気にしなくてよい。

## コードを書く

テキストエディタにコードを書く。
コンソールに文字列を出力するには、ConsoleクラスのWriteLineメソッドを使う。

```csharp
Console.WriteLine("Hello");
```
![2.png](https://qiita-image-store.s3.amazonaws.com/0/118451/9000aa53-d774-3d99-6a05-116947df0b9d.png)

ちなみに課金プレイヤーは コードスニペット を使える。
c, w, Tab と打ってみると、下記のようにコードが自動入力されるはずだ。

```csharp
Console.WriteLine();
```
コードスニペットが使いこなせるようになると生産性が大きく上がるので、ぜひ慣れてほしい。cwのほかにもいろいろある。↓は個人的によく使うスニペット。

|ショートカット|コード|
|--:|:--|
|cw|Console.WriteLine();|
|prop|public int MyProperty { get; set; }|
|ie|IEnumerable&lt;T&gt;|
|foreach|foreach (var element in sequence)<br>{<br><br>}|

## 実行する

テキストエディタ左上の実行ボタンを押すとコードが実行され、実行結果がテキストエディタの下に表示される。

![3.png](https://qiita-image-store.s3.amazonaws.com/0/118451/e135a213-b35f-20d5-9537-738907d9052d.png)


Visual Studio で Hello World するよりも10倍くらい楽だね！

# IISのログ解析スクリプト

IISのログは↓みたいな感じ。このログを抽出するプログラムをLINQPadで書いてみる。

```
#Software: Microsoft Internet Information Services 8.5
#Version: 1.0
#Date: 2017-04-22 11:40:22
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2017-04-20 11:42:23 192.168.0.2 GET / - 80 - 123.123.123.266 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:52.0)+Gecko/20100101+Firefox/52.0 http://example.jp/ 200 0 0 109
2017-04-20 12:42:23 192.168.0.2 GET /favicon.ico - 80 - 123.123.123.266 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:52.0)+Gecko/20100101+Firefox/52.0 - 404 0 2 15
2017-04-20 13:57:09 192.168.0.2 GET /hogehoge id=1234 80 - 66.249.65.138 Mozilla/5.0+(compatible;+Googlebot/2.1;++http://www.google.com/bot.html) - 200 0 0 218
2017-04-20 15:20:17 192.168.0.2 GET /test.aspx limit=10 80 - 123.456.789.1 Mozilla/5.0+(iPhone;+CPU+iPhone+OS+8_4+like+Mac+OS+X)+AppleWebKit/600.1.4+(KHTML,+like+Gecko)+Version/8.0+Mobile/12H143+Safari/600.1.4 http://example.jp/ 404 0 0 0
2017-04-20 15:20:45 192.168.0.2 POST /test.aspx - 80 - 123.456.789.1 Mozilla/5.0+(iPhone;+CPU+iPhone+OS+8_4+like+Mac+OS+X)+AppleWebKit/600.1.4+(KHTML,+like+Gecko)+Version/8.0+Mobile/12H143+Safari/600.1.4 http://example.jp/ 404 0 0 0
```

※サンプルコードを動かしたい場合はこのサンプルログを C:\tmp\iis.log に保存してね。


## LINQPad でプログラムを書く準備

複数行にまたがるプログラムを書きたいときは、Language を C# Program に設定する。
![4.png](https://qiita-image-store.s3.amazonaws.com/0/118451/81121a56-e58c-3c55-5949-4747d19672cd.png)


## ファイルから読んで表示する

### ここで使うメソッド

- File.ReadAllLines(string path)
- Dump()

File.ReadAllLines()はファイルから文字を読むメソッドの一つで、行単位で文字列の配列形式にしてくれる。
ReadAllLines()のほか、ReadAllText()や、ReadAllBytes()もある。

Dump() は LINQPad が提供する便利メソッド。文字列、配列、オブジェクト、とりえあえずなんでも .Dump() をつければ、いい感じに表示してくれる。Visual Studio で開発するときは使えないので注意。

※課金プレイヤーは、メソッドの入力中とメソッドにマウスカーソルを乗せたときに概要が表示される。使い方が分からなくなったらこれを見ればよい。それでもわからなければF1を押すとググってくれる。
![4-2.png](https://qiita-image-store.s3.amazonaws.com/0/118451/1484e135-8c40-469b-38ab-c1360ee427f1.png)

![4-3.png](https://qiita-image-store.s3.amazonaws.com/0/118451/fa691377-58be-5513-6817-f00da4e60c87.png)

### コードと実行結果
```csharp
void Main()
{
    string file = @"C:\tmp\iis.log";
    string[] lines = File.ReadAllLines(file);
    lines.Dump();
}
```

![5.png](https://qiita-image-store.s3.amazonaws.com/0/118451/de838981-6714-3a5d-36f6-d3e0cd5f402d.png)


文字列配列が良い感じに表示された。Dump()便利。


## いらない行をフィルタする

 #で始まる行は邪魔なので消したい。

### ここで使うメソッド

- Enumerable.Where&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, bool&gt; predicate)
- String.StartsWith(string value)

Where()は LINQ と呼ばれるメソッドの一つで、フィルタに使う。Func&lt;TSource, bool&gt; predicate はフィルタの条件で、条件にマッチした要素の IEnumerable&lt;T&gt; を返す。

※とりあえず今は IEnumerable&lt;T&gt;は配列ではないけど配列のようなものくらいの認識で良い。FuncとかTSourceとかもとりあえずおいておいて、今回は使い方だけ。

String.StartsWith() は文字列クラスのメソッド。文字列の先頭が指定した文字列と一致するかを判定し、一致したら true, 一致しなかったら false を返す。

### コードと実行結果
```csharp
void Main()
{
    string file = @"C:\tmp\iis.log";
    string[] lines = File.ReadAllLines(file);
    lines
    .Where(str => !str.StartsWith("#")) // 後で補足
    .Dump();
}
```

![6.png](https://qiita-image-store.s3.amazonaws.com/0/118451/c80c75e6-62bc-b352-6f28-e6b0d8cc5a51.png)


 #で始まる行を取り除けた！

### Whereの引数について補足
Whereメソッドの引数に渡している式はラムダ式といって、簡単に書ける関数みたいなもの。

```csharp
// predicate をラムダ式で書く 
str => !str.StartsWith("#")
```

↑のラムダ式は、↓のメソッドとだいたい同じ意味を持つ。

```csharp
// predicate をメソッドで書く 
bool Test(string str)
{
    return !str.StartsWith("#");
}
```

なので実はこうも書ける。

```csharp
void Main()
{
    string file = @"C:\tmp\iis.log";
    string[] lines = File.ReadAllLines(file);
    lines
    .Where(Test)
    .Dump();
}
bool Test(string str)
{
    return !str.StartsWith("#");
}
```

ただ、今のところはラムダ式がどーのこーのはあまり深く考えずに、こんな感じで書けるんだなーくらいで。

```csharp
// 例：文字列"#"で始まらない
.Where(str => !str.StartsWith("#"))

// 例：文字列"GET"を含む
.Where(str => str.Contains("GET"))

// 例：文字列長さ6より長い
.Where(str => str.Length > 6)
```

## フィールドの値でフィルタする

ログの日時や、応答にかかった時間でフィルタしてみたい。それにはまずフィールドを分割する必要もある。

### ここで使うメソッド

- String.Split(params char[] separator)
- DateTime.Parse(string s)
- Int32.Parse(string s)

String.Split() は文字列を separator で分割して、文字列配列にしてくれる。

DateTime.Parse() は文字列を DateTime に変換してくれる。

Int32.Parse() は文字列を int に変換してくれる。


### コードと実行結果

20日のログで、応答に20ミリ秒以上かかったリクエストを抽出するコード

```csharp
void Main()
{
    string file = @"C:\tmp\iis.log";
    string[] lines = File.ReadAllLines(file);
    lines
    .Where(str => !str.StartsWith("#"))
    .Where(str =>
    {
        string[] fields = str.Split(' ');
        DateTime dateTime = DateTime.Parse($"{fields[0]} {fields[1]}");
        int timeTaken = Int32.Parse(fields[14]);
        return dateTime.Day == 20 && timeTaken >= 20;
    })
    .Dump();
}
```

![8.png](https://qiita-image-store.s3.amazonaws.com/0/118451/1ddf9773-30b6-6e1c-d1a7-a454118a00a6.png)



## 特定の列だけ抜き出してファイルに書く


### ここで使うメソッド

- Enumerable.Select&lt;TSource, TResult&gt;(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, TResult&gt; selector)
- Enumerable.ToArray&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source)
- File.WriteAllLines(string path, string[] contents);

Select()は要素を変換する。Func&lt;TSource, TResult&gt; selector は変換関数。

ToArray() は IEnumerable&lt;T&gt; 型を配列に変換する。Where() とか Select() とかの戻り値は IEnumerable&lt;T&gt; という型で、配列ではない。

File.WriteAllLines() ReadAllLines()と対になるメソッドで、文字列の配列形式を1行1要素で書いてくれる。WriteAllLines()のほか、WriteAllText()や、WriteAllBytes()もある。

### コードと実行結果

```csharp
void Main()
{
	string file = @"C:\tmp\iis.log";
	string[] lines = File.ReadAllLines(file);
	string[] ipaddresses = lines
	                       .Where(str => !str.StartsWith("#"))
	                       .Select(str =>
	                       {
	                           string[] fields = str.Split(' ');
	                           return fields[8]; // c-ip
	                       })
	                       .ToArray();
	string outfile = @"C:\tmp\cip.log";
	File.WriteAllLines(outfile, ipaddresses);
}
```

![9.png](https://qiita-image-store.s3.amazonaws.com/0/118451/2b43b71d-8625-ae46-1651-37632433a60b.png)


~~grep + awk で良くね~~


# まとめ


LINQPadは学習にも向いているのでオススメ！
- Visual Studio よりもお手軽
- 出力がリッチで分かりやすい
- Visual Studio のような入力保管・ヘルプ表示もしてくれる（課金すれば）

今回は下記についてほんの少しだけ触れただけだけど、気になったことがあったらどんどん学習してC#に慣れて行ってもらえれば。
- ファイルの読み書き
- フィルタ、射影 (LINQ)
- 文字列から数値・日時に変換