Re: note

技術的な知見やポエムなど役に立たない情報を書き連ねる場所

W5500を使ってArduinoでUDP通信する

f:id:hik0leaf:20190615125456j:plain

WIZnetのW5500を使ってArduinoUDP通信を行ってみます。

1. 準備

W5500を入手します。日本国内で格安で扱っているところはあまりなさそうだったのでAliexpressで注文しました。1個$3.82。送料無料でも送ってくれますが、トラッキングができないのと配送期間も長くなる傾向があるので、商品価格よりも高くなりますが、$3~$5支払ってトラッキングと配送期間を短縮できるようにしました。 (今回は注文から12日程度で到着)

1 個 USR ES1 W5500 チップ新 spi 蘭イーサネット変換 TCPIP Mod - Aliexpress.com | Alibaba グループ上の 電子部品&用品 からの 集積回路 の中

f:id:hik0leaf:20190615222738j:plain
とってもコンパクト

ArduinoはUNO R3を使います。

2. 接続

ArduinoとはSPIで通信を行うため、W5500を以下のように接続します。IOレベルは3.3Vですが5VトレラントでもあるのでArduinoへ直結することができます。その他の注意点としてはW5500の消費電流が大きいため3.3Vの供給をArduinoから行わずに外部から加えるようにしています。

W5500 | WIZnet Co., Ltd.

f:id:hik0leaf:20190616105511j:plain
接続図

Arduino W5500
D10 (SS) CS
D11 (MOSI) MOSI
D12 (MISO) MISO
D13 (SCK) SCLK
GND GND

データシートを見ると100M Trasmittingのときに132mA消費すると書いてあるので、外部電源は200mA以上供給できるものを用意すると良いでしょう。

products:w5500:datasheet [Document Wiki]

f:id:hik0leaf:20190615134207j:plain
W5500の消費電流 (データシートから抜粋)

f:id:hik0leaf:20190615134554j:plain
W5500モジュールのピン配置

(2019.06.15 追記)
ブレッドボードで接続する場合はワイヤーをなるべく短くしてください。ワイヤーが長いと通信が不安定になります。

3. コード

以下のコードをArduinoに書き込みます。Arduinoの標準サンプルコードの[ファイル] > [スケッチ例] > [Ethernet] > [UDPSendReceiveString]を参考にしています。

ArduinoEthernetライブラリの詳細はこちらから確認することができます。

Arduino - Ethernet

#include <Ethernet.h>
#include <EthernetUdp.h>

byte mac[] = { 0x70, 0x69, 0x69, 0x2D, 0x30, 0x31 };
byte ip[] = { 10, 0, 1, 11 };

unsigned int localPort = 1337;

char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
char ReplyBuffer[] = "acknowledged";

EthernetUDP Udp;

void setup() {
  Serial.begin(9600);
  Serial.println("starting...");
  
  Ethernet.init(10);// CS pin 10
  Ethernet.begin(mac, ip);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  Serial.print("IP: ");
  Serial.println(Ethernet.localIP());
  
  Udp.begin(localPort);
}

void loop() {
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    IPAddress remote = Udp.remoteIP();
    for (int i=0; i < 4; i++) {
      Serial.print(remote[i], DEC);
      if (i < 3) {
        Serial.print(".");
      }
    }
    Serial.print(", port ");
    Serial.println(Udp.remotePort());

    // read the packet into packetBufffer
    Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Serial.print("Contents: ");
    Serial.println(packetBuffer);

    // send a reply to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(ReplyBuffer);
    Udp.endPacket();
  }
  delay(10);
}

MACアドレスIPアドレスは環境に合わせて変更してください。

4. 実行

シリアルモニタでログを確認します。通信相手はNode.jsを使うと簡単に用意することができます。正常に動作していれば、ログに受信したメッセージと相手先に"acknowledged"を送信していることが確認できます。

5. 参考リンク

