- 投稿日:2020-03-31T23:45:19+09:00
【KPI】C#の基本的な画面の作り方 - 05:登録ボタン押下イベントとメソッド作成 -【C#】
今回は前回と同じように、登録ボタンの押下イベントを作成する。
と同時に、その中で使うメソッドの一部を作成する。同じようにデザインの登録ボタンをダブルクリックして作成。
今回はtry-catch処理を入れておく。まず、登録前に、項目の桁数をチェックしたい。
下記仕様を決める
[コード]:最大桁数6桁の場合のみ登録可能
[名称] :値が入っており、最大桁数20桁(全角1文字は2桁換算)を超えていなければ登録可能。
※現在、[名称]テキストボックスは最大で20文字が入る。理論上は全角20文字で40桁換算となる。この処理を馬鹿正直に書く場合はかなりコードが長くなる。
さらに、これらのチェックは、項目ごとに似通っている。
よって、共通で使用できるメソッド[CheckLength]を作る。
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を返す。
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); } }これで、下記の各画面の状態で登録ボタンを押下した際、直近下側のメッセージが表示されるようになる。
これで事前チェックが完成する。
次回は更に作り込んでいく。
- 投稿日:2020-03-31T23:24:57+09:00
【KPI】C#の基本的な画面の作り方 - 04:終了ボタン押下イベントの作成 -【C#】
次は終了ボタンを押した際のイベントを作成する。
終了ボタンを押した際は、とりあえず画面が終了するものとする。
C#private void BtnEnd_Click(object sender, EventArgs e) { try { // 何か終了処理があればここに } catch (Exception ex) { // エラーメッセージを表示 MessageBox.Show(ex.Message); } // 画面を終了する this.Close(); }※try-catchは今回使用してないので消して構わない。
デバッグ時の画面はこうなる。
終了ボタンを押した際は、画面が終了する。次回は登録ボタンの押下イベント……に使用するチェック処理を作成する。
- 投稿日:2020-03-31T23:15:04+09:00
【KPI】C#の基本的な画面の作り方 - 03:プロパティの設定 -【C#】
前回は、画面にコントロールを大まかに張り付けた。
今回は、張り付けたコントロールのプロパティを設定する。
下記の画像を参照。
プロパティを設定したい項目を選択し、右下のプロパティ項目を設定する。
プロパティの設定
- [終了]ボタン
- (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]を設定
これらを設定できたら大雑把には見た目は完成。
他にもレイアウトを調整して上記の画面となる。次回はボタンイベントを設定する。
- 投稿日:2020-03-31T23:09:53+09:00
[C#]画像に対していろいろ行う
もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4fやりたいこと
WPFの画面表示や画像回りでいろいろやることがあるが、なんだかややこしくて、まとめようと思ってもまとめきれないまま時間が過ぎている。
いきなりまとめるのをちょっとあきらめて、やりたいこと、やったことのレシピ集みたいなのを作って経験値を貯めてから、見えてきたものを後でまとめようと思う。
→主に、System.Drawing.Bitmap
を使って画像をファイルに保存したり、編集したときのことをまずは書く。(あとは、出てきたやつを都度追記していく。)画像ファイルを開いて、好きな文字やら図形を書き込んで、別のファイルに保存する(その1)
- 元画像ファイル(.bmp等)を
System.Drawing.Bitmap
に格納System.Drawing.Graphics
で格納した画像を編集- 出力先ファイルへの
FileStream
を作成System.Drawing.Bitmap
のSave
メソッドで、出力先ファイルへのFileStream
を指定し保存- →これで、出力先ファイルに保存される
サンプル1.csusing (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)
- 元画像ファイル(.bmp等)を
System.Drawing.Bitmap
に格納System.Drawing.Graphics
で格納した画像を編集System.Drawing.Bitmap
のSave
メソッドで、出力先ファイルのパスを指定し保存- →これで、出力先ファイルに保存される
サンプル2.csusing (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.csusing (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.csusing (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で指定してるやつ。
よく出てくるがじつはわかってない。
- 投稿日:2020-03-31T22:57:56+09:00
【KPI】C#の基本的な画面の作り方 - 02:デザインの大まかな指定 -【C#】
前回の続きから。
次はツールボックスより、大雑把に画面に必要な項目を用意する。
今回は画面の仕様として。「コード・名称の項目をデータテーブルに登録する」というシンプルなものを想定。
まずは[登録ボタン][終了ボタン]となる2つのボタンを用意する。
ボタンは[ツールボックス][コモン コントロール][Button]から作成する。
次は[コード][名称]のラベルを用意する。
ラベルは[ツールボックス][コモン コントロール][Label]から作成する。
次は[コード][名称]のテキストボックスを用意する。
テキストボックスは[ツールボックス][コモン コントロール][TextBox]から作成する。
大体このようなデザインを作成できる。
次回は今回作成したコントロールのプロパティを修正していく。(名前や見た目、入力桁数等)
- 投稿日:2020-03-31T22:47:47+09:00
【KPI】C#の基本的な画面の作り方 - 01:プロジェクトの新規作成 -【C#】
- 投稿日:2020-03-31T19:03:53+09:00
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: サーバへの通信が失敗した後に起こる処理を記述する。
終わりに
大雑把な説明しかしていないので、他の記事やサイトも参考にしていくとよいかと思われます。
- 投稿日:2020-03-31T18:53:09+09:00
二重配列 と 配列内の配列
前置き
ある日ある時、パズルゲームを作っている時に二重配列を使いたくなって使いました。
他のある日、また二重配列を使いたくなって使いました。
また別のある日、その二つの二重配列を見返しました。あれぇれぇ~? この二つ種類違うぞぉ? おっかしいなぁ~?( ゚Д゚)
ここから本編
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} };ふむふむ、それならば、ある日ある時のビンゴ配列は、要素数が同じだから後者の配列でもよかった……。
ど、どうせ中身定義しなきゃいけなかったわけだし?ゲーム内ではちゃんと動いたわけだし?過去のことを思っちゃダメ。未来のことも思っちゃダメ。今を精一杯生きようじゃないか。(*´ω`)
- 投稿日:2020-03-31T18:53:09+09:00
二次元配列 と 配列内の配列
前置き
ある日ある時、パズルゲームを作っている時に二重配列を使いたくなって使いました。
他のある日、また二重配列を使いたくなって使いました。
また別のある日、その二つの二重配列を見返しました。あれぇれぇ~? この二つ種類違うぞぉ? おっかしいなぁ~?( ゚Д゚)
ここから本編
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} };ふむふむ、それならば、ある日ある時のビンゴ配列は、要素数が同じだから後者の配列でもよかった……。
ど、どうせ中身定義しなきゃいけなかったわけだし?ゲーム内ではちゃんと動いたわけだし?過去のことを思っちゃダメ。未来のことも思っちゃダメ。今を精一杯生きようじゃないか。(*´ω`)
- 投稿日:2020-03-31T18:23:14+09:00
プログラミング演習を実施できるウェブシステム
はじめに
はじめて 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
にアクセスすると,以下の画像のようなページが表示されます.デモ用コンテンツの準備
ログイン
上部メニューの右端にある Login をクリックし,ログイン画面に移動してください.
初期ユーザーとして,管理者権限を持つ
admin
と,通常権限のtest001
,test002
, ...,test010
が登録されていますが,まずは管理者権限のあるユーザーで設定を行う必要があるので,admin
でログインします.パスワードもadmin
です.ログインに成功すると,ホーム画面に自動的に遷移します.上部メニューに項目が増えているはずです.
Administration
は管理ページへのリンクで,admin
は自分のページへのリンクです.admin
をクリックしてください.左側に自分の情報が表示されています. タグ型のラベルはそれぞれユーザーのもつ権限を表しています.
Senior
権限をもつユーザーは,講義や,サンドボックス環境を新たに作成することができます.システムのセキュリティ上重要な部分に関わるユーザーですので,管理者が信用できるユーザーのみに付与してください.
Admin
権限はSenior
と同等の権限に加え,ユーザーの登録・編集・削除を行うことができるほか,システム全体の設定を行うことができます.サンドボックスの作成
まず,
Sandbox Settings
でプログラムを安全に実行するためのサンドボックス環境を作成します.
+
のアイコンをクリックし,表示されたダイアログに必要な情報を入力します.ここでサンドボックス環境に講義で使用する処理系等をインストールするコマンドを記述するのですが,それを何もない状態から書くのは面倒です.
本システムでは Ruby と Python のインストールコマンドのテンプレートを用意しています(このテンプレートは管理ページで任意に作成することができます).
デモ用コンテンツを動作させるには, Ruby の環境が必要ですので,Use package?
より,Ruby 2.7.0
を選択してください.Name
,Description
,Setup Commands
欄が自動的に埋められます.問題が無ければ,
OK
をクリックしてください.環境とRubyのインストールが始まり,状況がリアルタイムで表示されます.この処理には非常に時間がかかります.
Spinner が回転している間は処理中です.このページから移動したりブラウザを閉じずに,処理が終了するまで待機していてください.上の画像のような画面になればサンドボックスの構築は完了です.
Command...
のところに任意のコマンドを入力して Enterキーを押下することで,実際にその環境でコマンドを走らせることができます.このとき,コマンドは root ユーザーで実行されますので,追加でセットアップしたいものがあればここで行うことができます.講義の作成
次に,
Lecture Settings
1で講義を作成します.Lecture Settings
の+
アイコンをクリックし,表示されたダイアログにて以下のように入力してください.
Name
には半角英数で講義の識別名を入力します.Subject
には講義名を自由に入力します.Description
にはメモを任意で書いておくことが出来ます.
Teachers
およびStudents
には,この講義に割り当てる教員および学生のアカウント名をスペースまたは改行区切りで記述します.いまはひとまずTeachers
にadmin
が含まれているだけで充分です.
Empty Repositories
のチェックを忘れずにつけておいてください.以上の手続きで,画面が以下のようになっているはずです:
コンテンツのアップロード
Lecture Settings
のdemo
のリンクをクリックし, デモ講義の管理ページへ移動してください.本システムでは,講義コンテンツを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 masterBasic認証では,アカウント,パスワードともに
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 することで内容を変更できます.講義サンドボックスの作成
最後に,少し下にスクロールした先にある,
Sandbox Settings
より,講義にサンドボックスを登録します.
+
アイコンをクリックし,Name
にruby
と入力し,Clone From
でruby
を選択し,OK
をクリックしてください.しばらく待つと,以下の画面のように
ruby
が追加されます.この操作は,事前に作成しておいた Ruby のサンドボックスをこの講義用に複製する手続きです.事前に作成するサンドボックスと区別するため,講義サンドボックスと呼んでいます.講義サンドボックスではこの講義に割り当てられた教員・学生が,実際のユーザーとして登録されています.具体的には,
/etc/passwd
,/etc/group
にユーザーの情報が書き加えられているとともに,各ユーザーのホームがサンドボックス外にあるユーザーのためのディレクトリにマウントされています.その他は複製時の元のサンドボックスと完全に同一の状態です.なぜサンドボックスと講義サンドボックスに分けて管理しているのかというと,複数の講義を管理する際に便利だからです.また,今後,同様にサンドボックスを用いる講義とは異なる機能を実装する予定で,それを見越した仕様になっています.
以上で準備は完了です.
デモ用コンテンツの確認
ページ上部の
master
をクリックすることで,コンテンツページに移動することができます3.本システムの講義で扱う課題のことをアクティビティと呼びます.
アクティビティには,プログラムのコードの記述,単一・複数行のテキスト入力,ファイルのアップロード,フォームをサポートしており,デモ用コンテンツとしてそれぞれのサンプルを用意しています.また,これらのアクティビティは組み合わせることもでき,例えば,ファイルのアップロードとフォームへの入力の両方が必要なアクティビティなども作成可能です.各アクティビティでは,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を実行して下さい.
おわりに
奇妙に思える仕様があると思いますが,元々,自分が使いやすければ良い,という考えで仕様を練っておりましたのでご容赦ください.
近日中にコンテンツの作り方やその他の機能について投稿する予定です.
- 投稿日:2020-03-31T18:05:54+09:00
円分多項式の係数の計算
はじめに
- 円分多項式
- 次数が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、と続く。
- 投稿日:2020-03-31T17:46:41+09:00
はじめに
Qiitaを始めた理由
簡単に言えば、メモ代わり。
メモを書くだけで他の方にも評価されたりするって結構便利じゃないか。(^▽^)/……すいませんそれだけじゃないです。後輩がブログやってると聞いて、なんか負けたくなかったんです。
主に投稿すること
1. Unityについて
サークルでUnityを使っていて分からないところが多々ある。(後輩にすら知識で負けた)
そのたびに調べてたけど、面倒なので自分の使う分だけまとめてみよう!(≧▽≦)
自分の書いた文の方が、自分で構成を練っている分読みやすいと感じるからね。2. C#について
理由は至極単純なもので、UnityでC#を使っているから。
もしも、明日からC#禁止令なるものが出たら、掌を返して別の言語の話を書きなぐる!( ;∀;)3. 競技プログラミングについて
AtCoderは楽しいぞい!
C#で競技プログラミングの記事を書いている人は少ないから、需要があまりないのかもしれない。
関係ねえ!私は書くぞ! だってこれメモ代わりなんだもん。( `ー´)ノ
- 投稿日:2020-03-31T16:54:42+09:00
単体テストの作り方
序文
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()を呼び出して得た結果を、それぞれexpected
とactual
に格納。
3. Assert.AreEqual()でexpected
とactual
を比較。成功すれば完了、失敗なら原因を探ってもう一度テストを実行する。
補足
今回はMSTestを使いましたが、NUnitやxUnitでも基本は同じです。ただし、属性名やメソッド名が微妙に違うことに留意してください。
- 投稿日:2020-03-31T16:51:47+09:00
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); ...参考
- https://docs.aws.amazon.com/ja_jp/sdk-for-net/v3/developer-guide/net-dg-config-creds.html
https://dev.classmethod.jp/articles/sts-temporality-credential/
名前付きプロファイル
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-profiles.html
https://docs.aws.amazon.com/ja_jp/powershell/latest/userguide/specifying-your-aws-credentials.html
(Windowsはオプションでセキュリティキーが暗号化され保存される)
- 投稿日:2020-03-31T16:21:41+09:00
.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秒間隔で送信
- クライアントで受信した文字列を表示
設定
- サーバは内部で受信するIPアドレスを動的に決めています。うまく行かない場合は
ServerLoop.cs54: internal List<IPAddress> IPAddressesのプロパティを変更してみてください。
1. クライアントは実行時にサーバのIPアドレスをしてくしてください
以上です。
コアサンプルにコメントを書いておいたのでそちらを参照してください。P2P
うまくクライアントを制御すれば、サーバを返さずP2Pでクライアント同士がおしゃべりできるかも。
最後に
いろいろな環境(NAT)で試したわけではないので、動かない環境もあるやもしれません。
- 投稿日:2020-03-31T14:39:11+09:00
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.csusing System.Runtime.InteropServices; [DllImport("Test.dll")]] private static extern int Test(int a, int b); void main(){ Console.WriteLine(Test(1,2)); }
- 投稿日:2020-03-31T13:17:08+09:00
[Unity]コルーチンの中で他のコルーチンが動作完了するまで待つ
yield return コルーチン
コルーチンを動作させ、そのコルーチンが動作完了するまで待つ。例
sampleIEnumrator sample() { yield return ShowStageTitle(); yield return CountDown(); GameStart(); }
- 投稿日:2020-03-31T03:40:52+09:00
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
unsafeで
string
の先頭から順に末尾の文字と入れ替える方法.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.csusing 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
配列を操作する方法がより早いという結果に驚きました.
しかし,Span
とunsafe
の理解が乏しいため,今回の実装方法が誤っているかもしれません.参考