20200331のC#に関する記事は18件です。

【KPI】C#の基本的な画面の作り方 - 05:登録ボタン押下イベントとメソッド作成 -【C#】

今回は前回と同じように、登録ボタンの押下イベントを作成する。
と同時に、その中で使うメソッドの一部を作成する。

同じようにデザインの登録ボタンをダブルクリックして作成。
無題1.png
今回はtry-catch処理を入れておく。

まず、登録前に、項目の桁数をチェックしたい。
下記仕様を決める
[コード]:最大桁数6桁の場合のみ登録可能
[名称] :値が入っており、最大桁数20桁(全角1文字は2桁換算)を超えていなければ登録可能。
     ※現在、[名称]テキストボックスは最大で20文字が入る。理論上は全角20文字で40桁換算となる。

この処理を馬鹿正直に書く場合はかなりコードが長くなる。
さらに、これらのチェックは、項目ごとに似通っている。
よって、共通で使用できるメソッド[CheckLength]を作る。
無題2.png

C#
        private bool CheckLength(TextBox txt, string name, int len, bool maxOnly)
        {
            // 桁数取得(全角を2桁として扱う)
            int lenNow = Encoding.GetEncoding("Shift_JIS").GetByteCount(txt.Text);

            // 未入力の場合
            if (lenNow.Equals(0))
            {
                MessageBox.Show(name + "が入力されていません。");
                txt.Focus();
                return false;
            }

            // 最大桁数のみを受け付ける場合
            if (maxOnly && !lenNow.Equals(len))
            {
                MessageBox.Show(name + "の桁数が" + len + "桁ではありません。");
                txt.Focus();
                return false;
            }
            // 最大桁数を超える場合
            else if (lenNow > len)
            {
                MessageBox.Show(name + "の桁数が" + len + "桁を超えています。");
                txt.Focus();
                return false;
            }
            return true;
        }
  • txt :チェック対象のテキストボックス
  • name :項目名
  • len :最大桁数
  • maxOnly:trueの場合は最大桁数の場合のみチェックOKを返す

チェックがOKであれば、trueを、そうでなければfalseを返す。

このメソッドを使用して、登録ボタン押下イベントを作成する。
無題3.png

C#
        private void BtnUpd_Click(object sender, EventArgs e)
        {
            try
            {
                // チェック処理
                if (!CheckLength(this.TxtCode, this.LblCode.Text, this.TxtCode.MaxLength, true))
                {
                    return;
                }
                if (!CheckLength(this.TxtName, this.LblName.Text, 20, false))
                {
                    return;
                }
            }
            catch (Exception ex)
            {
                // エラーメッセージを表示
                MessageBox.Show(ex.Message);
            }
        }

これで、下記の各画面の状態で登録ボタンを押下した際、直近下側のメッセージが表示されるようになる。
無題4.png
無題5.png
無題5_5.png
無題6.png
無題7.png
無題8.png

これで事前チェックが完成する。
次回は更に作り込んでいく。

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

【KPI】C#の基本的な画面の作り方 - 04:終了ボタン押下イベントの作成 -【C#】

次は終了ボタンを押した際のイベントを作成する。

終了ボタンを押した際は、とりあえず画面が終了するものとする。

デザインの終了ボタンをダブルクリックする。
無題1.png

イベント[BtnEnd_Click]が作成される。
無題2.png

中に押下時の終了処理を記述する。
無題3.png

C#
        private void BtnEnd_Click(object sender, EventArgs e)
        {
            try
            {
                // 何か終了処理があればここに
            }
            catch (Exception ex)
            {
                // エラーメッセージを表示
                MessageBox.Show(ex.Message);
            }

            // 画面を終了する
            this.Close();
        }

※try-catchは今回使用してないので消して構わない。

デバッグ時の画面はこうなる。
無題4.png
終了ボタンを押した際は、画面が終了する。

次回は登録ボタンの押下イベント……に使用するチェック処理を作成する。

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

【KPI】C#の基本的な画面の作り方 - 03:プロパティの設定 -【C#】

前回は、画面にコントロールを大まかに張り付けた。

今回は、張り付けたコントロールのプロパティを設定する。

下記の画像を参照。
プロパティを設定したい項目を選択し、右下のプロパティ項目を設定する。
無題6.png

プロパティの設定

  • [終了]ボタン
    • (Name):[BtnEnd]を設定
    • Font - Name:[MS ゴシック]を設定
    • Text:[終了 (F1)]を設定
      • これが表示される
  • [登録]ボタン
    • (Name):[BtnEnd]を設定
    • Font - Name:[MS ゴシック]を設定
    • Text:[登録 (F12)]を設定
  • [コード]ラベル
    • (Name):[LblCode]を設定
    • Font - Name:[MS ゴシック]を設定
    • Text:[コード]を設定
  • [名称]ラベル
    • (Name):[LblName]を設定
    • Font - Name:[MS ゴシック]を設定
    • Text:[名称]を設定
  • [コード]テキストボックス
    • (Name):[TxtCode]を設定
    • ImeMode:[Disable]を設定
      • ImeModeの変換ができなくなる。
    • MaxLength:[6]を設定
    • Font - Name:[MS ゴシック]を設定
    • Text:[123456]を設定
  • [名称]テキストボックス
    • (Name):[TxtCode]を設定
    • ImeMode:[Disable]を設定
      • ImeModeの変換ができなくなる。
    • MaxLength:[20]を設定
      • 厳密に言えば全角1文字を2桁として計算した最大20桁としたいが、イベントで調整する。
    • Font - Name:[MS ゴシック]を設定
    • Text:[あいうえおかきく10]を設定

これらを設定できたら大雑把には見た目は完成。
他にもレイアウトを調整して上記の画面となる。

次回はボタンイベントを設定する。

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

[C#]画像に対していろいろ行う

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

WPFの画面表示や画像回りでいろいろやることがあるが、なんだかややこしくて、まとめようと思ってもまとめきれないまま時間が過ぎている。
いきなりまとめるのをちょっとあきらめて、やりたいこと、やったことのレシピ集みたいなのを作って経験値を貯めてから、見えてきたものを後でまとめようと思う。
→主に、System.Drawing.Bitmapを使って画像をファイルに保存したり、編集したときのことをまずは書く。(あとは、出てきたやつを都度追記していく。)

画像ファイルを開いて、好きな文字やら図形を書き込んで、別のファイルに保存する(その1)

  1. 元画像ファイル(.bmp等)をSystem.Drawing.Bitmapに格納
  2. System.Drawing.Graphicsで格納した画像を編集
  3. 出力先ファイルへのFileStreamを作成
  4. System.Drawing.BitmapSaveメソッドで、出力先ファイルへのFileStreamを指定し保存
  5. →これで、出力先ファイルに保存される
サンプル1.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var fs = new FileStream(@"output1.bmp", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 保存方法① streamで保存 → "output1.bmp"に保存される
    fs.SetLength(0);
    bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Bmp);
}