http://nopnop2002.webcrow.jp/Arduino_Networking/Network-2.html

mbedでUDP通信する

f:id:hik0leaf:20190614172133j:plain

前回はmbedでTCP通信を行いました。今回はUDP通信を試してみたいと思います。

1. 準備

以下の部品を用意します。

mbed LPC1768

f:id:hik0leaf:20190614172310j:plain

LANコネクタDIP化キット

秋月電子で購入することができます。

LANコネクタDIP化キット: パーツ一般 秋月電子通商-電子部品・ネット通販

f:id:hik0leaf:20190614172402j:plain
LANコネクタDIP化キット

2. 接続

mbedとLANコネクタDIP化キットを以下のように接続します。

f:id:hik0leaf:20190614172648j:plain
接続図

MBED LANコネクタDIP化キット
RD- pin 6
RD+ pin 3
TD- pin 2
TD+ pin 1

3. コード(mbed側)

ここからTCP記事と異なります。 mbedの公式handbookのページからSocket通信するためのコードとライブラリをインポートします。

UDP Echo Server
Socket - Handbook | Mbed

[Import program]ボタンを押すとmbedコンパイラが開いてコードとライブラリをインポートすることができます。 f:id:hik0leaf:20190614230723j:plain

インポート画面でImport AsがProgramにチェックがはいっていることと、Update all libraries to the latest versionにチェックが入っていないこと( NOT CHECKED )を確認してインポートします。Import Nameは任意の値で構いません。

f:id:hik0leaf:20190614184357j:plain
Import Program

【重要】ライブラリのアップデートは正常動作しなくなるためおすすめしません。

インポートしたらそのままコードを使用しても構いませんが、もう少し動作がわかりやすいように以下のコードをmbedに書き込みます。UDP通信はTCP通信と異なり、サーバ/クライアントの概念がないため今回mbed側をA、相手側のPCをBとします。

/*
 A: 自分(mbed)
 B: 相手(PC)
*/
#include "mbed.h"
#include "EthernetInterface.h" 

const char* A_ADDRESS = "10.0.1.25";// 自分
const int A_PORT = 33333;

const char* B_ADDRESS = "10.0.1.7";// 相手
const int B_PORT = 33333;

int main() {
    printf("starting...\n");
    
    char u_packet[2] = { 0x4F, 0x4B };// "OK"

    EthernetInterface eth; 
    eth.init(A_ADDRESS, "255.255.255.0", "");
    printf("IP : %s\n", eth.getIPAddress());
    
    if (eth.connect() < 0) {
        printf("Failed to connect\n\r");
        return -1;
    }
    
    UDPSocket socket; 
    
    if (socket.bind(A_PORT) < 0) {
        printf("Failed to bind UDP Socket to PORT : %d\n\r", A_PORT);
        return -1;
    } else {
        printf("Bind UDP Socket to PORT : %d\n\r", A_PORT);
    }
    
    socket.set_blocking(false);
    

    Endpoint EndpointB;
    EndpointB.set_address(B_ADDRESS, B_PORT);

    socket.sendTo(EndpointB, (char*)u_packet, sizeof(u_packet));

    while(1) {
        char u_buff[12];

        int n_receive = socket.receiveFrom(EndpointB, (char*)u_buff, sizeof(u_buff));
        if (n_receive > 0) {
            u_buff[n_receive] = '\0';// 終端文字追加(printfしないときは不要)
            printf("Received [%d]: %s\n", n_receive, u_buff);
        }
    } 
}

内容は起動したら"OK"[0x4F, 0x4B]メッセージを相手側(B)に送り、その後は相手側からのメッセージ受け続けます。

4. コード(PC側)

通信相手としてPC側のコードを用意します。UDP通信ができれば何でも構いせませんが、今回はnode.jsを使いました。

const dgram = require('dgram');

const PORT_A = 33333;
const HOST_A = '10.0.1.25';// 相手

