﻿# 如何讓 LINQPad 查詢直接當成 Console 來執行

我現在幾乎所有的 Side Project 都是在 [LINQPad](https://www.linqpad.net/) 寫的，因為實在是太方便，不但啟動速度快，而且也很好管理，萬能的 `Dump()` 方法有好多神奇用法，實在讓人愛不釋手。除此之外，你其實也可以拿 LINQPad 來寫「腳本」(C# Script)，使用上雖然不能直接編譯成 `*.exe` 可執行檔，但是搭配 `lprun8` 來執行其實也不會很麻煩，對於需要快速 Prototyping 的時候，使用 LINQPad 不失為一個好選擇。

![image2](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/90d9693593777d069f1e-3998d7b7-a041-43e2-beac-b773214d8ccc.webp)

LINQPad 內建一個 `lprun` 執行檔，但不同的 LINQPad 版本，其執行檔的名稱與路徑稍微有點不同：

-   LINQPad 5: `C:\Program Files (x86)\LINQPad5\LPRun.exe`
-   LINQPad 6: `C:\Program Files\LINQPad6\LPRun6.exe`
-   LINQPad 7: `C:\Program Files\LINQPad7\LPRun7.exe`
-   LINQPad 8: `C:\Program Files\LINQPad8\LPRun8.exe`

直接執行 `lprun8` 會看到一些說明：

```
Usage: lprun8 [<options>] <scriptfile> [<script-args>]

options: (all case-insensitive)
 -format={text|html|htmlfrag|csv|csvi}   Output format. csvi=invariant CSV.
 -cxname=<connection-name>               Sets/overrides a script's connection.
 -lang=<language>                        Sets/overrides a script's language.
 -warn                                   Writes compiler warnings (to stderr).
 -optimize                               Enables compiler optimizations.
 -compileonly                            Just checks that query will compile.
 -recompile                              Ignores compiler cache, forces build.
 -nunuget                                Freshens NuGet references to latest.
 -fx=<major.minor>                       Run under specified .NET version.

scriptfile: Path to script. If it's a .linq file, -lang & -cxname are optional.

script-args: Args following <script-filepath> are passed to the script itself.

Examples:
  lprun8 TestScript.linq
  lprun8 TestScript.linq > results.txt
  lprun8 script1.linq | lprun8 script2.linq
  lprun8 -format=csv script.linq HelloWorld

Go to https://www.linqpad.net/lprun.aspx for detailed help.
Version=8.2.4
```

以下我就列出幾個常見的使用方式。

1.  執行 LINQPad 查詢 (預設輸出 `text` 格式)
    
    ```powershell
    lprun8 test.linq
    ```
    
2.  執行 LINQPad 查詢，並以 `html` 格式輸出
    
    使用 `html` 格式輸出，其輸出的內容將會跟你在 LINQPad 中直接執行的結果長的一模一樣！
    
    ```powershell
    lprun8 -format=html test.linq > output.html
    ```
    
    你可以用 Google Chrome 直接開啟這個檔案：
    
    PowerShell
    
    ```powershell
    & "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" "$(PWD)\output.html"
    ```
    
    Command Prompt
    
    ```batch
    "%programfiles(x86)%\Google\Chrome\Application\chrome.exe" "%CD%\a.html"
    ```
    
3.  執行 LINQPad 查詢，並帶入命令列參數
    
    要將命令列參數傳入 LINQPad 查詢，其 Language 必須使用 `C# Program`，而且 `Main()` 方法要加入 `string[] args` 參數：
    
    ```csharp
    void Main(string[] args)
    {
      args.Dump();
    }
    ```
    
    如果你執行 `lprun8 test2.linq` 的話，你會看到類似這樣的輸出：
    
    ```csharp
    args: []
    ```
    
    如果你執行 `lprun8 test2.linq Hello World` 的話，你會看到這樣的輸出：
    
    ```csharp
    args: [
      "Hello",
      "World"
    ]
    ```
    
4.  執行 LINQPad 查詢，並輸出 `CSV` 格式
    
    要輸出 CSV 格式，首先你要在查詢中輸出「表格」或「清單」(兩筆以上) 類型的資料，這樣輸出 CSV 才有意義。
    
    我用以下程式碼為例：
    
    ```csharp
    void Main(string[] args)
    {
      Encoding.GetEncodings().Dump();
    }
    ```
    
    如果你執行 `lprun8 -format=csv test3.linq` 的話，你會看到這樣的輸出：
    
    ```csv
    CodePage,Name,DisplayName
    1200,utf-16,Unicode
    1201,utf-16BE,Unicode (Big-Endian)
    12000,utf-32,Unicode (UTF-32)
    12001,utf-32BE,Unicode (UTF-32 Big-Endian)
    20127,us-ascii,US-ASCII
    28591,iso-8859-1,Western European (ISO)
    65001,utf-8,Unicode (UTF-8)
    ```
    
    注意: 就算你在程式碼中輸出字串，只要不是「表格」或「清單」的形式，就不會輸出。
    
    例如：
    
    ```csharp
    void Main(string[] args)
    {
      Encoding.GetEncodings().Dump();
    
      "Header".Dump();
    
      Encoding.GetEncodings().Dump();
    }
    ```
    
    如果你執行 `lprun8 -format=csv test3.linq` 的話，你會看到這樣的輸出：
    
    ```csv
    CodePage,Name,DisplayName
    1200,utf-16,Unicode
    1201,utf-16BE,Unicode (Big-Endian)
    12000,utf-32,Unicode (UTF-32)
    12001,utf-32BE,Unicode (UTF-32 Big-Endian)
    20127,us-ascii,US-ASCII
    28591,iso-8859-1,Western European (ISO)
    65001,utf-8,Unicode (UTF-8)
    CodePage,Name,DisplayName
    1200,utf-16,Unicode
    1201,utf-16BE,Unicode (Big-Endian)
    12000,utf-32,Unicode (UTF-32)
    12001,utf-32BE,Unicode (UTF-32 Big-Endian)
    20127,us-ascii,US-ASCII
    28591,iso-8859-1,Western European (ISO)
    65001,utf-8,Unicode (UTF-8)
    ```
    
    > 注意：你也可以直接在查詢中呼叫 `Util.ToCsvString (IEnumerable<T> elements, ...)` 函式，他會自動將「表格」或「清單」等內容變成 CSV 格式的字串。
    
5.  將一個 LINQPad 查詢的執行結果透過 Pipe 傳給下一個 LINQPad 查詢
    
    你可以透過 Pipe `|` 來將一個 LINQPad 查詢的執行結果傳給下一個 LINQPad 查詢，組成強大的自動化腳本。
    
    假設 `test4.linq` 內容如下：
    
    ```csharp
    void Main(string[] args)
    {
      Encoding.ASCII.Dump();
    }
    ```
    
    假設 `test5.linq` 內容如下：
    
    ```csharp
    void Main(string[] args)
    {
      string str = args?.Length > 0 ? args[0] : "Hello World";
    
      #if CMD
      str = Console.In.ReadToEnd();
      #endif
    
      str.Dump();
    }
    ```
    
    這裡特別值得一提的地方，在於 `#if CMD` 與 `#endif` 這兩個條件式編譯語法([C# 前置處理器指示詞](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/preprocessor-directives?WT.mc_id=DT-MVP-4015686))，這裡的 `CMD` 是 LINQPad 專屬的條件式常數，只要你是透過 `lprun8` 執行，這個 `CMD` 就會被設定，這樣你就可以在 LINQPad 查詢中使用 `Console.In.ReadToEnd()` 來讀取 Pipe 進來的資料。
    
    所以當你執行以下程式時：
    
    ```powershell
    lprun8 test4.linq | lprun8 test5.linq
    ```
    
    就會得到以下結果：
    
    ```
    str: Encoding.ASCII: {
      "IsSingleByte": true,
      "BodyName": "us-ascii",
      "EncodingName": "US-ASCII",
      "HeaderName": "us-ascii",
      "WebName": "us-ascii",
      "WindowsCodePage": 1252,
      "IsBrowserDisplay": false,
      "IsBrowserSave": false,
      "IsMailNewsDisplay": true,
      "IsMailNewsSave": true,
      "EncoderFallback": {
        "DefaultString": "?",
        "MaxCharCount": 1
      },
      "DecoderFallback": {
        "DefaultString": "?",
        "MaxCharCount": 1
      },
      "IsReadOnly": true,
      "CodePage": 20127
    }
    ```
    
6.  除了用 `|` (Pipe) 來串接 LINQPad 查詢外，你也可以用 `Util.Run()` 工具函式，讓你直接從查詢中執行另外一個查詢
    
    以下是一段 LINQPad 查詢的範例：
    
    ```csharp
    void Main(string[] args)
    {
      string result = Util.Run("test2.linq", QueryResultFormat.Text, "Hello", "World").AsString();
      result.Dump();
    }
    ```
    
    執行結果如下：
    
    ```
    result: args: [
      "Hello",
      "World"
    ]
    ```
    
    這裡特別值得一提的地方，在於 `Util.Run()` 最後可以用 `As*()` 方法來轉換成不同的型別：
    
    -   `Util.Run().AsString()`: 將結果轉換成字串
    -   `Util.Run().AsStringAsync()`: 將結果轉換成 `Task<string>` 非同步版本
    -   `Util.Run().AsMailAttachment("attachment.txt")`: 將內容轉成 `System.Net.Mail.Attachment` 物件，便於寄出郵件並夾帶附件
    -   `Util.Run().AsMailAttachmentAsync("attachment.txt")`: 將內容轉成 `Task<System.Net.Mail.Attachment>` 非同步版本
    -   `Util.Run().Save("filename.txt")`: 將結果儲存成檔案
    -   `Util.Run().SaveAsync("filename.txt")`: 將結果儲存成檔案，回傳非同步的 `Task` 物件
    -   `Wait()`: 等待 `Util.Run()` 執行完畢
    -   `WaitAsync()`: 回傳非同步的 `Task` 物件，可搭配 `await` 等待 `Util.Run()` 執行完畢
7.  從批次檔捕捉例外狀況
    
    ```batch
    lprun8 test.linq
    if %errorlevel% neq 0 echo Error!
    ```
    
8.  變更「編譯」選項
    
    加入 `-optimize` 參數，可讓 LINQPad 查詢執行在最佳化的狀態下，執行速度較快！
    
    ```powershell
    lprun8 -optimize test4.linq
    ```
    
    加入 `-warn` 參數，可讓 LINQPad 查詢執行時顯示編譯器的警告訊息。
    
    ```powershell
    lprun8 -warn test4.linq
    ```
    
    加入 `-compileonly` 參數，可以自動編譯 LINQPad 查詢中的程式碼，但不執行。
    
    > 注意: 首次透過 `lprun8` 執行一個新的 LINQPad 查詢時，預設就會包含編譯的過程，因此啟動速度較慢。但如果你預先編譯過，下次執行就會快很多！
    
    ```powershell
    lprun8 -compileonly test4.linq
    lprun8 test4.linq
    ```
    

### 相關連結

-   [LINQPad Command-Line and Scripting](https://www.linqpad.net/lprun.aspx)
-   [C# 前置處理器指示詞](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/preprocessor-directives?WT.mc_id=DT-MVP-4015686)