※fs(ファイルストリーム)がClose=Disposeされた時点(つまり、usingを抜けた時点)で、実際にbmpファイルに書き出される。

画像ファイルを開いて、好きな文字やら図形を書き込んで、別のファイルに保存する(その2)

  1. 元画像ファイル(.bmp等)をSystem.Drawing.Bitmapに格納
  2. System.Drawing.Graphicsで格納した画像を編集
  3. System.Drawing.BitmapSaveメソッドで、出力先ファイルのパスを指定し保存
  4. →これで、出力先ファイルに保存される
サンプル2.cs
using (var bmp = new Bitmap(@"input.bmp"))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 保存方法② ファイルパス指定で保存 → "output2.bmp"に保存される
    bmp.Save(@"output2.bmp");
}

画像ファイルを開いて、好きな文字やら図形を書き込んで、画面に表示する

FileStreamを使うやり方。

サンプル3-1.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var fs = new FileStream(@"output1.bmp", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    // 画面に表示 ※MyImageは、xamlに配置された<Image Name="MyImage"/>
    var a = BitmapFrame.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    MyImage.Source = a;
}

下記でも、同じことができる。(ファイルをかまさずに、メモリ(MemoryStream)で済ます)

3-2.cs
using (var bmp = new Bitmap(@"input.bmp"))
using (var ms = new MemoryStream())
{
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawString("あいうえお", new Font("Arial", 16), System.Drawing.Brushes.Red, new PointF(10.0f, 10.0f));
        g.DrawRectangle(Pens.Red, new System.Drawing.Rectangle(100, 100, 100, 100));
    }

    //// MemoryStreamに一旦保存
    ms.SetLength(0);
    bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);

    // 画面に表示
    var a = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    MyImage.Source = a;
}

画面表示を取り込んで、ファイルに保存する

以前の記事参照。
BitmapSourceやその派生クラスから、System.Drawing.Bitmapに変換する

// BitmapSourceの派生クラス「RenderTargetBitmap」で、画像を取ってくる
var canvas = new RenderTargetBitmap((int)MyImage.ActualWidth, (int)MyImage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
canvas.Render(MyImage); // canvasに画像を描画

// BmpBitmapEncoderに画像を入れる
using (var stream = new MemoryStream())
{
    // BmpBitmapEncoderに画像を書きこむ(使うBitmapEncoderの派生クラスによって、いろんなフォーマットのファイルを作れる)
    //BitmapEncoder encoder = new BmpBitmapEncoder();
    BitmapEncoder encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(canvas));// canvasからBitmapEncoder に書き込み
    encoder.Save(stream);

    // BmpBitmapEncoderからSystem.Drawing.Bitmapをつくる
    var bitmap = new System.Drawing.Bitmap(stream);
    bitmap.Save(@".\out.jpeg");
}

画面表示を取り込んで、画像に四角を書き込んで、それをもう一回画面に表示する

ややこしい。下記のようなクラスを経由していく流れ。

  • RenderTargetBitmap(画面から画像の取り込み)
  • BmpBitmapEncoder(RenderTargetBitmapをMemoryStreamに流し込む)
  • MemoryStream(編集前の画像が流れてる)
  • System.Drawing.Bitmap(MemoryStreamからBitmap作成)
  • Graphics(BitmapをGraphicsで編集)
  • MemoryStream(編集の画像が流れてる)
  • BitmapFrame(MemoryStreamからBitmapFrameを作成)
  • Image.Source(BitmapFrameを画面にセット)
  • →おわり

※画面上の画像に四角を書き込む、ということがこれでもできそう。
 できそうだが、画面上に四角を出したいなら、xaml上に<Canvas>とその中に<Rectangle/>を置いて、そのWidthやheight、Canvas.TopやCanvas.Bottomをプログラムで制御するほうが絶対いいなと思った。(テキストでも同様(<TextBlock>で。))

// 画面からRenderTargetBitmapに画像を描画
var canvas = new RenderTargetBitmap((int)MyImage.ActualWidth, (int)MyImage.ActualHeight, 96, 96, PixelFormats.Pbgra32);
canvas.Render(MyImage);

using (var stream = new MemoryStream())
{
    // MemoryStreamに、RenderTargetBitmapから画像を流し込む
    BitmapEncoder encoder = new BmpBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(canvas));
    encoder.Save(stream);

    // MemoryStreamからBitmapを作成
    var editted = new System.Drawing.Bitmap(stream);

    // GraphicsでBitmapを編集(四角を書き込む)
    using (var g = Graphics.FromImage(editted))
    {
        g.DrawRectangle(Pens.Green, new System.Drawing.Rectangle(3, 3, 200, 200));
    }
    // →この時点で、editted(Bitmap)に緑の四角が書き込まれてる
    // Streamはまだ。

    // 四角を書き込んだ画像をstreamに流し込む
    editted.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
    editted.Dispose();

    // MemoryStreamからBitmapを作成から、BitmapFrame(BitmapSourceの子クラス)を作成
    stream.Seek(0, SeekOrigin.Begin);// seekでBeginに戻さないと例外
    var bitmapSource = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
    // それをImageのSourceにセット → 表示!
    MyImage.Source = bitmapSource;
}

参照

いろんな画像関連の型の変換
https://qiita.com/YSRKEN/items/a24bf2173f0129a5825c

これで画像に四角とか書き足す、できんか?(System.Drawingではないやつで)
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/painting-with-images-drawings-and-visuals

しらべたいこと

PixelFormats.Pbgra32 ってなに?
https://water2litter.net/gin/?p=987 でやってるCreateで指定してるやつ。
よく出てくるがじつはわかってない。

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

【KPI】C#の基本的な画面の作り方 - 02:デザインの大まかな指定 -【C#】

前回の続きから。

次はツールボックスより、大雑把に画面に必要な項目を用意する。

今回は画面の仕様として。「コード・名称の項目をデータテーブルに登録する」というシンプルなものを想定。

まずは[登録ボタン][終了ボタン]となる2つのボタンを用意する。
ボタンは[ツールボックス][コモン コントロール][Button]から作成する。
無題1.png
無題2.png

次は[コード][名称]のラベルを用意する。
ラベルは[ツールボックス][コモン コントロール][Label]から作成する。
無題3.png
無題4.png

次は[コード][名称]のテキストボックスを用意する。
テキストボックスは[ツールボックス][コモン コントロール][TextBox]から作成する。
無題5.png
無題6.png

大体このようなデザインを作成できる。
次回は今回作成したコントロールのプロパティを修正していく。(名前や見た目、入力桁数等)

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