const PORT_B = 33333;
const HOST_B = '10.0.1.7';// 自分

const socket = dgram.createSocket('udp4');

var count = 0;

setInterval(() => {
    count++;
    const data = Buffer.from(String(count));
    socket.send(data, 0, data.length, PORT_A, HOST_A, (err, bytes) => {
        if (err) throw err;
    });
}, 500);


socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);
});

socket.bind(PORT_B, HOST_B);

内容は500msec毎に数値をカウントアップしてmbed側に送信しています。

以下のコマンドで上記のサーバ側のコードを実行することができます。

$ npm init
$ npm install dgram
$ node server.js

5. 実行

LANコネクタにLANケーブルを接続し、mbedもシリアル通信でログを確認します。正常に動作していればシリアル通信で以下のようにログを確認することができます。

f:id:hik0leaf:20190614185737p:plain

6. 参考リンク

mbedでTCP通信する

f:id:hik0leaf:20190614172133j:plain mbed LPC1768ではチップにEthernet物理層(PHY)を内蔵しているため、LANコネクタ(RJ45)を追加するだけでEthernetを使った通信を行うことができます。ここではmbedを使って実際にTCP通信を行ってみます。

1. 準備

以下の部品を用意します。

mbed LPC1768

f:id:hik0leaf:20190614172310j:plain

LANコネクタDIP化キット

秋月電子で購入することができます。

LANコネクタDIP化キット: パーツ一般 秋月電子通商-電子部品・ネット通販

f:id:hik0leaf:20190614172402j:plain
LANコネクタDIP化キット

2. 接続

mbedとLANコネクタDIP化キットを以下のように接続します。

f:id:hik0leaf:20190614172648j:plain
接続図

MBED LANコネクタDIP化キット
RD- pin 6
RD+ pin 3
TD- pin 2
TD+ pin 1

3. コード(mbed側)

mbedの公式handbookのページからSocket通信するためのコードとライブラリをインポートします。

Socket - Handbook | Mbed

[Import program]ボタンを押すとmbedコンパイラが開いてコードとライブラリをインポートすることができます。 f:id:hik0leaf:20190614230002j:plain

インポート画面でImport AsがProgramにチェックがはいっていることと、Update all libraries to the latest versionにチェックが入っていないこと( NOT CHECKED )を確認してインポートします。Import Nameは任意の値で構いません。

f:id:hik0leaf:20190614174433j:plain
Import Program

【重要】ライブラリのアップデートは正常動作しなくなるためおすすめしません。

インポートしたらそのままコードを使用しても構いませんが、もう少し動作がわかりやすいように以下のコードをmbedに書き込みます。今回mbedはクライアントで動作します。

#include "mbed.h"
#include "EthernetInterface.h"

const char* ECHO_SERVER_ADDRESS = "10.0.1.7";
const int ECHO_SERVER_PORT = 3000;
 
