【UiPath Studio】「コードソースファイル」を使ってC#のコードで共通部品を作ってみよう。

# はじめに

:::note
**コードソースファイル**とは、UiPath Studio v23.10で追加された機能の１つです。
:::

タイトルの内容を検証してみます。公式ドキュメントの説明は以下です。

https://docs.uipath.com/ja/studio/standalone/2023.10/user-guide/invoking-coded-source-file

正直、自分にはドキュメントをサラッと読んだけでは意味が分かりませんでした。
じっくり…ゆっくり…読んだら理解できた気がしたので、実際に作って動かしてみました。



コード化されたワークフロー、コード化されたテストケースの概要は以下の記事に記載しています。ご興味があればご参照ください。

https://qiita.com/moripo/items/aabc93a6098b39cc9d00



コード化されたワークフロー、コード化されたテストケースとの違いを端的に言えば、
実行する側ではなく、呼び出される側（≒ 部品のようなもの）であることです。

:::note
紹介する機能および当記事の内容について、ある程度のプログラム言語の知識をお持ちの方が対象になっています。
:::

# 「コードソースファイル」を理解するためだけの処理を作ってみます。

機能の理解を目的とし、とても簡単なものを作ってみます。

## まずは「コードソースファイル」を作成する手順です。

① 右クリック > コードソースファイル をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/2d10013f-ee16-2864-1408-9508c69e21e7.png)

② デフォルトの「ソースファイル」の名前のまま、作成をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/4a3d6334-e276-3262-549b-0eec40969567.png)
　プロジェクト内に「ソースファイル.cs」が作成されます。



## つづいて、"Hello World!!" を出力してみます。

① 「ソースファイル.cs」は以下のように実装してみます。

:::note
当記事では、プロジェクト名は **「コードを使ってみる」** という名前にしました。
コード内の ```namespace``` の値にプロジェクト名が設定されます。呼び出し時にこの値を使うので意識しておきましょう。
:::

```ソースファイル.cs
using System;
using System.Collections.Generic;
using System.Data;
using UiPath.Core;
using UiPath.Core.Activities.Storage;
using UiPath.Orchestrator.Client.Models;
using UiPath.Testing;
using UiPath.Testing.Activities.TestData;
using UiPath.Testing.Activities.TestDataQueues.Enums;
using UiPath.Testing.Enums;
using UiPath.UIAutomationNext.API.Contracts;
using UiPath.UIAutomationNext.API.Models;
using UiPath.UIAutomationNext.Enums;
//using コードを使ってみる.ObjectRepository; ← 実験的にコメント化して動かしました。

namespace コードを使ってみる
{
    public class ソースファイル
    {
        public static string helloWorld(string s) {
            Console.WriteLine( "受け取った値です。--->" + s);
            return "[Hello World!!]を返します！";
        }   

    }
}
```

:::note
```public static```とついている点がポイントです。
```static```の指定により```class.関数```という形で呼び出すことができます。
（公式ドキュメントで説明されている方法です）

今回は stringを返す **helloWorld** 関数として用意しました。
:::

:::note warn
```static```でない```class```での実行も可能でした、後述の章をご参照ください。
:::

② **Main.xaml** は下図のように実装しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/3988186d-4389-bf73-88d9-2279fd25e241.png)

メッセージの詳細な値は下図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/c125f47b-8631-6e81-90b6-3aa351574cd7.png)

:::note
呼び出し方の解釈は以下のような形です。

```
namespace名.class名.関数名(引数)
```

namespace名はリネームしていないければ、プロジェクト名になっているはずです。
:::

### 実行したところ、ログ出力は以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/c33634e4-21b9-eab2-c5ba-6fea3a6a6d8c.png)

**Console.WriteLine** の「**こんにちは、世界！**」も、
**メッセージをログ** アクティビティの「**Hello World!!**」も確認できました


:::note warn
少し手こずった点もあり、**些細な学び** を共有します。

上記のnamespace名やclass名、関数名も正しく記述できていれば、しっかりサジェスト機能が効きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/2c9c842a-2926-0dae-cf6c-0cb689e3c801.png)

サジェストされない場合は、
- 別のcsファイルにエラーがあってコンパイルされていないとか、<BR> （個人的に ↑ でハマりました…）
- ```namespace```指定に誤りがあるとか、
- 関数が```public```になっていないとか…、

、、、様々ありそうです。まずは小さく始めてみると混乱は少ないです。
:::

# ちなみに公式ドキュメントの説明は、こんな処理です。

https://docs.uipath.com/ja/studio/standalone/2023.10/user-guide/invoking-coded-source-file

上記の公式ドキュメントにも一通り説明が記載されていますが、ブロックごとに分かれていて全体像の理解に時間が掛かりました。
改めてこの内容を確認していきます。

:::note
**引数として渡した２つの値の範囲でランダムなTimeSpanを生成する**、という処理のようです。

この処理自体の意味ということよりも、**この手法の応用で共通部品が作成できる**…という点をご理解いただけると嬉しいです。
:::

### 実装は以下。

:::note
こちらでは```namespace```はsampleという名前にしています。
デフォルトがプロジェクト名であるだけで、任意の名前に変更することもできます。
:::

