C#で三菱PLCと通信する方法（第3弾）デバイス読み出しコマンドの使い方

## はじめに
前回の記事では、通信に必要なMcpXの各種設定（通信方式・フレーム・プロトコルなど）を紹介しました。

> 🔗 [第2弾はこちら](https://qiita.com/YudaiKitamura/items/a14b50220181ed7e38a8)

今回は、McpXが対応している**デバイス読み出し系コマンド**を中心に、サンプルコードと一緒に紹介します。

## 使用ライブラリ
- [McpX](https://yudaikitamura.github.io/McpX/)

## 対応しているデバイス読み出しコマンド一覧
|名称|説明|同期メソッド|非同期メソッド|
|-|-|-|-|
|**単一読み出し**|デバイスの単一値を取得します。|`Read<T>(Prefix prefix, string address)`| `ReadAsync<T>(...)`|
|**一括読み出し**|連続したデバイスから、指定数のデータを一括で読み出します。|`BatchRead<T>(Prefix prefix, string address, ushort length)` | `BatchReadAsync<T>(...)` |
|**ランダム読み出し**|非連続アドレスからワード・ダブルワード単位で読み出します。|`RandomRead<T1, T2>((Prefix, string)[] wordAddresses, (Prefix, string)[] doubleWordAddresses)`|`RandomReadAsync<T1, T2>(...)`|

## デバイスアドレスの指定方法
McpXでは、各コマンドでPLCのデバイスを指定する際に、`Prefix`（デバイス種別）と `Address`（アドレス）を組み合わせて指定します。

`Prefix` は列挙型（`enum`）として定義されており、例えば内部リレー（M）を指定する場合は `Prefix.M`、データレジスタ（D）の場合は `Prefix.D` のように指定します。

`Address` は文字列型で指定します。
10進数のアドレスを指定する場合は `"1234"` のように、16進数のアドレスを指定する場合は `"4D2"` のように記述します。

### デバイス種別（Prefix）一覧
| 名称                         | キー |
|------------------------------|------|
| 入力                         | X    |
| 出力                         | Y    |
| 内部リレー                   | M    |
| ラッチリレー                 | L    |
| アナンシェータ               | F    |
| エッジリレー                 | V    |
| リンクリレー                 | B    |
| データレジスタ               | D    |
| リンクレジスタ               | W    |
| タイマ接点                   | TS   |
| タイマコイル                 | TC   |
| タイマ現在値                 | TN   |
| 積算タイマ接点               | SS   |
| 積算タイマコイル             | SC   |
| 積算タイマ現在値             | SN   |
| カウンタ接点                 | CS   |
| カウンタコイル               | CC   |
| カウンタ現在値               | CN   |
| リンク特殊リレー             | SB   |
| リンク特殊レジスタ           | SW   |
| ステップリレー               | S    |
| ダイレクトアクセス入力       | DX   |
| ダイレクトアクセス出力       | DY   |
| 特殊リレー                   | SM   |
| 特殊レジスタ                 | SD   |
| インデックスレジスタ         | Z    |
| ファイルレジスタ（ブロック） | R    |
| ファイルレジスタ（連番）     | ZR   |

:::note warn
誤ったデバイスアドレスの指定をした場合、例外（`DeviceAddressException`）をスローします。

例外条件
- 10進のデバイスに対して、アドレスに数字以外の文字列を含んでいる
- 16進のデバイスに対して、アドレスに16進に変換できない文字列を含んでいる
:::

## 読み出す型の指定方法
McpXでは、ジェネリックを使用した実装をしており、読み出すデータの型は、各メソッドのジェネリック型引数 `T` に指定します。
例えば、単一読み出しの`Read<T>(...)`メソッドでshort型を指定する場合は、`Read<short>(...)`のように記述します。
### 対応している型一覧
| 型         | データ型分類                     |
|------------|----------------------------------|
| `bool`     | 論理型（真偽値）                 |
| `short`    | 整数型（16ビット符号付き）       |
| `ushort`   | 整数型（16ビット符号なし）       |
| `int`      | 整数型（32ビット符号付き）       |
| `uint`     | 整数型（32ビット符号なし）       |
| `long`     | 整数型（64ビット符号付き）       |
| `ulong`    | 整数型（64ビット符号なし）       |
| `float`    | 浮動小数点型（単精度）           |
| `double`   | 浮動小数点型（倍精度）           |

:::note warn
対応していない型を指定した場合、例外（`NotSupportedException`）をスローします。
:::

## 読み出すデバイスの点数の指定方法
McpXでは、指定されたデータ型に応じて、内部で自動的に必要な読み出し点数（ワード数）を計算する仕組みになっています。

そのため、各メソッドの`length`パラメータには、C#側で扱う配列の要素数（＝欲しい値の個数）を指定してください。

例えば、単精度の浮動小数点（float）を2つ読み込む場合、
PLC上では1つあたり2ワード（D0〜D1、D2〜D3）が必要となりますが、`length`には`2`を指定します。
このように、McpXは型に応じて必要なワード数を内部で自動計算してくれるため、使う側は型と要素数を意識するだけで簡単に扱えます。

### 型別の使用ワード数
| 型        | 使用ワード数 |
|-----------|---------------|
| `bool`    | 1ビット（ビットデバイス） |
| `short`   | 1ワード        |
| `int`     | 2ワード        |
| `float`   | 2ワード        |
| `double`  | 4ワード        |

:::note warn
MCプロトコルでは、一度の一括読み出しコマンドで指定できる点数に上限があります。

しかし、McpXではこの上限を超えた場合でも、**自動的にパケットを分割して送信**する仕組みが備わっているため、MCプロトコルの制限を意識せずに使用できます。

ただし、読み出し点数が増えるほど、パケット数や通信量も増加するため、**大量のデバイスを読み込む場合はパフォーマンスへの影響に注意が必要です。**

📎 [パフォーマンス測定結果はこちら](https://qiita.com/YudaiKitamura/items/f8010ae96134701d46c1#%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9%E6%B8%AC%E5%AE%9A%E7%B5%90%E6%9E%9C)
:::

## 単一読み出し
指定デバイス１点を読み出し、指定した型で返します。
### 同期メソッドの例
D0 から整数型（16ビット符号付き）で読み込む場合
```csharp
short shortValue = mcpx.Read<short>(Prefix.D, "0");
```
### 非同期メソッドの例
D10 から整数型（32ビット符号付き）で読み込む場合
```csharp
int intValue = await mcpx.ReadAsync<int>(Prefix.D, "10");
```

## 一括読み出し
連続したデバイスから、指定数のデータを一括で読み出し、指定した型の配列で返します。
### 同期メソッドの例
D0 から整数型（16ビット符号付き）で100点を読み込む場合
```csharp
short[] shortValues = mcpx.BatchRead<short>(Prefix.D, "0", 100);
```
### 非同期メソッドの例
D10 から整数型（32ビット符号付き）で200点を読み込む場合
```csharp
int[] intValues = await mcpx.BatchReadAsync<int>(Prefix.D, "10", 200);
```
## ランダム読み出し
ワード単位・ダブルワード単位で指定されたデバイスのデータを読み出し、それぞれの型に応じた配列を返します。
### 同期メソッドの例
D100 と D110 を 16ビット符号付き整数（short） として読み出し、D120 と D130 を 32ビット符号付き整数（int） として読み出す場合
``` csharp
var drrArr = await mcpx.RandomReadAsync<short, int>(
    wordAddresses: [ 
        (Prefix.D, "100"),
        (Prefix.D, "110")
    ],
    doubleWordAddresses: [
        (Prefix.D, "120"),
        (Prefix.D, "130") 
    ]
);

Console.WriteLine($"D100: { drrArr.wordValues[0] }");
Console.WriteLine($"D110: { drrArr.wordValues[1] }");

Console.WriteLine($"D120: { drrArr.doubleValues[0] }");
Console.WriteLine($"D130: { drrArr.doubleValues[1] }");
```
### 非同期メソッドの例
D200 と D210 を 16ビット符号付き整数（short） として読み出し、D220 と D230 を 32ビット符号付き整数（int） として読み出す場合
``` csharp
var drrArr = mcpx.RandomRead<short, int>(
    wordAddresses: [ 
        (Prefix.D, "200"),
        (Prefix.D, "210")
    ],
    doubleWordAddresses: [
        (Prefix.D, "220"),
        (Prefix.D, "230") 
    ]
);

Console.WriteLine($"D200: { drrArr.wordValues[0] }");
Console.WriteLine($"D210: { drrArr.wordValues[1] }");

Console.WriteLine($"D220: { drrArr.doubleValues[0] }");
Console.WriteLine($"D230: { drrArr.doubleValues[1] }");
```

## 同期メソッドと非同期メソッドの選び方
McpXでは、すべての読み出し系コマンドに同期版と非同期版の両方が用意されています。
選択のポイントは、アプリケーションの構造と処理負荷です。

### 同期メソッドを使うケース
- コンソールアプリやバッチ処理など、一連の処理が順次完了すればよい場合
- 通信待ちによるUIブロックが問題にならない場合
- ロジックをシンプルに保ちたい場合（学習・デバッグ中など）

### 非同期メソッドを使うケース
- WPFやWinForms、ASP.NETなど、UIやWebサーバーで非同期処理が求められる環境
- 通信中にアプリの応答性を保ちたい場合
- 複数のPLC通信や長時間通信を並列処理したい場合

## まとめ
今回は、三菱PLCと通信するための McpXライブラリの読み出し系コマンドについて紹介しました。
- Prefix と Address を組み合わせたデバイス指定方法
- デバイス点数の指定方法
- `Read`, `BatchRead`, `RandomRead` 各メソッドの使用方法
- 同期／非同期メソッドの選定基準と使い分け方

また、GitHubにサンプルプロジェクトを載せてますので、ご確認ください。

https://github.com/YudaiKitamura/McpX/tree/main/Example


次回は、McpXが対応している**デバイス書き込み系コマンド**とその使い方を具体的なサンプルコードとともに紹介していきます！

## 参考リンク
- [McpX ドキュメント](https://yudaikitamura.github.io/McpX/)
- [McpX GitHubリポジトリ](https://github.com/YudaiKitamura/McpX)
- [McpX NuGet](https://www.nuget.org/packages/McpX/)

---
💡 気になった方はぜひ[GitHubのリポジトリ](https://github.com/YudaiKitamura/McpX)をチェックしてみてください。  
よければ ⭐スター もよろしくお願いします！