int main() {
    printf("starting...\n");
    
    EthernetInterface eth;
    eth.init(); // Use DHCP
    eth.connect();
    printf("Client IP Address is %s\n", eth.getIPAddress());
    
    // Connect to Server
    TCPSocketConnection socket;
    while (socket.connect(ECHO_SERVER_ADDRESS, ECHO_SERVER_PORT) < 0) {
        printf("Unable to connect to (%s) on port (%d)\n", ECHO_SERVER_ADDRESS, ECHO_SERVER_PORT);
        wait(1);
    }
    printf("Connected to Server at %s\n",ECHO_SERVER_ADDRESS);
    
    // Send message to server
    char hello[] = "Hello World";
    printf("Sending  message to Server : '%s' \n",hello);
    socket.send_all(hello, sizeof(hello) - 1);
    
    // Receive message from server
    while (1) {
        char buf[256];
        int n = socket.receive(buf, 256);
        if (n > 0) {
            buf[n] = '\0';
            printf("Received message from server: '%s'\n", buf);
        }
    }

    // Clean up
    socket.close();
    eth.disconnect();

内容は起動したら"Hello World"メッセージをサーバに送り、その後はサーバからのメッセージ受け続けます。

4. コード(サーバ側:PC)

PC側にサーバを用意します。TCPサーバが立てられれば何でも構いませんが、今回はnode.jsを使いました。

server.js

const net = require('net');

var count = 0;

const server = net.createServer(socket => {
    socket.on('data', data => {
        console.log(data + ' from ' + socket.remoteAddress + ':' + socket.remotePort);

        setInterval(() => {
            count++;
            console.log(count)
            socket.write(String(count));
        }, 500);

    });

    socket.on('close', () => {
        console.log('client closed connection');
    });
}).listen(3000);

console.log('listening on port 3000');

内容は500msec毎に数値をカウントアップしてmbed(クライアント)に送信しています。

以下のコマンドで上記のサーバ側のコードを実行することができます。

$ npm init
$ npm install net
$ node server.js

5. 実行

LANコネクタにLANケーブルを接続し、mbedもシリアル通信でログを確認します。mbedはDHCPでネットワーク設定を取得するため、ルーターDHCPサーバが機能していることを確認してください。正常に動作していればシリアル通信で以下のようにログを確認することができます。

f:id:hik0leaf:20190614180709p:plain

6. 参考リンク

Node.jsでTCP通信する

f:id:hik0leaf:20190609130426p:plain

以下のサイトを参考にしてNode.jsでTCP通信を行ってみました。

blog.mitsuruog.info

nodeによるTCP通信は net モジュールを使用します。APIリファレンスは以下を参照ください。

Net | Node.js v12.4.0 Documentation

1. 準備

以下のコマンドを入力します。

$ npm install net

2. コード

server用とclient用のコードを以下のようにそれぞれ準備します。

server.js

const net = require('net');

const server = net.createServer(socket => {
    socket.on('data', data => {
        console.log(data + ' from ' + socket.remoteAddress + ':' + socket.remotePort);
        socket.write('server -> Repeating: ' + data);
    });

    socket.on('close', () => {
        console.log('client closed connection');
    });
}).listen(3000);

console.log('listening on port 3000');


client.js

const net = require('net');

const client = net.connect('3000', 'localhost', () => {
    console.log('connected to server');
    client.write('Hello World!');
});

client.on('data', data => {
    console.log('client-> ' + data);
    client.destroy();
});

client.on('close', () => {
    console.log('client-> connection is closed');
});

3. 実行

serverとclient別々のターミナルを立ち上げ、以下のコマンドを実行してメッセージが表示されれば成功です。

$ node server.js
$ node ceient.js

参考にしたサイトではクライアントからの標準入力をサーバでエコーバックし続けていますが、今回のこのコードはclientから「Hello World!」を送って返ってきたら destroy して接続を終了しています。

Node.jsでUDP通信する

f:id:hik0leaf:20190608234421p:plain

以下のサイトを参考にして実際にNode.jsでUDP通信を行ってみました。

dgramを使うと簡単にUDP通信できるようです。

nodejs.org

1. 準備

以下のコマンドを入力します。

$ npm init
$ npm install dgram

2. コード

UDP通信では明確にサーバとクライアントで区別できないため、今回は便宜上、片方をterminal_a、もう一方をterminal_bとします。以下のサンプルコードは同じPC上で動かすため、IPアドレスは同じで異なるPORTで通信を行います。

terminal-a.js

const dgram = require('dgram');

const PORT_A = 3002;
const HOST_A = '127.0.0.1';

const PORT_B = 3003;
const HOST_B = '127.0.0.1';

const socket = dgram.createSocket('udp4');

socket.on('listening', () => {
    const address = socket.address();
    console.log('UDP socket listening on ' + address.address + ":" + address.port);
});

socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);

    socket.send(message, 0, message.length, PORT_B, HOST_B, (err, bytes) => {
        if (err) throw err;
    });
});