```TimeSpanHelper.cs
//   ～
//using ブロックは省略します
//   ～

namespace sample
{
    public class TimeSpanHelper
    {
        // ランダムな値を生成するために使います
        private static Random _random = new Random();
        // スレッドを保護しながら、複数のスレッドが同時に処理できるようにします
        private static object _lockObj = new object();
        
        // パブリックな静的メソッドを実装します。
        public static TimeSpan GetRandomTimeSpanBetween(int lowerBoundMs, int upperBoundMs)
        {
            // スレッドを保護しながら、複数のスレッドが同時に処理されるようにします。
            lock (_lockObj)
          {
              // ランダムな整数値を生成し、
              var ms = _random.Next(lowerBoundMs, upperBoundMs);
              // 生成されたランダム値を TimeSpan オブジェクトに変換します
              return TimeSpan.FromMilliseconds(ms);
          }
        }
    }
}
```

:::note
簡単な補足をすると…
```private static Random _random```という変数定義にも```static```がついています。staticの関数から利用するために必要な指定です。

```lock (_lockObj)```は同時に処理された際の不整合を予防するための記述です。
:::


### 呼び出しは下図。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/bffc6dbb-a3a7-2688-3477-ac7bbc0cc6f0.png)
（当実装は、公式ドキュメントの記載に則ってみました。TimeSpanをログ出力すること自体に意味はなさそうですね…）

**値の詳細** は以下です。

```
sample.TimeSpanHelper.GetRandomTimeSpanBetween(2000, 4000)
```

:::note
TimeSpanが返るため当然、下図のように代入アクティビティを使って後続処理でこの変数を活用していくことも可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/c9354513-cee7-f390-025e-9fef9e8152c6.png)
:::


## 以上、C#で共通部品ができました！

サンプル自体はあまり意味の無さそうな処理ですが、同時処理を考慮したスレッドセーフな書き方になっているので、応用の元としては良さそうです。

:::note warn
**使ってみて… 補足Tipsです。**
ワークフローでの活用を想定すると、C#コードで作成する関数は**戻り値を用意したほうが使いやすい**です。
**戻り値があればアクティビティから呼び出しやすい** ためです。以下、実例です。

戻り値ありと戻り値なしの関数を用意しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/fc67a32e-dfe7-f9d5-2b57-7fc0f1a8ce7b.png)


戻り値があれば「メッセージをログ」アクティビティや「代入」アクティビティで手軽に呼び出せます。
（↓ 図の上段）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/d4a6a5b7-5b3c-0a0f-d4f6-7359df2e44e7.png)
（↑ 図の下段）
戻り値がない場合は、呼び出し方法を一考する必要があります。
:::

# まとめ

正直、公式ドキュメントの理解が追い付かず「コードソースファイル」についてはスルーしていました。
ただ理解を深めると「これはもしかしたら強力かもしれない…！？」と感じてきました。


- ライブラリやワークフローだけでは難しい処理要件がある…とか、
- Qiitaで見つけたコードやChatGPTで生成したコードをコピペで部品化してみる…とか、
- ローコード実装で煩雑になりすぎた処理をC#でリファクタリングする…とか、

…私は上記のようなシーンで使ってみたいと感じました。

:::note
ちなみに… **「コードを呼び出し」** アクティビティ　でも同じようなことは可能です。
https://docs.uipath.com/ja/activities/other/latest/workflow/invoke-code

ただ以下のような点に、**細かいように見えるけれど大きな違いがある**と感じます。

- 再利用しやすい
- コードを書きやすい
- 開発環境への依存度が下がるため、部品としてテストや保守がしやすい

具体的なアドバンテージとしては、ブレークポイントを設定してのステップイン実行も可能です。よりデバッグがしやすくなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/c249aa93-9fc5-dfba-ebf5-bdf60874d2c5.png)
:::

「あともうちょっとなんだけど…」と諦めていた所に、部分的に使える有効な手段ではないかと思います。
以上、なにか活用にヒントになれば幸いです。


## 補足： staticではない関数でも利用可能です。

staticではない関数でも実行可能でした。何か制約があるのかな、と思ったりしたのですが、C#の基本的な使い方はできるようです。

ログ出力は全く同じでしたので省略します。利用のポイントは以下をご参照ください。

コードは下図です。
```static```のみを除いた「ソースファイル1」を作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/920b8041-2383-b324-2daa-b7e8d3d00053.png)


ワークフローからの呼び出しもほぼ同じですが、```New```がついている点が違いです。
（細かいところですが「ソースファイル1」を指定している点も違います）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3302589/57e11e76-9f68-c177-f8a8-1f09e218b836.png)
（当然、生成したインタンスを変数に設定することも可能です。先の章の書き方の対比としてこのような呼び方としています）


:::note
呼び出し方の解釈は以下のような形です。
class名の後ろに```() カッコ```がついていることが```static```指定との違いです。

```
New namespace名.class名().関数名(引数)
```
:::

:::note
※ プログラムが分かる方向けの補足ですが、検証したところ独自のコンストラクターを定義した呼び出しも可能でした。
:::


以上です。