20200720のC#に関する記事は4件です。

Empty path name is not legal.(Unity,C#) の 意味と エラー対処

Unity(C#)でファイル書き込みをしようとした際にエラーが出ました.
あまりUnityやC#について日本語で書かれているものが見つからなかったので,残しておきます.

Empty path name is not legal. とは

これで検索をかけたところファイル名に使用できない文字が含まれていないか?
という原因がありましたが,
自分の場合は呼び出し位置の変更によってエラーが出たため、
ファイルパスには問題がないことが分かっていました。

⇒Empty path name is not legal.
 は単純に和訳しても「空のパス名は違法です」なので,
 上記で言っていることは全く違いますね……
 全然日本語の記事が見つからなかったので,
 素直に英文の意味を考えればこんなに悩むエラーでもなかったです.反省. 

エラー箇所

エラーが出たのは下記の記述部分でした.
最初はa,b,cを同じ関数の中で定義していたことから,
関数に引数を渡す時点(もしくは渡す前)に文字コード周りで
エラーが起きているのかな?と思いましたがそういうわけでもなく……

hoge.cs
public void WriteHeader(string a,string b,string c){
 using (StreamWriter sw = new StreamWriter(filepath, false, Encoding.GetEncoding("Shift_JIS")))
 {
      string[] s1 = { a,b,c };
      string s2 = string.Join(",", s1);
      sw.WriteLine(s2);
 }
}

結局,原因は?

Unityでありがちな問題ですがStart()の実行順序関連でした.
自分の場合はfilepathを同じクラスのStart()で定義しており,
それより前に別のクラスからWriteHeader()関数を呼び出しているため,
「FilePath」が見つからない,というようなエラーだったようです.
(それならもっと他のエラーが出てもよさそうな気はしますが……)

しかし,Start()の実行順は確か決まりがなかった気がするけど,
どうやって確実に元ファイルのStart()を実行後にこの関数を呼び出せるだろうか?
ということで調べなおしてみたところこんな記事がありました.
https://ekulabo.com/execution-order
どうやらスクリプトの実行順をEdit>Project Settingsから設定できるようです.

よくここまでこれ無しでやってきたもんだな……という便利さなので,
使ったことのない人,同じようなエラーで実行順に悩んでいる人がいたら(いるのか?)
ぜひ一度お試しいただければと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#] List<T>の正体とは

動機

List<T>は配列と違い、動的に要素を追加したり削除したりできることは誰でも知るところだろう。筆者のようなC#初心者は、恥ずかしながら今までのその仕組みを考えたことは一度もなく、ただ何となく便利なコレクションクラスとして使っていたわけだが、あるときList<T>についてちゃんと調べようと思って、Microsoft.Docsを読んでいたら、以外と単純な仕組みで実装されていることを知ったので、簡単な検証を行ってみた。

前提

Microsoft.Docsを読めばクラスの全機能が分かるし、ご丁寧にコードサンプルまで書いてあるので非常にありがたいのだが、如何せん書き方が小難しくて敵わないし、表現に統一性がないのはどうにかならないのだろうか。まずは下記プロパティを見てほしい。

List<T>.Capacity プロパティ

C#
public int Capacity { get; set; }

内部データ構造体がサイズ変更せずに格納できる要素の合計数を取得または設定します。
Capacityは、サイズ変更が必要になる前にList<T>が格納できる要素の数であり、Countは実際にList<T>内にある要素の数です。
Capacityは、常にCount以上です。要素の追加中にCountCapacityを超えた場合は、古い要素をコピーして新しい要素を追加する前に、内部配列が自動的に再割り当てされ、容量が増加します。

すなわち、List<T>は内部的に要素数の固定された配列をラップしていて、アイテムの追加で限界値Capacityを超えそうになったら、自動的に要素数の大きい配列を作り直して元データをコピーしているということらしい。
さらに、下記メソッドの注釈部分を見てみる。

List<T>.Add(T) メソッド

C#
public void Add (T item);

Countが既にCapacityに等しい場合は、内部配列が自動的に再割り当てされ、既存の要素が新しい配列にコピーされてから、新しい要素が追加されるまで、List<T>の容量が増加します。
CountCapacityより小さい場合、このメソッドは $O(1)$ 操作になります。 新しい要素に対応するために容量を増やす必要がある場合、このメソッドは $O(n)$ 操作になります。ここで、$n$ はCountです。

つまり、通常の値の追加であれば、既に確保されたメモリへの書き込みなので $O(1)$ 操作で済むが、Capacityが足りなくなったら配列の作成とコピーの手間が発生するので、$O(n)$ 操作になるということらしい。

ここで、実際どのようにサイズ変更が行われているのか検証してみたいと思う。

観測

List<T>に対してAddメソッドを繰り返したときに、Capacityがどのように変更されるかを観測するため、以下のメソッドを用意した。

観測用メソッド
/// <summary>
/// List<T> の Capacity がどのように変動するか観測します。
/// </summary>
/// <param name="list">List<T>。</param>
/// <param name="length">探索する長さ。</param>
private static void ViewCapacity<T>(List<T> list, int length)
{
    var result = new List<(int Start, int Last, int Capacity)>();
    (int Count, int Capacity) current = (list.Count, list.Capacity);
    for (int i = 0; i < length; i++)
    {
        list.Add(default(T));
        if (list.Capacity > current.Capacity)
        {
            // Capacityに変化があった場合は記録
            result.Add((current.Count, list.Count - 1, current.Capacity));
            current = (list.Count, list.Capacity);
        }
    }
    result.Add((current.Count, list.Count, list.Capacity));
    Console.WriteLine(" Count      | Capacity");
    result.ForEach(r => Console.WriteLine($"{r.Start.ToString().PadLeft(4)} - {r.Last.ToString().PadLeft(4)} | {r.Capacity.ToString().PadLeft(4)}"));
}

List<T>には下記のコンストラクタが用意されているので、各パターンでCapacityの変化を観測する。

パターン コンストラクタ 説明
1. List() 空で、既定の初期量を備えた、List<T>クラスの新しいインスタンスを初期化します。
2. List(IEnumerable<T> collection) 指定したコレクションからコピーした要素を格納し、コピーされる要素の数を格納できるだけの容量を備えた、List<T>クラスの新しいインスタンスを初期化します。
3. List(int capacity) 空で、指定した初期量を備えた、List<T>クラスの新しいインスタンスを初期化します。

パターン1. List()

  • 特に何も考えず初期化した場合を観測してみる。
実行処理1-1
ViewCapacity(new List<int>(), 1000);
実行結果1-1
 Count      | Capacity
   0 -    0 |    0
   1 -    4 |    4
   5 -    8 |    8
   9 -   16 |   16
  17 -   32 |   32
  33 -   64 |   64
  65 -  128 |  128
 129 -  256 |  256
 257 -  512 |  512
 513 - 1000 | 1024
  • 観測メモ
    • 初期化された状態のとき、Capacity0になっている。
    • 初めて項目を追加したときに、Capacity4が設定される。
    • 以降、CountCapacityを超えようとすると、Capacity2倍になる。

パターン2. List(IEnumerable<T> collection)

  • 初期化時に要素数を与えた場合を観測してみる。
実行処理2-1
ViewCapacity(new List<string>(Enumerable.Repeat(string.Empty, 100)), 1000 - 100);
実行結果2-1
 Count      | Capacity
 100 -  128 |  128
 129 -  256 |  256
 257 -  512 |  512
 513 - 1000 | 1024
実行処理2-2
ViewCapacity(new List<string>(Enumerable.Repeat(string.Empty, 128)), 1000 - 128);
実行結果2-2
 Count      | Capacity
 128 -  128 |  128
 129 -  256 |  256
 257 -  512 |  512
 513 - 1000 | 1024
実行処理2-3
ViewCapacity(new List<string>(Enumerable.Repeat(string.Empty, 129)), 1000 - 129);
実行結果2-3
 Count      | Capacity
 129 -  256 |  256
 257 -  512 |  512
 513 - 1000 | 1024
  • 観測メモ
    • 初期化された状態のとき、Countに応じてCapacityが自動でセットされる。
    • Capacityの値と増加方法は、空のコンストラクタで初期化した場合と同じ。

パターン3. List(int capacity)

  • 最後に、初期化時にCapacityを指定するとどうなるのか試してみる。
実行処理3-1
ViewCapacity(new List<int>(3), 1000);
実行結果3-1
 Count      | Capacity
   0 -   3  |    3
   4 -   6  |    6
   7 -   12 |   12
  13 -   24 |   24
  25 -   48 |   48
  49 -   96 |   96
  97 -  192 |  192
 193 -  384 |  384
 385 -  768 |  768
 769 - 1000 | 1536
実行処理3-2
ViewCapacity(new List<int>(100), 1000);
実行結果3-2
 Count      | Capacity
   0 -  100 |  100
 101 -  200 |  200
 201 -  400 |  400
 401 -  800 |  800
 801 - 1000 | 1600
  • 観測メモ
    • 初期化時に与えられたCapacityが設定される。
    • 以降、CountCapacityを超えようとすると、Capacity2倍になる。

以上の観測から、サイズが最初から判明している場合は、明示的にCapacityを指定した方が内部的再定義の時間を短縮できるのでは、という推測が浮かんでくる。

計測

通常の初期化と明示的にCapacityを指定した初期化で、その後Addメソッドを繰り返した時の処理速度がどのくらい変わるか比較するため、下記のメソッドを用意した。

計測用メソッド
/// <summary>
/// List<T> と List<T>(capacity) で Add() メソッドを繰り返したときの処理速度を比較します。
/// </summary>
private static void CompareTime()
{
    Console.WriteLine("Count | WithOut[ms] | With[ms]    | percentage[%]");
    for (var i = 0; i < 9; i++)
    {
        // 要素数は1, 10, 100, ..., 100000000 のパターンを試す
        var length = (int)Math.Pow(10, i);
        double Test(List<int> list)
        {
            var watch = new Stopwatch();
            watch.Start();
            for (int k = 0; k < length; k++) list.Add(0);
            watch.Stop();
            return watch.Elapsed.TotalMilliseconds;
        }

        var timeSpans = (new List<double>(), new List<double>());
        for (int j = 0; j < 10; j++)    // 試行回数:10回
        {
            timeSpans.Item1.Add(Test(new List<int>()));
            timeSpans.Item2.Add(Test(new List<int>(length)));
        }

        // それぞれの平均処理速度と速度改善率を計算
        var ave1 = timeSpans.Item1.Average();
        var ave2 = timeSpans.Item2.Average();
        Console.WriteLine($"10^{i}  | {ave1:000.0000000} | {ave2:000.0000000} | {((ave2 * 100) / ave1):00.00}");
    }
}
筆者のポンコツPCで計測[1回目]
Count | WithOut[ms] | With[ms]    | percentage[%]
10^0  | 000.0003400 | 000.0000900 | 26.47
10^1  | 000.0002200 | 000.0000700 | 31.82
10^2  | 000.0021600 | 000.0002900 | 13.43
10^3  | 000.0174000 | 000.0025900 | 14.89
10^4  | 000.0923000 | 000.0220400 | 23.88
10^5  | 000.4015200 | 000.2589700 | 64.50
10^6  | 005.6044200 | 003.2822300 | 58.57
10^7  | 064.1446900 | 033.7847300 | 52.67
10^8  | 544.1132700 | 300.4292300 | 55.21
筆者のポンコツPCで計測[2回目]
Count | WithOut[ms] | With[ms]    | percentage[%]
10^0  | 000.0003300 | 000.0000800 | 24.24
10^1  | 000.0002000 | 000.0001100 | 55.00
10^2  | 000.0016800 | 000.0003000 | 17.86
10^3  | 000.0052400 | 000.0022200 | 42.37
10^4  | 000.0699700 | 000.0215700 | 30.83
10^5  | 000.3693000 | 000.2578000 | 69.81
10^6  | 005.8541300 | 003.1537400 | 53.87
10^7  | 067.5346200 | 031.0078600 | 45.91
10^8  | 542.6710200 | 300.1461600 | 55.31

今回の計測だけで言えば、やはり明示的にCapacityを指定した方が処理速度は速くなることが分かった。

終わりに

闇に包まれていたList<T>の真の姿が見えてきて、少し親近感が湧いた気がする、、
処理速度計測はPCのスペックにもよるところなので、各々試されたい。
また他の言語、例えばc++でいうVectorの内部構造はどのようになっているのかも興味深いところである。

追記

2020.07.22
@koturn 氏から教わりましたが、リファレンスソースList<T>の内部実装を確認しました。随所で下記メソッドが呼ばれ、二倍量のCapacityが確保されていると分かりますね。情報ありがとうございました!

list.cs
// Ensures that the capacity of this list is at least the given minimum
// value. If the currect capacity of the list is less than min, the
// capacity is increased to twice the current capacity or to min,
// whichever is larger.
private void EnsureCapacity(int min) {
   if (_items.Length < min) {
       int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
       // Allow the list to grow to maximum possible capacity (~2G elements) before encountering >overflow.
       // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
       if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
       if (newCapacity < min) newCapacity = min;
       Capacity = newCapacity;
   }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Webclientクラスを用いてHTTP通信をする。

メモとして残します。

■やり方

Uri url = new Uri(Path.Combine("https://hoge.com", "api.php"));

//WebClientオブジェクトを作成
var wc = new WebClient();
var ps = new NameValueCollection();
// ヘッダを設定
wc.Headers.Add("user-agent", "hogebrowser");  
////送信するPOSTデータを設定
ps.Add("i", "hoge");

////リクエストしてレスポンスを変数へ
byte[] response = wc.UploadValues(url, ps);
wc.Dispose();// オブジェクトを破棄


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UiPathでライブラリに引数を設定して呼び出し、処理結果を取得する

UiPathでライブラリに引数を設定して呼び出し、処理結果を取得する

前回の記事「UiPathでライブラリを直接呼び出す」「UiPathでライブラリに引数を設定して呼び出す」の拡張編です。
UiPathのプロジェクトファイルに格納されたDLLに対して、引数を設定して呼び出し、処理結果を方法を紹介します。

開発のきっかけ

最近では当たり前ですが、昔はUiPathでカスタムライブラリを作成するには、
Visual StudioでDLLを作成し、Nuget ExplorerなどでDLLをnuget化して利用しかありませんでした。

UiPathでデバッグするにも、Immediate Panelがなかったり、現時点でもComやラムダ式をデバッグしたり
解析することができませんでした。

お約束事項(免責事項)

この記事は2020年6月時点の情報を基に作成しております。
記事の内容は私個人の見解であり、所属する組織の公式見解ではありません。

カスタムライブラリ

前回の記事で使用したサンプルのカスタムライブラリを使用します

ソース内容

前回のソースと同じものです。
今回は、Test3のメソッドを使用します。

using System.Windows;

namespace UiPathCustomLibrary
{
    public static class TestClass
    {
        public static void Test1()
        {
            MessageBox.Show("Hello World");
        }
        public static void Test2(string message)
        {
            MessageBox.Show(message);
        }

        public static int Test3(int a, int b)
        {
            return a + b;
        }
    }
}

UiPathのプロジェクト構成

こちらも、前回と同様の構成を想定しております。
カスタムライブラリのサンプル(UiPathCustomLibrary.dll)はプロジェクト直下に格納しています。

image.png

UiPathの実装

基本的には、前回と同様の実装です。変更点は以下の3つです。

  • 「7.変数の値を設定」で、使用するメソッド名を「Test3」に変更し、
  • 「7.変数の値を設定」で、引数に「{1,2}」の2つを設定します。
  • 「11.処理の呼び出し」で取得した処理結果を表示を追加します。

1. アセンブリ変数の作成

UiPathでasmという変数を作成します。
image.png

2. 型を変更

asmの型をBrowse for Types...を選択します。
image.png

3. 型名

Type NameにSystem.Reflection.Assemblyを入力し、Asssemblyを選択します。
image.png

4. Assignの追加

Assign(代入)アクティビティを追加し、左辺にasmを設定します。
image.png

5. Assignの値を設定

Assignの右辺に以下の値を設定します。

Assembly.LoadFile( _
    Path.Combine( _
        Environment.CurrentDirectory, _
        "UiPathCustomLibrary.dll" _
    ) _
)

image.png

これで、DLLをロードすることができました。

6. 変数の追加

実行するメソッドの変数を追加します

  • 名前空間(usingName) : String
  • クラス名(className) : String
  • メソッド名(methodName) : String
  • 引数(paramObj) : System.Object[] or Object[]

image.png

7. 変数の値を設定

変数に値をそれぞれ設定します。

  • usingName = "UiPathCustomLibrary"
  • className = "TestClass"
  • methodName = "Test3"
  • paramObj = {CType(1, Object), CType(2, Object)}

image.png

8. 実行結果の変数を追加

実行結果を設定する変数 resultObj を追加し、変数の型をObjectに変更します。

image.png

9. Assignの追加

実行と実行結果を設定するAssignを追加します。

image.png

10. 処理の呼び出し

resultObjの値に、以下の値を設定します

asm _
    .GetType( usingName + "." + className ) _
    .GetMethod(methodName) _
    .Invoke(Nothing, paramObj)

image.png

11. 処理結果の出力

WriteLineアクティビティを追加し、resutObjの値を出力します。

  • Text : "処理結果(resutlObj) = " + resultObj.ToString

image.png

UiPathの実行

実行するとTest3の処理結果が、Outputパネルに出力されます。

image.png

終わりに

本投稿では、メソッドに引数を設定して呼び出し、処理結果を取得する方法をご紹介しました。

もし、よければLGTMをお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む