socket.bind(PORT_A, HOST_A);


terminal-b.js

const dgram = require('dgram');

const PORT_A = 3002;
const HOST_A ='127.0.0.1';

const PORT_B = 3003;
const HOST_B ='127.0.0.1';

const socket = dgram.createSocket('udp4');

var count = 0;

setInterval(() => {
    count++;
    const data = Buffer.from(String(count));
    socket.send(data, 0, data.length, PORT_A, HOST_A, (err, bytes) => {
        if (err) throw err;
    });
}, 500);


socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);
});

socket.bind(PORT_B, HOST_B);

3. 実行

terminal-aとterminal-bを別々のコンソールを立ち上げて以下のコマンドをそれぞれ入力します。正常に動作していればterminal-bからintervalでカウントアップ文字列を送り、terminal-aでエコーバックしていることが確認できます。

$ node terminal-a.js
$ node terminal-b.js

UniRxを使ってみる

f:id:hik0leaf:20190608155416p:plain


ここではUniRxの基本的なオペレータとそのサンプルコードを見ていきたいと思います。 概念を理解するのも大切ですが実際に使って動かしてみるのも理解への近道です。

全体のコードは以下のリポジトリにありますので、別記事の UnityでLINQを使って楽をする も合わせてご覧ください。

github.com

また、本記事は全体的に以下のサイトの記事(RxJava)を参考にさせて頂きました。

qiita.com

What's UniRx?

f:id:hik0leaf:20190608164039j:plain
Everything is a stream

http://slides.com/robwormald/everything-is-a-stream#/

特徴

  • ReactiveXのUnity版
  • マーブルダイアグラム
  • Observable / Subscribe の世界
  • 豊富なオペレータ
  • 非同期並列処理
  • Asset StoreからUniRxをImportして using UniRx で使えるようになるよ

Timer

指定時間後に処理を行います。

f:id:hik0leaf:20190608164747p:plain

using UniRx;
using System;

Debug.Log("Timer START");
Observable.Timer(TimeSpan.FromSeconds(3))
    .Subscribe(_ => Debug.Log("OK")); // 3秒後に"OK"

Interval

指定時間間隔で繰り返します。

f:id:hik0leaf:20190608165058p:plain

using UniRx;
using System;

Observable
    .Interval(TimeSpan.FromSeconds(1))
    .Subscribe(x => Debug.Log(x));
// 0, 1, 2, 3, 4, 5......

Where

条件式がTrueとなるものを流します。

f:id:hik0leaf:20190608165140p:plain

using UniRx;

var list = new List<int> { 1, 2, 3, 4, 5 };

list.ToObservable()
    .Where(x => x > 2)
    .Subscribe(x => Debug.Log(x));
// 3, 4, 5

Distinct

重複を削除します。

f:id:hik0leaf:20190608165210p:plain

using UniRx;

var list = new List<int> { 1, 2, 3, 3, 4, 5, 5 };

list.ToObservable()
    .Distinct()
    .Subscribe(x => Debug.Log(x));
// 1, 2, 3, 4, 5

Take

指定回数だけ流します。

f:id:hik0leaf:20190608165231p:plain

using UniRx;

var list = new List<int> { 1, 2, 3, 4, 5 };

list.ToObservable()
    .Take(3)
    .Subscribe(x => Debug.Log(x));
// 1, 2, 3

Skip

指定回数だけスキップします。

f:id:hik0leaf:20190608165454p:plain

using UniRx;

var list = new List<int> { 1, 2, 3, 4, 5 };

list.ToObservable()
    .Skip(3)
    .Subscribe(x => Debug.Log(x));
// 4, 5

Scan

畳み込みを行います。

f:id:hik0leaf:20190608165521p:plain

using UniRx;