【KPI】C#の基本的な画面の作り方 - 01:プロジェクトの新規作成 -【C#】

とても大雑把に、私がよく仕事で作るようなC#画面の作成方法を載せる。
Windowsフォームアプリケーションを使用して作成する為、興味があれば。

Visual Studioを起動した後、下記、[ファイル][新規作成][プロジェクト]を選択。
無題2.png

下記、[Visual C#][Windows][Windows フォーム アプリケーション]を選択。
プロジェクト名は画面IDをつける。(今回はFrm100)
無題3.png

下記が完了する。
無題4.png

次回以降、これに対してデザインや処理の追加を行っていく。

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

Ajax通信 覚書

 序文

 Ajax通信の簡単な覚書のつもりで作成しました。初心者が初心者にもわかりやすいように書いたものなので拙い点は多々あるはずですが、ご容赦ください。

 使用言語

1.JavaScript
2.C#

 サンプル

      function (method, url, param) {
        let defer = $.Deferred();        
        $.ajax({
            type: method,
            url: url,
            data: param,
            success: function (data) {

                    let json = JSON.parse(data);
                    defer.resolve(json);
                    return;

            },
            error: function () {
                alert("通信に失敗しました。");
                defer.reject(false);
            }

        });

 

 処理の概要

1.サーバにjson型のデータを、指定したHttpメソッドで、指定したURLに送信。
2.成功したときと失敗したときで処理が分岐する。
 (ア)成功時
    ・レスポンスされたデータをjson型に変換。
 (イ)失敗時
    ・「通信に失敗しました。」というメッセージ付きの警告が出る。

 単語の解説

  • type: Httpメソッド(POST or GET)
  • url: 処理を担当するメソッド(表記は「../コントローラ名/メソッド名」)
  • data: サーバに送るデータ(json型で送る場合の表記は「json = {'データの名前':データの値(変数など), 'データの名前':データの値(変数など),....}」)
  • success: サーバへの通信が成功した後に起こる処理を記述する。ここでの「data」はサーバからレスポンスされたデータを指す。
  • error: サーバへの通信が失敗した後に起こる処理を記述する。

 終わりに

 大雑把な説明しかしていないので、他の記事やサイトも参考にしていくとよいかと思われます。

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

二重配列 と 配列内の配列

前置き

ある日ある時、パズルゲームを作っている時に二重配列を使いたくなって使いました。
他のある日、また二重配列を使いたくなって使いました。
また別のある日、その二つの二重配列を見返しました。

あれぇれぇ~? この二つ種類違うぞぉ? おっかしいなぁ~?( ゚Д゚)

ここから本編

hairethu.rb
/C#/
/ある日あるときの配列/
 int[][] bingo = { new int[] { 0, 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8 }, new int[] { 0, 3, 6 }, new int[] { 1, 4, 7 }, new int[] { 2, 5, 8 }, new int[] { 0, 4, 8 }, new int[] { 2, 4, 6 } };

/他のある日の配列/
int[,] day = new int[2,3];

前者の配列は、要素数が一定でないときにも対応できる。
けど、後者の配列は要素数が一定でないときは対応できない。
配列を定義するだけなら後者でよさそう。('ω')

hairethu.rb
/C#/
/要素数が一定でないときの配列の使い方/
/要素数が3個の配列(中身{0,1,2})と2個の配列(中身{3,4})を、同じ配列に入れている/
 int[][] arr1 = { new int[] { 0, 1, 2 }, new int[] { 3, 4 }};


/要素数が一定な時の配列の使い方/
/前者と同じように中身を定義することもできる(定義していないと全部中身は0になる)/
int[,] arr2 = new int[2,3]{ { 0, 1, 2 } { 3, 4, 5} };

ふむふむ、それならば、ある日ある時のビンゴ配列は、要素数が同じだから後者の配列でもよかった……。

ど、どうせ中身定義しなきゃいけなかったわけだし?ゲーム内ではちゃんと動いたわけだし?過去のことを思っちゃダメ。未来のことも思っちゃダメ。今を精一杯生きようじゃないか。(*´ω`)

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

二次元配列 と 配列内の配列

前置き

ある日ある時、パズルゲームを作っている時に二重配列を使いたくなって使いました。
他のある日、また二重配列を使いたくなって使いました。
また別のある日、その二つの二重配列を見返しました。

あれぇれぇ~? この二つ種類違うぞぉ? おっかしいなぁ~?( ゚Д゚)

ここから本編

array1
//C#
//ある日あるときの配列
 int[][] bingo = { new int[] { 0, 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8 }, new int[] { 0, 3, 6 }, new int[] { 1, 4, 7 }, new int[] { 2, 5, 8 }, new int[] { 0, 4, 8 }, new int[] { 2, 4, 6 } };

//他のある日の配列
int[,] day = new int[2,3];

前者の配列は、要素数が一定でないときにも対応できる。
けど、後者の配列は要素数が一定でないときは対応できない。
後者のやつが二次元配列で、前者はあくまでも配列内の配列って扱いなのかな。
配列を定義するだけなら後者でよさそう。('ω')

array2
//C#
//要素数が一定でないときの配列の使い方
//要素数が3個の配列(中身{0,1,2})と2個の配列(中身{3,4})を、同じ配列に入れている
 int[][] arr1 = { new int[] { 0, 1, 2 }, new int[] { 3, 4 }};


//要素数が一定な時の配列の使い方
//前者と同じように中身を定義することもできる(定義していないと全部中身は0になる)
int[,] arr2 = new int[2,3]{ { 0, 1, 2 } { 3, 4, 5} };

ふむふむ、それならば、ある日ある時のビンゴ配列は、要素数が同じだから後者の配列でもよかった……。

ど、どうせ中身定義しなきゃいけなかったわけだし?ゲーム内ではちゃんと動いたわけだし?過去のことを思っちゃダメ。未来のことも思っちゃダメ。今を精一杯生きようじゃないか。(*´ω`)

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

プログラミング演習を実施できるウェブシステム

はじめに

はじめて Qiita に投稿します.普段,私は非情報系の学科でプログラミングに関連する内容の演習や講義を行っています.数十人のプログラミング(というよりコンピュータ)の初心者を対象に,統計学や制御工学の文脈で,R や Octave,Pythonなどを教えているのですが,教員ひとりでこれをハンドリングするのは非常に大変です.そこで,数年前にウェブでプログラミング課題を実施できる教育管理システムを開発し,授業で使用しています.このシステムを導入してから,プログラミングの教育にかかる些末な手間から解放され,本来伝えたい話に集中できていて,手放せないツールになっています.

ところで,大学は COVID-19 対策に追われています.私も教育業務だけに限っても,授業の内容をすべて見直す必要があり,本当に大変です.ただ,幸いにもプログラミング系の授業は開発していたシステムがあるので,Zoomも併用すればなんとかなりそうです.

Zoomもそうですが,いま,色々な人がこの難局を乗り越えるために動いて下さっていて,事実,大変助かっています.私も誰かの役に立てられれれば幸いと思い,元々ひとりでひっそりと使っていくつもりだったこのシステムを公開させていただきます.

https://github.com/kjmtks/LMS7

数年間の運用経験に基づいて仕様を練り直し,最近,短期集中的に再開発したものです.
主たる機能を挙げておきます:

  • chrootで切り離されたサンドボックス環境を任意に構築することができ,その環境内でプログラムを実行する演習課題を作成することができる
  • プログラム課題の合否をテストケース方式で自動的に判定することができる
  • Gitをインタフェースとして,教育コンテンツやユーザーのデータをアップロード・ダウンロードすることができる

ひとまず動かしてみるには,Docker および docker-compose をインストール後,

$ git clonet https://github.com/kjmtks/LMS7.git
$ cd LMS7
$ make development-up

を実行後に http://localhost:8080 へアクセスするだけです.ただし,独特の仕様となっておりますので,どう使えば良いのか全くわからないと思います.

本記事では,デモ用コンテンツを動作させる手順について解説します.

入手方法

本システムは Docker および docker-compose を用いることを推奨しており,以降もこれらを前提として解説しています.また,Windows 10 pro, macOS, Ubuntuでの動作を確認しています.

ソースはGitHubのリポジトリから最新版を入手してもいいのですが,雑にGitHubを運用しているため,Releaseから入手することを推奨します:

$ wget https://github.com/kjmtks/LMS7/archive/ver.1.0.zip
$ unzip ver.1.0.zip
$ cd LMS7-ver.1.0

起動方法

プロジェクトルートには Makefile と 複数の docker-compose.*がありますが, make コマンドを介して docker-compose を実行するように準備しています.
ローカル環境で動作させるだけであればなんらファイルをカスタマイズする必要はなく,そのまま

$ make development-up

を実行すればOKです.初回実行時にはイメージを生成するために時間がかかります.
しばらくすると以下のような出力が得られます:

...
app_1  | info: Microsoft.Hosting.Lifetime[0]
app_1  |       Now listening on: http://[::]:8080
app_1  | info: Microsoft.Hosting.Lifetime[0]
app_1  |       Application started. Press Ctrl+C to shut down.
app_1  | info: Microsoft.Hosting.Lifetime[0]
app_1  |       Hosting environment: Development
app_1  | info: Microsoft.Hosting.Lifetime[0]
app_1  |       Content root path: /app/ALMS.App/out

その後, http://localhost:8080 にアクセスすると,以下の画像のようなページが表示されます.

ss01.png

デモ用コンテンツの準備

ログイン

上部メニューの右端にある Login をクリックし,ログイン画面に移動してください.

ss02.png

初期ユーザーとして,管理者権限を持つ admin と,通常権限の test001, test002, ..., test010 が登録されていますが,まずは管理者権限のあるユーザーで設定を行う必要があるので,adminでログインします.パスワードもadminです.

ss03.png

ログインに成功すると,ホーム画面に自動的に遷移します.上部メニューに項目が増えているはずです.Administration は管理ページへのリンクで, admin は自分のページへのリンクです.admin をクリックしてください.

ss04.png

左側に自分の情報が表示されています. タグ型のラベルはそれぞれユーザーのもつ権限を表しています.
Senior 権限をもつユーザーは,講義や,サンドボックス環境を新たに作成することができます.システムのセキュリティ上重要な部分に関わるユーザーですので,管理者が信用できるユーザーのみに付与してください.
Admin 権限は Senior と同等の権限に加え,ユーザーの登録・編集・削除を行うことができるほか,システム全体の設定を行うことができます.

サンドボックスの作成

まず,Sandbox Settings でプログラムを安全に実行するためのサンドボックス環境を作成します.
+ のアイコンをクリックし,表示されたダイアログに必要な情報を入力します.

ss05.png

ここでサンドボックス環境に講義で使用する処理系等をインストールするコマンドを記述するのですが,それを何もない状態から書くのは面倒です.
本システムでは Ruby と Python のインストールコマンドのテンプレートを用意しています(このテンプレートは管理ページで任意に作成することができます).
デモ用コンテンツを動作させるには, Ruby の環境が必要ですので, Use package? より, Ruby 2.7.0 を選択してください.Name, Description, Setup Commands 欄が自動的に埋められます.

ss06.png

問題が無ければ,OK をクリックしてください.

ss07.png

環境とRubyのインストールが始まり,状況がリアルタイムで表示されます.この処理には非常に時間がかかります.
Spinner が回転している間は処理中です.このページから移動したりブラウザを閉じずに,処理が終了するまで待機していてください.

ss08.png

上の画像のような画面になればサンドボックスの構築は完了です.
Command... のところに任意のコマンドを入力して Enterキーを押下することで,実際にその環境でコマンドを走らせることができます.このとき,コマンドは root ユーザーで実行されますので,追加でセットアップしたいものがあればここで行うことができます.

講義の作成

次に,Lecture Settings1で講義を作成します.Lecture Settings+ アイコンをクリックし,表示されたダイアログにて以下のように入力してください.

ss09.png

Name には半角英数で講義の識別名を入力します. Subject には講義名を自由に入力します. Description にはメモを任意で書いておくことが出来ます.
Teachers および Students には,この講義に割り当てる教員および学生のアカウント名をスペースまたは改行区切りで記述します.いまはひとまず Teachersadmin が含まれているだけで充分です.
Empty Repositories のチェックを忘れずにつけておいてください.

以上の手続きで,画面が以下のようになっているはずです:

ss10.png

コンテンツのアップロード

Lecture Settingsdemo のリンクをクリックし, デモ講義の管理ページへ移動してください.

ss11.png

本システムでは,講義コンテンツをGitを介してアップロードすることになっています.いまは何のコンテンツもないため, Contents Repository に Git のコマンド群が表示されています.後で使用しますので,このコマンド群をコピーしておきます.

ブラウザの操作は一度置いておき,シェルでの操作を行います.

$ cp -r samples/demo ~/demo
$ cd ~/demo
# 以下にコピーしておいたGitのコマンド群をはりつける
$ git init .
$ git remote add origin http://localhost:8080/api/git/lecture/admin/demo.contents.git
$ touch .keep
$ git add .
$ git commit -m "Initial Commit"
$ git push origin master

Basic認証では,アカウント,パスワードともに admin としてください2
以上の操作で,本システムにデモ用コンテンツが push (アップロード)されます:

...
Counting objects: 100% (13/13), done.
Delta compression using up to 4 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (13/13), 3.59 KiB | 3.59 MiB/s, done.
Total 13 (delta 0), reused 0 (delta 0)
To http://localhost:8080/api/git/lecture/admin/demo.contents.git
 * [new branch]      master -> master

ブラウザの画面を更新してください.コンテンツが確認できるようになっています.
また, Parameters にも複数のデータが登録されています.これは,本講義のコンテンツで利用可能なグローバル定数のようなものです.~/demo/parameters.xml を編集して再度 push することで内容を変更できます.

ss12.png

講義サンドボックスの作成

最後に,少し下にスクロールした先にある,Sandbox Settings より,講義にサンドボックスを登録します.

ss13.png

+ アイコンをクリックし, Nameruby と入力し, Clone Fromruby を選択し, OK をクリックしてください.

ss14.png

しばらく待つと,以下の画面のように ruby が追加されます.

ss15.png

この操作は,事前に作成しておいた Ruby のサンドボックスをこの講義用に複製する手続きです.事前に作成するサンドボックスと区別するため,講義サンドボックスと呼んでいます.講義サンドボックスではこの講義に割り当てられた教員・学生が,実際のユーザーとして登録されています.具体的には, /etc/passwd, /etc/group にユーザーの情報が書き加えられているとともに,各ユーザーのホームがサンドボックス外にあるユーザーのためのディレクトリにマウントされています.その他は複製時の元のサンドボックスと完全に同一の状態です.

なぜサンドボックスと講義サンドボックスに分けて管理しているのかというと,複数の講義を管理する際に便利だからです.また,今後,同様にサンドボックスを用いる講義とは異なる機能を実装する予定で,それを見越した仕様になっています.

以上で準備は完了です.

デモ用コンテンツの確認

ss16.png

ページ上部の master をクリックすることで,コンテンツページに移動することができます3

ss17.png

本システムの講義で扱う課題のことをアクティビティと呼びます.
アクティビティには,プログラムのコードの記述,単一・複数行のテキスト入力,ファイルのアップロード,フォームをサポートしており,デモ用コンテンツとしてそれぞれのサンプルを用意しています.また,これらのアクティビティは組み合わせることもでき,例えば,ファイルのアップロードとフォームへの入力の両方が必要なアクティビティなども作成可能です.

各アクティビティでは,Save, Run, Submit, Validate, Reset の各操作を行うことができます.それぞれの詳細は次の通りです:

  • Save は現在のアクティビティへの入力状態をユーザーの個人ディレクトリに保存します.
  • Run は,Save を行った上で,アクティビティに定義されたコマンドをサンドボックス環境で実行し,その結果を表示します.典型的にはユーザーの記述したプログラムを実行する目的で使用しますが,例えばフォームの内容を自動でチェックして合否を表示する,といった使い方もできます.
  • Submit は,Save を行った上で,提出用ディレクトリに保存します.この操作をもって提出がされたと見做します.Submitを行う条件として,以下を任意に設定可能です.
    • 提出期限の間のみ提出可能
    • 次に述べる Validate で Accept となった場合にのみ提出可能
  • Validate は,Save を行った上で,アクティビティに定義されたテストケースによる自動検証を行い,その合否(Accept/Reject)を出力します4
  • Reset は,アクティビティを初期状態にリセットします.

また,教員限定で,アクティビティに定義された解答をセットする Answer も利用可能です.

Save, Run, Submit, Validate については,実行の度にその記録がデータベースに残ります.
また,ユーザーのデータもGitで管理されており,いつどのような変更を行ったのかすべて調べることができます.

終了と削除

docker-compose が動いているだけなので,Ctrl+C で終了できます.

システムで生成したデータ(サンドボックスやデータベースの内容)を消去するには,

$ make developemnt-remove

を実行して下さい.

おわりに

奇妙に思える仕様があると思いますが,元々,自分が使いやすければ良い,という考えで仕様を練っておりましたのでご容赦ください.

近日中にコンテンツの作り方やその他の機能について投稿する予定です.


  1. 誤字... 

  2. 本システムの認証情報を使用します. 

  3. 実はブランチに対応しています.学生はmasterブランチに基づくコンテンツのみを閲覧でき,一方,教員は任意のブランチのものを閲覧可能です. 

  4. テストケースの合否を命題論理変数とした,命題論理式としてテストケースを記述することができます. 

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

円分多項式の係数の計算

はじめに

  • 円分多項式
  • 次数が105になって初めて係数に0,1,-1以外が現れるということなので、具体的に計算してみた。

係数を計算するプログラム

円分多項式の係数を計算して、latex形式で出力する。

public static void Run()
{
    var poly_list = new List<List<int>>();
    poly_list.Add(new List<int>() { 1 });
    int n_max = 385;
    for (int n = 1; n <= n_max; n++)
    {
        var poly = new List<int>();
        for (int j = 0; j <= n; j++) poly.Add(0);
        poly[0] = 1;
        poly[n] = -1;
        for (int d = 1; d < n; d++)
        {
            if (n % d != 0) continue;
            poly = Devide(poly, poly_list[d]);
        }
        poly_list.Add(poly);
        Console.WriteLine("\\Phi_{{{0}}}&={1}\\\\", n, Str(poly));
    }
}
static List<int> Devide(List<int> numerator, List<int> denominator)
{
    var result = new List<int>();
    for (int i = 0; i <= numerator.Count - denominator.Count; i++)
    {
        result.Add(numerator[i]);
        for (int j = 1; j < denominator.Count; j++)
        {
            numerator[i + j] -= numerator[i] * denominator[j];
        }
    }
    return result;
}
static string Str(List<int> poly)
{
    var sb = new StringBuilder();
    var dim = poly.Count - 1;
    for (int i = 0; i <= dim; i++)
    {
        int c = poly[i];
        if (c == 0) continue;
        if (c > 0 && i > 0)
        {
            sb.Append("+");
        }
        if (c < 0)
        {
            sb.Append("-");
            c = -c;
        }
        var d = dim - i;
        if (c > 1 || d == 0)
        {
            sb.Append(c);
        }
        if (d == 1)
        {
            sb.Append("x");
        }
        else if (d > 1)
        {
            sb.AppendFormat("x^{{{0}}}", d);
        }
    }
    return sb.ToString();
}

出力結果

\begin{align*}
\Phi_{1}&=x-1\\
\Phi_{2}&=x+1\\
\Phi_{3}&=x^{2}+x+1\\
\Phi_{4}&=x^{2}+1\\
\Phi_{5}&=x^{4}+x^{3}+x^{2}+x+1\\
\Phi_{6}&=x^{2}-x+1\\
\Phi_{7}&=x^{6}+x^{5}+x^{4}+x^{3}+x^{2}+x+1\\
\Phi_{8}&=x^{4}+1\\
\Phi_{9}&=x^{6}+x^{3}+1\\
\Phi_{10}&=x^{4}-x^{3}+x^{2}-x+1\\
\Phi_{11}&=x^{10}+x^{9}+x^{8}+x^{7}+x^{6}+x^{5}+x^{4}+x^{3}+x^{2}+x+1\\
\Phi_{12}&=x^{4}-x^{2}+1\\
\vdots\\
\Phi_{105}&=x^{48}+x^{47}+x^{46}-x^{43}-x^{42}-2x^{41}-x^{40}-x^{39}+x^{36}+x^{35}+x^{34}+x^{33}\\
&\quad+x^{32}+x^{31}-x^{28}-x^{26}-x^{24}-x^{22}-x^{20}+x^{17}+x^{16}+x^{15}+x^{14}+x^{13}\\
&\quad+x^{12}-x^{9}-x^{8}-2x^{7}-x^{6}-x^{5}+x^{2}+x+1\\
\vdots\\
\Phi_{385}&=x^{240}+x^{239}+x^{238}+x^{237}+x^{236}-x^{233}-x^{232}-x^{231}-x^{230}-2x^{229}\\
&\quad-x^{228}-x^{227}-x^{226}-x^{225}+x^{222}+x^{221}+x^{220}+x^{219}+x^{218}+x^{205}+x^{204}\\
&\quad+x^{203}+x^{202}+x^{201}-x^{198}-x^{197}-x^{196}-x^{195}-2x^{194}-x^{193}-x^{192}-x^{191}\\
&\quad-x^{190}+x^{187}+x^{186}+2x^{185}+2x^{184}+2x^{183}+x^{182}+x^{181}-x^{178}-x^{177}\\
&\quad-x^{176}-x^{175}-2x^{174}-x^{173}-x^{172}-x^{171}+x^{169}+x^{168}+2x^{167}+2x^{166}\\
&\quad+x^{165}+x^{164}+x^{163}-x^{159}-x^{158}-x^{157}-2x^{156}-2x^{155}-x^{154}-x^{153}\\
&\quad-x^{152}+x^{150}+x^{149}+x^{148}+x^{147}+x^{146}+x^{145}+x^{144}-x^{140}-2x^{139}\\
&\quad-x^{138}-x^{137}-x^{136}+x^{134}+x^{133}+2x^{132}+2x^{131}+2x^{130}+2x^{129}+2x^{128}\\
&\quad+x^{127}+x^{126}-x^{124}-2x^{123}-2x^{122}-3x^{121}-3x^{120}-3x^{119}-2x^{118}-2x^{117}\\
&\quad-x^{116}+x^{114}+x^{113}+2x^{112}+2x^{111}+2x^{110}+2x^{109}+2x^{108}+x^{107}+x^{106}\\
&\quad-x^{104}-x^{103}-x^{102}-2x^{101}-x^{100}+x^{96}+x^{95}+x^{94}+x^{93}+x^{92}+x^{91}\\
&\quad+x^{90}-x^{88}-x^{87}-x^{86}-2x^{85}-2x^{84}-x^{83}-x^{82}-x^{81}+x^{77}+x^{76}+x^{75}\\
&\quad+2x^{74}+2x^{73}+x^{72}+x^{71}-x^{69}-x^{68}-x^{67}-2x^{66}-x^{65}-x^{64}-x^{63}-x^{62}\\
&\quad+x^{59}+x^{58}+2x^{57}+2x^{56}+2x^{55}+x^{54}+x^{53}-x^{50}-x^{49}-x^{48}-x^{47}-2x^{46}\\
&\quad-x^{45}-x^{44}-x^{43}-x^{42}+x^{39}+x^{38}+x^{37}+x^{36}+x^{35}+x^{22}+x^{21}+x^{20}\\
&\quad+x^{19}+x^{18}-x^{15}-x^{14}-x^{13}-x^{12}-2x^{11}-x^{10}-x^{9}-x^{8}-x^{7}+x^{4}+x^{3}+x^{2}+x+1\\
\end{align*}

おわりに

  • 係数に初めて2が現れるのは次数105、初めて3が現れるのは次数385であることが分かった。
  • 以下、次数1365で4、次数1785で5、次数2805で6、次数3135で7、次数6545で8と9、と続く。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめに

Qiitaを始めた理由

簡単に言えば、メモ代わり。
メモを書くだけで他の方にも評価されたりするって結構便利じゃないか。(^▽^)/

……すいませんそれだけじゃないです。後輩がブログやってると聞いて、なんか負けたくなかったんです。

主に投稿すること

1. Unityについて
 サークルでUnityを使っていて分からないところが多々ある。(後輩にすら知識で負けた)
 そのたびに調べてたけど、面倒なので自分の使う分だけまとめてみよう!(≧▽≦)
 自分の書いた文の方が、自分で構成を練っている分読みやすいと感じるからね。

2. C#について
 理由は至極単純なもので、UnityでC#を使っているから。
 もしも、明日からC#禁止令なるものが出たら、掌を返して別の言語の話を書きなぐる!( ;∀;) 

3. 競技プログラミングについて
 AtCoderは楽しいぞい!
 C#で競技プログラミングの記事を書いている人は少ないから、需要があまりないのかもしれない。
 関係ねえ!私は書くぞ! だってこれメモ代わりなんだもん。( `ー´)ノ

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

単体テストの作り方

 序文

 C#での単体テストコードの書き方を、体系的に整理したいと思って作成しました。ご指摘あれば遠慮なくお申し付けください。

 開発環境

  • OS: Windows10
  • IDE: Microsoft Visual Studio2019 ver16.5.1
  • フレームワーク: ASP.NET Core 3.1
  • テスト用フレームワーク: MSTest

 手順

 考え方

1. テスト対象のメソッドのうち、検証したい事項を明確化する
2. 実際にテスト対象のメソッドを呼び出して得た結果が予想通りであれば成功、失敗なら成功するまで繰り返す
3. テストコードのメソッドに戻り値や引数は基本的につけない

 書き方

1. 戻り値、引数無しのメソッドを生成する
2. 結果を予想する
3. テスト対象のメソッドを呼び出し、その結果と予想を比較する

 実例

今回テストするのはこちらのCalculationクラスのaddメソッド。

public class Calculation(){
    public static int add(int a, int b){
          return a + b;
    }
}

 テストコードはこちら。

[TestClass]
public class CalculationTest(){
    [TestMethod]
    public static void addTest(){
         int x = 3;
         int y = 5;
     
     //予想している結果
         var expected = 8;
     //実際の結果
         var actual = Calculation.add(x,y);
         Assert.AreEqual(expected, actual);
     }
}

 今回検証したいのは、

  • addメソッドが予想した通りの値を返すのか?

 ということ。つまり、return a + b;の部分。
 これを検証するため、
1. CalculationTestクラスを生成、そしてその中にaddTestメソッドを生成。
2. 予想値と、Calculation.add()を呼び出して得た結果を、それぞれexpectedactualに格納。
3. Assert.AreEqual()でexpectedactualを比較。

 成功すれば完了、失敗なら原因を探ってもう一度テストを実行する。

 補足

 今回はMSTestを使いましたが、NUnitやxUnitでも基本は同じです。ただし、属性名やメソッド名が微妙に違うことに留意してください。

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

AWSアカウント間の認証でスイッチ先のサービスを制御

目的

  • ソースに認証文字列を書かない
  • ローカルにセキュリティキーなどの認証文字列を置かない
  • スイッチ先のRDSに接続したい、S3のデータを取りたい ...

プロフィール準備

ローカルまたは運用サーバにAWS1のUSERのプロフィールを作成
(ECSのようなサーバレスは環境変数にSSMを利用)

STS(Security Token Service)を使用してスイッチ先のクレデンシャル取得

クレデンシャル取得フロー

AWS1(IAM USER) -> スイッチ先:AWS{2-N}(IAM ROLE)

IAM

AWS1 IAM USER

arn:aws:iam::012345678:user/aspnet
ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

スイッチ先 IAM Role

arn:aws:iam::123456789:role/aspnet
信頼関係

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::012345678:user/aspnet",
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

例).Net Application

app.config

<appSettings>
  <add key="AwsRoleArnBaseProfile" value="aspnet"/> ← IAM USER
  <add key="AwsRoleArnAws1" value="arn:aws:iam::123456789:role/aspnet"/> ← スイッチ先Role
  <add key="AwsRoleArnAws2" value="arn:aws:iam::234567891:role/aspnet"/>
  ...
</appSettings>

AmazonConfig.cs

using Amazon;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;

function ... (accountNo スイッチ先){
    var roleArn = $"AwsRoleArnAws{(byte)accountNo}";    //accountNo: 1~N
    var chain = new CredentialProfileStoreChain();
    AWSCredentials awsCredentials;
    var profile =  System.Configuration.ConfigurationManager.AppSettings["AwsRoleArnBaseProfile"];
    if (chain.TryGetAWSCredentials(profile, out awsCredentials))    
    {
        var sts = new  AmazonSecurityTokenServiceClient(awsCredentials);
        var arn =  System.Configuration.ConfigurationManager.AppSettings[roleArn];
        var stsreq = new AssumeRoleRequest
        {
            RoleArn = arn,
            RoleSessionName =  $"{(byte)accountNo}_{region.SystemName}_{DateTime.Now.ToString("yyyyMMddHHmmsssss")}",
            DurationSeconds = 900
        };
        var stsres = sts.AssumeRole(stsreq);
        StsCredentials = stsres.Credentials;    
    }
}

Ec2Service.cs

using Amazon;
using Amazon.EC2;
using Amazon.EC2.Model;

...
aconf = new AmazonConfig(accountNo);
IAmazonEC2 client = new AmazonEC2Client(aconf.StsCredentials,  RegionEndpoint.APNortheast1);
...

参考

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

.Net Core(C#)によるUDP Hole Punching

背景

途中で無くなったり、歯抜けになってもいいけど、素早くデータを送りたい場合に必要なUDPをNAT越しでクライアントに送る場合のお話。

環境

.NetCore3.1

検証環境

クライアント サーバ
Privateアドレス Privateアドレス
Privateアドレス NAT Privateアドレス
Privateアドレス NAT Azure VM(Ubuntu)
Privateアドレス DockerのNAT Docker

UDP Hole Punching

NATをどうしたら外側から超えられるか。
NATはクライアントのアドレスおよびポート番号を変換しサーバに転送するので、サーバからクライアントに向けて(IPアドレス)パケットを送っても到達しません(そもそもクライアントはプライベートIPの場合も)。
そこでネットを漁るとUDP Hole Punchingなるキーワードが。
NATの変換の癖を利用した方法で、一度クライアントからサーバにパケットを送るとNATに変換テーブルが作成され穴が開いた状態になるので、
その穴に向かってパケットを発射するとクライアントに転送されるといった技だそうです。

サンプルについて

ネットを探すといろいろ情報があるけど、サンプルを実行しても動かない。
一番近いサンプルでした。
調査すると、どうやらサーバ側のUDPにソースアドレスが設定されていないため、NATが変換できないことが判明。
簡単なコアサンプルを作成しましたので、ご自身のプログラムに組み込んではいかがでしょうか。
コアサンプルソース

使い方

  1. サーバを起動
  2. クライアントを起動
  3. サーバの時間をクライアントに1秒間隔で送信
  4. クライアントで受信した文字列を表示

設定

  1. サーバは内部で受信するIPアドレスを動的に決めています。うまく行かない場合は
ServerLoop.cs
54:        internal List<IPAddress> IPAddresses

のプロパティを変更してみてください。
1. クライアントは実行時にサーバのIPアドレスをしてくしてください
以上です。
コアサンプルにコメントを書いておいたのでそちらを参照してください。

P2P

うまくクライアントを制御すれば、サーバを返さずP2Pでクライアント同士がおしゃべりできるかも。

最後に

いろいろな環境(NAT)で試したわけではないので、動かない環境もあるやもしれません。

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

C#用にC++のDllを作る方法

僕忘れやすいからメモとして残しておきます

VisualStudio2019で空のプロジェクトの作成を行い以下の二つのファイルを追加し、プロパティでdll向けに設定をして,ビルドを行います。

Test.cpp
#include "Test.h"

//Test用メソッド
int Test(int a, int b){
    return (a+b);
}
Test.h
#pragma once
#define DLLEXPORT __declspec(dllexport)

extern "C" {
    //Test用メソッド
    DLLEXPORT int Test(int a, int b);
}

出力されたdllをC#側で以下のようにして読むと実行できます

Test.cs
using System.Runtime.InteropServices;

[DllImport("Test.dll")]]
private static extern int Test(int a, int b);

void main(){
     Console.WriteLine(Test(1,2)); 
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]コルーチンの中で他のコルーチンが動作完了するまで待つ

yield return コルーチン
コルーチンを動作させ、そのコルーチンが動作完了するまで待つ。

sample
IEnumrator sample() {
   yield return ShowStageTitle();
   yield return CountDown();
   GameStart();
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#の文字列反転操作の速度比較

TL;DR

  • unsafeでのポインタ操作が一番早い
  • 次点でchar配列に変換して前後入れ替える操作が早い

比較方法

文字数が100と10000の文字列をそれぞれ次の6つの方法で反転させ,BenchmarkDotNetを使ってベンチマークを行いました.
- Enumerable.Reverse() + new string
- Enumerable.Reverse() + String.Join()
- String.ToCharArray()
- StringBuilder
- Span
- unsafe

また,使用する文字列は,Web制作小物ツール様のダミーテキストジェネレータで,lorem ipsumの100文字と10000文字で生成されたテキストを使用しました.

実行環境

  • OS: Windows 10.0.18363
  • CPU: AMD Ryzen 5 3600 (6 cores, 12 logical)
  • Microsoft Visual Studio: Community 2019 Version 16.5.0
  • .Net Core: 3.1.2
  • Benchmark: BenchmarkDotNet

実装

Enumerable.Reverse() + new string

Enumerable.Reverse() で反転させたchar型の配列から文字列を生成する方法.

public string UseReverse()
{
    return new string(_sample.Reverse().ToArray());
}

Enumerable.Reverse() + String.Join()

Enumerable.Reverse() で反転させたEnumerable<char>string.Join()で連結する方法.

public string UseReverseAndJoin()
{
    return string.Join("", _sample.Reverse());
}

String.ToCharArray()

String.ToCharArray()char型の配列を生成し,文字列の先頭から順に末尾の文字と入れ替える方法.

public string UseCharArray()
{
    var chars = _sample.ToCharArray();
    for (int i = 0, j = chars.Length - 1; i < j; i++, j--)
    {
        var tmp = chars[i];
        chars[i] = chars[j];
        chars[j] = tmp;
    }
    return new string(chars);
}

StringBuilder

StringBuilderを使って文字列の末尾から先頭まで文字をAppend()する方法.

public string UseStringBuilder()
{
    var builder = new StringBuilder(_sample.Length);
    for (var i = _sample.Length - 1; i >= 0; i--)
    {
        builder.Append(_sample[i]);
    }
    return builder.ToString();
}

Span

Spanでスタックに一時領域を確保し,文字列の先頭と末尾から順に文字を変更する方法.

public string UseSpan()
{
    var normal = _sample.AsSpan();
    Span<char> reversed = stackalloc char[_sample.Length];
    for (int i = 0, j = reversed.Length - 1; i <= j; i++, j--)
    {
        reversed[i] = normal[j];
        reversed[j] = normal[i];
    }
    return reversed.ToString();
}

unsafe

unsafestringの先頭から順に末尾の文字と入れ替える方法.

public unsafe string UseUnsafe()
{
    var reversed = _sample;
    fixed (char* pc = reversed)
    {
        for (int i = 0, j = reversed.Length - 1; i < j; i++, j--)
        {
            var tmp = pc[i];
            pc[i] = pc[j];
            pc[j] = tmp;
        }
    }
    return reversed;
}

プログラム全体

上のまとめただけなので閉じ
ReverseStringSpeedTest.cs
using BenchmarkDotNet.Attributes;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ReverseStringBenchmark
{
    [RankColumn]
    public class ReverseStringSpeedTest
    {
        // 単位に対して値が大きくなってしまったため別々に実行
        // [Params(100, 10000)] public int Length;
        public int Length = 100; // or 10000

        private string _sample;

        [GlobalSetup]
        public void Setup()
        {
            var filePath = $"ReverseStringBenchmark.Resources.sample{Length}.txt";
            using var assemblyStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filePath);
            using var streamReader = new StreamReader(assemblyStream ?? throw new FileNotFoundException());
            _sample = streamReader.ReadToEnd().Trim();
        }

        [Benchmark]
        public string UseReverse()
        {
            return new string(_sample.Reverse().ToArray());
        }

        [Benchmark]
        public string UseReverseAndJoin()
        {
            return string.Join("", _sample.Reverse());
        }

        [Benchmark]
        public string UseCharArray()
        {
            var chars = _sample.ToCharArray();
            for (int i = 0, j = chars.Length - 1; i < j; i++, j--)
            {
                var tmp = chars[i];
                chars[i] = chars[j];
                chars[j] = tmp;
            }
            return new string(chars);
        }

        [Benchmark]
        public string UseStringBuilder()
        {
            var builder = new StringBuilder(_sample.Length);
            for (var i = _sample.Length - 1; i >= 0; i--)
            {
                builder.Append(_sample[i]);
            }
            return builder.ToString();
        }

        [Benchmark]
        public string UseSpan()
        {
            var normal = _sample.AsSpan();
            Span<char> reversed = stackalloc char[_sample.Length];
            for (int i = 0, j = reversed.Length - 1; i <= j; i++, j--)
            {
                reversed[i] = normal[j];
                reversed[j] = normal[i];
            }
            return reversed.ToString();
        }

        [Benchmark]
        public unsafe string UseUnsafe()
        {
            var reversed = _sample;
            fixed (char* pc = reversed)
            {
                for (int i = 0, j = reversed.Length - 1; i < j; i++, j--)
                {
                    var tmp = pc[i];
                    pc[i] = pc[j];
                    pc[j] = tmp;
                }
            }
            return reversed;
        }
    }
}

結果

注意: 100文字と10000文字で単位が異なります.

100文字

Method Mean Error StdDev Rank
UseReverse 996.21 ns 14.508 ns 12.861 ns 5
UseReverseAndJoin 2,148.26 ns 21.109 ns 19.746 ns 6
UseCharArray 85.09 ns 0.491 ns 0.436 ns 2
UseStringBuilder 291.50 ns 2.113 ns 1.765 ns 4
UseSpan 96.39 ns 1.284 ns 1.201 ns 3
UseUnsafe 47.35 ns 0.260 ns 0.230 ns 1

10000文字

Method Mean Error StdDev Rank
UseReverse 77.216 us 0.9029 us 0.8004 us 5
UseReverseAndJoin 199.305 us 2.1672 us 1.8097 us 6
UseCharArray 7.652 us 0.0526 us 0.0492 us 2
UseStringBuilder 27.652 us 0.3975 us 0.3718 us 4
UseSpan 9.123 us 0.1207 us 0.1129 us 3
UseUnsafe 5.145 us 0.0073 us 0.0061 us 1

まとめ

C#において,文字数が100と10000の文字列の反転操作を,6つの方法において比較しました.
結果として,unsafeを使ったポインタでの操作が一番早く,次点としてchar配列の操作が早いという結果になりました.
スタック領域においてメモリを扱えるSpanが早いと思っていましたが,char配列を操作する方法がより早いという結果に驚きました.
しかし,Spanunsafeの理解が乏しいため,今回の実装方法が誤っているかもしれません.

参考

  1. Web制作小物ツール, ダミーテキストジェネレータ
  2. BenchmarkDotNet
  3. Microsoft, Enumerable.Reverse(IEnumerable) Method
  4. Microsoft, String.ToCharArray Method
  5. Microsoft, StringBuilder Class
  6. Microsoft, Span Struct
  7. Microsoft, unsafe (C# Reference)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む