var list = new List<int> { 1, 2, 3, 4, 5 };

list.ToObservable()
    .Scan((a, b) => a + b)
    .Subscribe(x => Debug.Log(x));
// 1, 3, 6, 10, 15

Range

範囲を指定して流します。

f:id:hik0leaf:20190608165540p:plain

using UniRx;

Observable
    .Range(1, 5)
    .Subscribe(x => Debug.Log(x));
// 1, 2, 3, 4, 5

Create

Observableを作成します。

using UniRx;

var observable = Observable.Create<int>(observer => {
    observer.OnNext(1);
    observer.OnNext(2);
    observer.OnNext(3);
    observer.OnNext(4);
    observer.OnNext(5);
    observer.OnCompleted();
    return Disposable.Empty;
});

observable.Subscribe(x => Debug.Log(x));
// 1, 2, 3, 4, 5

ボタンクリックイベント

OnClickイベントをObservable化します。

using UniRx;
using UnityEngine.UI;

[SerializeField] Button button = default;

button
    .OnClickAsObservable()
    .Subscribe(_ => Debug.Log("on click"));

SubscribeOn

Observerを処理するスレッドを指定します。

using UniRx;
using System.Threading;

Debug.Log("START");
var observable = Observable.Create<int>(observer => {
    Thread.Sleep(3000);
    observer.OnNext(1);
    observer.OnCompleted();
    return Disposable.Empty;
}).SubscribeOn(Scheduler.ThreadPool);

observable.Subscribe(x => Debug.Log(x));
Debug.Log("END");
// START
// END
// 1 (3秒後)

Merge

Observableを合成します。順不同 f:id:hik0leaf:20190608165631p:plain

using UniRx;
using System.Threading;

var list1 = Observable.Create<string>(observer => {
    Thread.Sleep(1);
    observer.OnNext("A1");
    Thread.Sleep(8);
    observer.OnNext("A2");
    Thread.Sleep(2);
    observer.OnNext("A3");
    observer.OnCompleted();
    return Disposable.Empty;
}).SubscribeOn(Scheduler.ThreadPool);

var list2 = Observable.Create<string>(observer => {
    Thread.Sleep(3);
    observer.OnNext("B1");
    Thread.Sleep(1);
    observer.OnNext("B2");
    Thread.Sleep(12);
    observer.OnNext("B3");
    observer.OnCompleted();
    return Disposable.Empty;
}).SubscribeOn(Scheduler.ThreadPool);

Observable
    .Merge(list1, list2)
    .Subscribe(x => Debug.Log(x));
// A1, B1, B2, A2, A3, B3

Concat

Observableを合成します。流れる順序は引数受け取り順です。

f:id:hik0leaf:20190608165709p:plain

using UniRx;
using System.Threading;

var list1 = Observable.Create<string>(observer => {
    Thread.Sleep(1);
    observer.OnNext("A1");
    Thread.Sleep(8);
    observer.OnNext("A2");
    Thread.Sleep(2);
    observer.OnNext("A3");
    observer.OnCompleted();
    return Disposable.Empty;
}).SubscribeOn(Scheduler.ThreadPool);

var list2 = Observable.Create<string>(observer => {
    Thread.Sleep(3);
    observer.OnNext("B1");
    Thread.Sleep(1);
    observer.OnNext("B2");
    Thread.Sleep(12);
    observer.OnNext("B3");
    observer.OnCompleted();
    return Disposable.Empty;
}).SubscribeOn(Scheduler.ThreadPool);

Observable
    .Concat(list1, list2)
    .Subscribe(x => Debug.Log(x));
// A1, A2, A3, B1, B2, B3

Zip

MergeやConcatとの違いは異なる型のObservableを扱えます。

f:id:hik0leaf:20190608165743p:plain

using UniRx;

var list1 = new List<string> { "A", "B", "C", "D", "E" }.ToObservable();
var list2 = new List<int> { 1, 2, 3, 4, 5 }.ToObservable();

Observable
    .Zip(list1, list2, (a, b) => a + b)
    .Subscribe(x => Debug.Log(x));
// A1, B2, C3, D4, E5

Subject

外部から任意のタイミングでOnNext、OnComplete、OnErrorを送ることが可能です。

f:id:hik0leaf:20190608165804p:plain

using UniRx;

var subject = new Subject<int>();
subject.Subscribe(x => Debug.Log(x));

subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
// 1, 2, 3

BehaviorSubject

Subjectとの違いは初期値の有無です。

f:id:hik0leaf:20190608165825p:plain

using UniRx;

var subject = new BehaviorSubject<int>(5);
subject.Subscribe(x => Debug.Log(x));

subject.OnNext(1);
subject.OnNext(2);
subject.OnNext(3);
// 5, 1, 2, 3

終わりに

オペレータはこの他にもたくさんありますので、使い方などの詳しい情報はUniRxのリポジトリwikiをみると良いでしょう。それでは良いUniRxライフを。

github.com

UnityでLINQを使って楽をする

f:id:hik0leaf:20190608153114p:plain

LINQを使うとデータ処理を簡単に行うことができます。 全体のコードは以下のリポジトリにありますので、別記事の UniRxを使ってみる も合わせてご覧ください。

github.com

What's LINQ?

C# の統合言語クエリ (LINQ) | Microsoft Docs

  • Language Integrated Query (LINQ)
  • SQLクエリっぽい操作
  • ラムダ式
  • メソッドチェーン
  • using System.Linq で使えるようになるよ

LINQの詳しい解説は他のサイトに譲るとして、ここではサンプルコードを記載します。実際に実行して挙動を確認してみると理解が進むはずです。

LINQ Operators

Average

平均値を抽出

using System.Linq;

double average = new List<int> { 0, 1, 2, 3 }.Average();
Debug.Log(average); // 1.5

Min

最小値を抽出

using System.Linq;

int min = new List<int> { 21, 9, 15, 3, 8, 11 }.Min();
Debug.Log(min); // 3

Max

最大値を抽出

using System.Linq;

int max = new List<int> { 5, 9, 15, 3, 21, 8, 11 }.Max();
Debug.Log(max); // 21

Sum

合計値を算出

using System.Linq;

int sum = new List<int> { 1, 2, 3, 4, 5 }.Sum();
Debug.Log(int); // 15

Where

条件式がTrueとなる要素で再構成します。

using System.Linq;

var list = new List<int> { 1, 2, 3, 4, 5 }.Where(x => x > 2);
Debug.Log(string.Join(", ", list)); // { 3, 4, 5 }

Select

式を評価した値で再構成します。

using System.Linq;

var list = new List<int> { 1, 2, 3, 4, 5 }.Select(x => x * x);
Debug.Log(string.Join(", ", list)); // { 1, 4, 9, 16, 25}

メソッドチェーン

メソッド(オペレータ)を連結することで連続して処理することができます。

using System.Linq;

var list1 = new List<int> { 1, 2, 3, 4, 5 };
var list2 = list1.Where(x => x > 2);
var list3 = list1.Where(x => x > 2).Select(x => x * x);

Debug.Log(string.Join(", ", list1));// { 1, 2, 3, 4, 5 }
Debug.Log(string.Join(", ", list2));// { 3, 4, 5 }
Debug.Log(string.Join(", ", list3));// { 9, 16, 25 }

(foreach)

foreachはLINQではありませんが、要素を取り出す操作としてLINQと組み合わせて使うこともできます。

var list = new List<int> { 1, 2, 3, 4, 5 };
list.ForEach(x => Debug.Log(x * x)); // 1, 4, 9, 16, 25

// list.ForEach(x => {
//     Debug.Log("-----");
//     Debug.Log(x * x);
// });