20200406のC#に関する記事は7件です。

つくるオーオース Authorization Code Grant編 その1

今回からはOAuth徹底入門を参考に、C#をつかってオーオースを学習する方法を書いてみます。
https://www.amazon.co.jp/dp/4798159298/

Client Credentials Grantと、Resource Owner Password Credentials Grantと、
https://qiita.com/namikitakeo/items/38be899790cb27a323df

Implicit Grantは再現ずみなので、
https://qiita.com/namikitakeo/items/c4f1a23ed7609c65342e

いよいよAuthorization Code Grantを再現してみます。
https://qiita.com/namikitakeo/items/b0b6c32f2289267beb05

まずはCodeモデルを追加します。
https://qiita.com/namikitakeo/items/0de598b8e43eb5b1ff94

Models/Tables.cs
(省略)
  public class Code
  {
    [DisplayName("code")]
    public string Id { get; set; }

    [DisplayName("user_id")]
    public string UserId { get; set; }

    [DisplayName("query")]
    public string Query { get; set; }

    [DisplayName("iat")]
    public DateTime Iat { get; set; }
  }
}

つぎにTokenコントローラーを修正します。
http://localhost:5000/op/token

Controllers/TokenController.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using myop.Models;

namespace myop.Controllers
{
    public class AccessToken
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public string token_type { get; set; }
        public string scope { get; set; }
    }

    [Route("op/[controller]")]
    [ApiController]
    public class TokenController : ControllerBase
    {
        private readonly myopContext _context;
        string CLIENT_ID;
        string CLIENT_SECRET;
        string GRANT_TYPE;
        string SCOPE;
        string USERNAME;
        string PASSWORD;
        string CODE;

        public TokenController(myopContext context)
        {
            _context = context;
        }

        // POST: op/token
        [HttpPost]
        public async Task<ActionResult<AccessToken>> doPost()
        {
            string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
            string[] p =  body.Split('&');
            for (int i=0; i<p.Length; i++){
                string[] values =  p[i].Split('=');
                switch(values[0])
                {
                    case "client_id":CLIENT_ID=values[1];break;
                    case "client_secret":CLIENT_SECRET=values[1];break;
                    case "grant_type":GRANT_TYPE=values[1];break;
                    case "scope":SCOPE=values[1];break;
                    case "username":USERNAME=values[1];break;
                    case "password":PASSWORD=values[1];break;
                    case "code":CODE=values[1];break;
                }
            }
            var client = await _context.Clients.FindAsync(CLIENT_ID);
            if (client == null) {
                return null;
            }
            if (client.GrantType != GRANT_TYPE) {
                return null;
            }
            if (client.GrantType == "client_credentials") USERNAME="admin";
            if (client.GrantType == "password") {
                var user = await _context.Users.FindAsync(USERNAME);
                if (user == null ) return null;
                if (user.Password != PASSWORD) return null;
            }
            if (client.GrantType == "authorization_code") {
                var code = await _context.Codes.FindAsync(CODE);
                if (code == null) {
                    return null;
                }
                USERNAME=code.UserId;
                _context.Codes.Remove(code);
                await _context.SaveChangesAsync();
            }
            string t="openid";
            if (SCOPE != null) {
                string[] s =  SCOPE.Split(' ');
                for (int j=0; j<s.Length; j++){
                    if (s[j]!="openid" && client.AllowedScope.Contains(s[j])) t=t+" "+s[j];
                }
            }
            SCOPE=t;
            if (client.AccessType == "confidential") {
                if (client.ClientSecret != CLIENT_SECRET) return null;
            } else if (client.AccessType == "public") {
                if (client.GrantType == "client_credentials") return null;
                if (CLIENT_SECRET != null) return null;
            } else {
                return null;
            }
            var token = await _context.Tokens.FindAsync(USERNAME);
            if (token != null) {
                _context.Tokens.Remove(token);
                await _context.SaveChangesAsync();
            }
            string random = Guid.NewGuid().ToString("N").ToUpper();
            token=new Token {Id = USERNAME, AccessToken = random, ExpiresIn=60, TokenType="bearer", Scope = SCOPE, Iat=DateTime.Now};
            _context.Add(token);
            await _context.SaveChangesAsync();
            AccessToken access_token=new AccessToken {access_token = random, expires_in=60, token_type="bearer", scope = SCOPE};
            return access_token;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でNextPermutationを実装して順列全列挙を行う【競技プログラミング特化ver】

はじめに

みなさん、C#で競技プログラミング、してますか?
なかなかつらいですよね、実行速度も速くない、コレクションもライブラリも貧弱、人口が少ない、などなど、C#erにとってはなかなか厳しい環境ですが、めげずにやっていきましょう。

今回は、競技プログラミング用にC#でNextPermutationを実装して順列全列挙を可能にしてみましたので、忘備録として記事に残しておこうと思います。
汎用的な順列全列挙のC#での実装は以下のサイトでまとめられていましたので、早さが必要でない場合はこちらでもいいかもしれません。
Web備忘録-[C#] 順列を求める next_permutation() 代わりを実装する方法

C++のnext_permutationについて

引数として渡した配列を基準として、辞書順によりその次の順列を生成するものです。
引数の値が生成された順列に変更され、戻り値は順列が生成できたか否かをbool値で返します。すなわち、引数として渡した順列が辞書順で最大であるときのみfalseを返します。(詳しくはリファレンスを参照してください。)
つまり、これをwhile文でfalseを返してやるまで回してやることで、ある順列以降の順列をすべて列挙することができます。

next_permutationの内部実装

詳しい話はこちらの記事で解説されています。
要点をまとめると、ある順列を表す配列arrayを引数として受け取った際に

array[i]<array[i+1]を満たす最大のiarray[i]<array[j]を満たす最大のjを求める
・array[i]とarray[j]をswap
・arrayのi番目以降をreverse

という手順により、next_permutation、すなわち次の順列を生成することができます。
条件を満たすiが存在しなかった場合、与えられた順列は降順となっており辞書順で最大となるので、falseを返します

これだけです。けっこうシンプルですね。実装してみましょう。

C#での実装

NextPermutationメソッドの実装

NextPermutation.cs
using System;

class hoge
{
    bool NextPermutation(int[] array)
    {
        var size = array.Length;
        var ok = false;

        //array[i]<array[i+1]を満たす最大のiを求める
        int i = size - 2;
        for (; 0 <= i; i--)
        {
            if (array[i] < array[i + 1])
            {
                ok = true;
                break;
            }
        }

        //全ての要素が降順の場合、NextPermutationは存在しない
        if (ok == false) return false;

        //array[i]<array[j]を満たす最大のjを求める
        int j = size - 1;
        for (; ; j--)
        {
            if (array[i] < array[j]) break;
        }

        //iとjの要素をswapする
        var tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;

        //i以降の要素を反転させる
        Array.Reverse(array, i + 1, size - (i + 1));

        return true;
    }
}

こんな感じです。要素の型はint型に限定しています。理由は後ほど。
わりと読みやすさを重視したのとアルゴリズムの要点だけをコードに落とし込んだので、C++のそれと同じ実装になっているかはわかりませんが。

NextPermutationメソッドを用いた順列全列挙

今回の本題です。要素数Nが与えられたときに0からN-1までで構成される順列をすべて列挙するメソッドと、辞書順が引数以上の順列すべてを列挙するメソッドです。引数によって動作が異なります。順列全探索を使って問題を解く際に使用することを想定しています。

GetAllPermutation.cs
using System;
using System.Collections.Generic;
using System.Linq;

class hoge
{
    //要素数Nを引数として渡し、0~N-1でできた順列をすべて返す
    static List<int[]> GetAllPermutation(int N)
    {
        var res = new List<int[]>();
        var array = Enumerable.Range(0, N).ToArray();
        do
        {
            var copy = new int[N];
            array.CopyTo(copy, 0);
            res.Add(copy);
        } while (NextPermutation(array));

        return res;
    }

    //配列arrayを引数として渡し,辞書順がarray以上の順列をすべて返す
    static List<int[]> GetAllPermutation(int[] array)
    {
        var size = array.Length;
        var res = new List<int[]>();
        do
        {
            var copy = new int[size];
            array.CopyTo(copy, 0);
            res.Add(copy);
        } while (NextPermutation(array));

        return res;
    }
}

C#の配列は参照渡しのため、コピーしたものをリストに追加します(ToArray()するよりコピーの方が速いです。)

Listで返すのでなく、Enumrabale型にしてyield returnすると、メモリ効率がよくなるので、お好みで。個人的には競プロで求められる範囲内ならListにしちゃっても困ることはないと思いますが、こういう考え方はエンジニアとしてはあんまりよろしくないんでしょうかね……。

実行速度

手元で計測してみたところ、N=9のとき90ms ,N=10のとき1000ms,N=11のとき12.5秒ほどでした。コンテストで使用するとしたらN<=10のときに限られそうですね。
C++はN=12でもぎりぎり間に合うらしく、やはり言語間の速度の壁を感じます。しかたないですね。

補足説明とか

NextPermutationとGetAllPermutationを分離したのは、分かりやすさのためもあったんですが、単純にNextPermutation単体でも使うことがあるんじゃないかなーと思ったからです。本当にそんな機会があるのかは知りませんが。NextPermutationに相当するものをGetAllPermutation内部でwhile文で実装したものも試しに作ってみましたが、時間にして3%くらいしか変わらなかったのでそんなに気にしなくていいのかな、と思います。

NextPermutationの引数をIComparableを継承するジェネリック型にすればlong型やdouble型、自作クラスなどでも使用可能になるのですが、試しに実装してみたところ実行にかかる時間が1.6倍程度になってしまいました。
おそらくCompareTo()の呼び出しが大量に行われているためだと思われます。なので今回はint型限定として、実行速度を求めました。double型やlong型で使いたくなることそんなにないだろうし……。
以前Dinic法をC#で実装したときにも書いたんですが、数値型であることを表すインターフェースが欲しいですね。

double型やlong型で使用したい場合は、型の部分を書き直すのもありなんですが、Select()を使うことで各順列を写像してやるという手もあります。
たとえばdouble型の配列dを並び替えて順列全列挙しようと思った場合

Array.Sort(d);
double[][] dperm = GetAllPermutation(d.length)
                  .Select(p => p.Select(s => d[s])
                                .ToArray())
                  .ToArray();

とすることで、元の要素を維持した順列を生成することができます。ただし、この方法はLINQを用いて全ての順列に対して再度アクセスしているため、二度手間となってしまいます。要素数がN<=9なら問題となることはないと思いますが、N=10のときは3秒近くかかってしまうので注意が必要です。

最後に

説明は以上となりますが、わかりにくい部分や間違い等ありましたらコメントかTitterでお知らせください。また、「俺のコードの方が速いんだが?」という方がいらっしゃいましたら是非ご教示ください、参考にさせていただきます。

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

C#で電子署名を実装する

はじめに

opensslのdgstコマンドをC#で実装しようとしてハマったので、やり方を残しておこうと思います。

前提条件

自分で作成した証明局が発行したクライアント証明書を使用しています。
以下の環境で動作確認を行いました。
Windows OS用のOpenSSL openssl 1.1.0k
.Net Framework 4.6

opensslコマンドで電子署名

まず、opensslコマンドで電子署名の動作を確認します。
opensslでは、電子署名にopenssl dgstコマンドを使います。
以下のコマンドで、target.txtに秘密鍵:private-key.pemで署名した結果をsignatureに出力します。

署名
openssl dgst -sha256 -sign private-key.pem -out signature target.txt 

以下のコマンドで、公開鍵:public-key.pemと署名: signatureを使用して、target.txtを検証します。

検証
openssl dgst -verify public-key.pem -signature signature target.txt

C#で電子署名

C#では、RSACryptoServiceProviderのSignDataとVerifyDataメソッドを使います。
byteDataは署名を行うデータ、fileはpfxファイル名、pfxFilePasswardはpfxファイルのパスワードです。
ただし、以下の署名コードは、例外が発生して動作しません。

署名
//pfxファイルを読み込み
var cert = new X509Certificate2(file, pfxFilePassward);
//秘密鍵の取り出し
var rsa = (RSACryptoServiceProvider)cert.PrivateKey;
//署名実行
//!!! 例外発生 !!!!
var signature = rsa.SignData(byteData, HashAlgorithm.Create("SHA256"));
検証
//crtファイルを読み込み
var cert = new X509Certificate2(file);
//公開鍵の取り出し
var rsa = (RSACryptoServiceProvider)cert.PublicKey.Key;
// 検証実行
var result = rsa.VerifyData(byteData, HashAlgorithm.Create("SHA256"), signature);

署名の処理を変更

.Net Frameworkの問題で上記のコードではSHA256のアルゴリズムが使用できないようです。

以下の記事を参考にコードを変更しました。

Using certificate from store for RSACryptoServiceProvider

署名
//pfxファイルを読み込み
var cert = new X509Certificate2(file, pfxFilePassward);
//秘密鍵の取り出し
var rsa = (RSACryptoServiceProvider)cert.PrivateKey;
//以下の処理を行わないとSHA256アルゴリズムが使用できない
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName);
var privKey = new RSACryptoServiceProvider(cspparams);
// 署名実行
var signature = privKey.SignData(byteData, HashAlgorithm.Create("SHA256"));

最後に

opensslコマンドとC#が出力した電子署名をバイナリ比較して確認しましたが同じものでした。

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

Visual StudioとC#を初めて使ってブロック崩しゲームを作ってみた②

u9N4og9PN6gH1A7Lxa6o1586313765-1586313833.gif
前回の続きになります。

ホーム画面からプレイ画面をダイアログ表示し
ゲームを失敗したらリザルト画面へ遷移するようにします。
丁寧に解説している故に記事が長くなってしまうのでリザルト画面は第3回に投稿します。

ダイアログ表示だけを学びたい人は
前回の参照サイトをプロジェクト(Form1)の作成まで実施してください。

前回:参照
ブロック崩しゲームを始めから作りたい人は→:Visual StudioとC#を初めて使ってブロック崩しゲームを作ってみた①

まずはForm1の画面に別ウィンドウを追加しようと思います。

画面追加

画面右側の「ソリューションエクスプローラー」の「プロジェクト名」を右クリック
「追加」→「新しい項目の追加」を選択。
Windowsフォームを選択し、名前は「Form2.cs」で追加。
image.png
image.png
すると、Form2が作成されます。
これで表示される画面の準備はOK!

次はボタンを設置します。
Form1からForm2に遷移させたいのでForm1の画面にボタンを設置します。
Form1のデザイン画面(Form1.cs)を表示し、「表示(V)」→「ツールボックス」→「コモンコントロール」
→「Button」をクリック。
image.png
画面にマウスホールドして適当な四角形を作ったらボタンフォームが表示されます。
(「Button」クリック後画面任意箇所クリックしても設置できます。)

ボタンを右クリック→「プロパティ」でプロパティが右下に表示されるので、
ここでボタンに表示されるテキストを設定します。
image.png

ボタンをクリック時に動作させたいので
⚡マークのイベントをクリック→アクション欄にある「Click」で処理を行うメソッド名を決めます。
Form2をホーム画面にするので、ここでは「home_Click」と命名します。
image.png

すると、Form1.csにメソッドが自動生成されます。
image.png

生成されたメソッドにForm2を表示する処理を記述します。

Form1.cs
private void home_Click(object sender, EventArgs e)
        {
            // Form2のインスタンスを生成
            Form2 form2 = new Form2();
            // form2を表示
            form2.Show();
        }

実行(F5)してみてください。
ボタンを押すと別ウィンドウが表示されるはずです。
image.png
※実はここでパドルの操作が効かなくなっています。
後のホーム画面作成時に原因・対策を追記しましたのでこのまま進めてください。

ダイアログ表示

一応完成しましたが、このままではボタンを押下する毎にウィンドウが表示されます。
開いた画面を閉じないと元の画面を触れないのが一般的だと思うのでダイアログ表示にします。

form2.Show() → form2.ShowDialog()に変更するだけです。
初心者向け支記事なので文字補完を使ってみましょう。
「Show」にカーソルを合わせ Ctrlとスペースキーを同時に押下すると候補が表示されるので、
ここから「ShowDialog」を選択します。
image.png

実行してみてください。
Form2を閉じないとボタンをクリックできなくなっています。

ホーム画面作成

Form2の画面をスタート画面にします。

Form1のときと同じようにForm2にボタンを作成します。
image.png
ボタン名はスタート。
メソッド名はstart_Clickにします。

メソッドが自動生成されたはずなので、
Form1へ遷移する処理を記述します。

Form2.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Breakout
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void start_Click(object sender, EventArgs e)
        {
            // 以下の処理を追記する!!

            // Form1のインスタンスを生成 
            Form1 form1 = new Form1();
            // form1を表示
            form1.ShowDialog();
        }
    }
}

実行すると遷移できましたね。
CriILA193PpPJSG6Q5v01586149345-1586149356.gif

初期起動時にスタート画面を表示するようにします。
ソリューションエクスプローラーから「Program.cs」を開きます。
ここで最初に開く画面を設定しています。
Form1から2へ変更します。
Application.Run(new Form1()); → Application.Run(new Form2());
実行してみてください。
ホーム画面が最初に表示されたらOKです。

※追記

試しにゲームをしてみたらパドルが動きませんでした。
キーイベント処理がある画面でボタンを設置すると
キーイベントが発生しなくなるようです。
home_Clickとボタンを削除します。
image.png
image.png

ボール速度変更ボタン

Form2デザインから「表示」→「ツールボックス」
→「ComboBox」と「Label」を選択して画面に設置します。
image.png

各テキストは以下のように設定。
image.png

コンボボックスにリストを作成します。
作成したコンボボックスを右クリックしプロパティを開いて
「item」の「コレクション」をクリックすると
右端に「・・・」メニューが現れるのでクリック。
エディターが開くので、1行毎に選択リスト用テキストを入力します。
「Easy」「Normal」「Hard」「Expert」
image.png
image.png

コンボボックスにイベントを設定します。
以下のイベントはリスト選択する毎に処理が流れて値を取得してくれます。
image.png

ホーム画面(Form2)のコードは以下になります。

Form2.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;

namespace Breakout
{
    public partial class Form2 : Form
    {
        //ボールの速度(x, y)
        public static int x { get; set; }
        public static int y { get; set; }


        public Form2()
        {
            InitializeComponent();
        }

        private void start_Click(object sender, EventArgs e) //スタートボタン
        {
            mode_Select(sender, e);

            // Form1のインスタンスを生成
            Form1 form1 = new Form1();
            // form1を表示
            form1.ShowDialog();

        }

        private void mode_Select(object sender, EventArgs e) //モードセレクト
        {
            int selectedMode = comboBox1.SelectedIndex;
            switch (selectedMode)
            {
                case 0:
                    //Easy
                    x = -2;
                    y = -4;
                    break;
                case 1:
                    //Normal
                    x = -3;
                    y = -6;
                    break;
                case 2:
                    //Hard
                    x = -5;
                    y = -10;
                    break;
                case 3:
                    //Expert
                    x = -8;
                    y = -16;
                    break;
                default:
                    //未選択時はNormal
                    x = -3;
                    y = -6;
                    break;
            }
        }
    }
}

解説をします。
選択したボール速度を次のForm1画面へ渡すため、
x軸をx、y軸をyとします。
C#にグローバル変数はないので、どうやってこのxy値を渡すかは以下記事参考にしました。
  C#にグローバル変数がない!対応策
comboBox1.SelectedIndexで要素を取得できます。
中身のテキストを抜き出すコードもあるようです。
今回は要素だけを抜き出し、どのモードを選んだのかswich文で分岐させます。
後はスタートボタンクリック時の処理を書いて終わりです。

Form1画面でxyを受け取れるようにします。
1行変更するだけで簡単です。
this.ballSpeed = new Vector(-4, -8) → this.ballSpeed = new Vector(Form2.x, Form2.y)
form2.xのようにインスタンス参照ではなく、
Form2.xであることに注意。詳細は以下「グローバル変数がない!対応策」記事を読んでください。

モードを変更して実行してみてください。
Expertだと早くて、Easyだと遅ければOKです!

次はセキュリティ面です。

モード選択時にテキストを書き換えられるのでこれを不可にします。

コンボボックスを入力不可にするにはプロパティから
DropDownstyleの「DropDown」を「DropDownList」へ変更するだけです。

(プログラムから設定する場合は以下のようにします。
comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;)
image.png

するとグレーアウトし入力不可になっています。
デフォルト(Normal)とボックス内に書いていましたが消えてしまったので
ラベルを「Modo:デフォルト(Normal)」へ書き換えます。
image.png
image.png
image.png

これでホーム画面は完成です!

感想

簡単でしたね!
かなりわかりやすかったのでは?ww
丁寧すぎたかも・・・それはそれでヨシ!
ツールボックスの位置とか
プロパティの使い方に慣れてきました!
にしても未だにタスクを振られていない・・
引き続きゲーム作っていくかあ(仕事中)

次はリザルト画面の作成です!↓
Visual StudioとC#を初めて使ってブロック崩しゲームを作ってみた③

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

C# WinFormsでドッキングウィンドウを使う

環境

  • Windows 10 Pro
  • Microsoft Visual Studio 2019
  • .Net Freamwork 4.5

必要なもの

  • DockPanel Suite(導入の仕方については後述)

DockPanel Suiteの導入

  1. Visual Studioのメニューの、プロジェクト(P) > NuGetパッケージの管理(N)をクリック

  2. NuGetパッケージマネージャーを開いたら、「dockpanelsuite」を検索する。
    NuGet1.PNG

  3. 検索結果に出てきた DockPanelSuite をインストールする。

早速使ってみる

1.Visual Studioのメニューの、プロジェクト(P) > 新しい項目の追加(W)...をクリック
2.「新しい項目の追加」を開いたら、左の インストール済み > Visual C# アイテム > Windows Forms を選択する。
3.継承フォーム(Windows フォーム)を選択し、追加(A)をクリックする。
4.「継承ピッカー」が開いたら、「参照(B)...」をクリックし、「WeifenLuo.WinFormsUI.Docking.dll」を選択する。

注意: このサンプルでは、継承フォーム「Window1」「Window2」「Window3」「Window4」を作成しています。

コード:

MainForm.cs
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;

namespace DockingExample
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            dockPanel1.DocumentStyle = DocumentStyle.DockingWindow;

            Window1 window1 = new Window1();
            window1.Show(dockPanel1, DockState.DockRight);

            Window2 window2 = new Window2();
            window2.Show(dockPanel1, DockState.Document);

            Window3 window3 = new Window3();
            window3.Show(dockPanel1, DockState.DockLeft);

            Window4 window4 = new Window4();
            window4.Show(dockPanel1, DockState.DockLeft);
        }
    }
}
Window1.cs
namespace DockingExample
{
    public partial class Window1 : WeifenLuo.WinFormsUI.Docking.DockContent
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}
Window2.cs
namespace DockingExample
{
    public partial class Window2 : WeifenLuo.WinFormsUI.Docking.DockContent
    {
        public Window2()
        {
            InitializeComponent();
        }
    }
}
Window3.cs
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace DockingExample
{
    public partial class Window3 : WeifenLuo.WinFormsUI.Docking.DockContent
    {
        public Window3()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 1; i <= 100; i++)
            {
                progressBar1.Value = i;
                System.Threading.Thread.Sleep(80);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 50;
            progressBar1.MarqueeAnimationSpeed = 40;
            progressBar1.Style = ProgressBarStyle.Marquee;
        }

        private void button3_Click(object sender, EventArgs e)
        {
            progressBar1.Value = 0;
            progressBar1.Style = ProgressBarStyle.Continuous;
        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            progressBar2.Value = trackBar1.Value;
        }
    }
}
Window4.cs
namespace DockingExample
{
    public partial class Window4 : WeifenLuo.WinFormsUI.Docking.DockContent
    {
        public Window4()
        {
            InitializeComponent();
        }
    }
}

結果

ScreenShot1.PNG

サンプルコードのダウンロード :
http://kgf086.bake-neko.net/Data/DockingExample.zip

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

AtCoderの言語アップデートまとめ(C#)

AtCoderの言語アップデート

ということで使用できる言語がアップデートされるそうですので、C#での変化をまとめておきます。

環境

旧環境

環境 バージョン
Mono 4.6.2.0 C# 6

新環境

環境 バージョン 備考
.NET Core 3.1.201 C# 8.0 .NET Core
Mono (mcs) 6.8.0.105 C# 7.2 旧環境のバージョン版
Mono (csc) 3.5.0 C# 7.3 Roslynコンパイラ

旧環境のバージョンアップに相当するのは、「Mono (mcs) 6.8.0.105」です。
「Mono (csc) 3.5.0」はMicrosoft製のRoslynコンパイラを使用します。

「.NET Core 3.1.201」はMicrosoft純正の環境です。

環境ごとの比較

Monoと.NET Coreは全く別の実行環境なので動作に違いが出ることがあります。

また、mcsとcscでもコンパイラの最適化の度合いが異なることもありえます。

Hello World

コードテストでHello Worldを実行してみます。

Hello World
using System;

class Program
{
  static void Main()
  {
    Console.WriteLine("hello world!");
  }
}

環境 実行時間
.NET Core 3.1.201 60 ms
Mono (mcs) 6.8.0.105 23 ms
Mono (csc) 3.5.0 22 ms

Hello Worldにかかる時間は.NET Coreが一番長くなりました。

実際に出力にかかる時間は0msとみなせるので、起動にかかる時間がそれぞれ60msと23msといえます。

D Calculating GCD

D問題を解いてみます。

コード
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var line = Console.ReadLine().Split(' ');
        var N = int.Parse(line[0]);
        var Q = int.Parse(line[1]);
        var arr = Console.ReadLine().Split(' ').Select(str => int.Parse(str)).ToArray();
        var ss = Console.ReadLine().Split(' ').Select(str => int.Parse(str)).ToArray();
        var gcds = new int[N];
        gcds[0] = arr[0];
        for (int i = 1; i < gcds.Length; i++)
            gcds[i] = Gcd(gcds[i - 1], arr[i]);

        var res = new int[Q];
        for (int i = 0; i < Q; i++)
        {
            var s = ss[i];
            var last = Gcd(gcds[gcds.Length - 1], s);
            if (last != 1)
                res[i] = last;
            else
            {
                int l = 0;
                int r = gcds.Length - 1;
                while (l < r)
                {
                    var m = (l + r) >> 1;
                    if (Gcd(gcds[m], s) != 1) l = m + 1;
                    else r = m;
                }
                res[i] = l + 1;
            }
        }

        foreach (var item in res)
        {
            Console.WriteLine(item);
        }
    }
    static int Gcd(int a, int b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b));
}

実行時間は

環境 実行時間
.NET Core 3.1.201 439 ms
Mono (mcs) 6.8.0.105 566 ms
Mono (csc) 3.5.0 576 ms

となりました。

起動は遅いもののループの回数が大きくなると.NET Coreが高速になることが多いです。

以降は言語バージョンも最新の.NET Core環境について記載します。

言語アップデート

新環境で使えるようになった機能のうち、AtCoderで有用なものをまとめます。

ValueTuple

C# 7の目玉機能のValueTuple構造体が使用できるようになります。

static void Main()
{
    var N = int.Parse(Console.ReadLine());
    var points = new (int x, int y)[N];
    for (int i = 0; i < N; i++)
    {
        var line = Console.ReadLine().Split(' ');
        points[i] = (int.Parse(line[0]), int.Parse(line[1]));
    }

    Console.WriteLine(points.Max(p => p.x));
}

基本的にはTupleクラスと同様の機能ですが、各要素にエイリアスがつけられるValueTupleになって扱いやすくなりました。

GetHashCode, Equals, CompareToも実装されているのでDictionaryのKeyとして使ったりSortしたりすることもできます。

CompareToする場合はItem1で比較→同じならItem2で比較→...

という直感的な比較がされます。

Deconstruct

ValueTupleを使用して複数戻り値も実装できます。

using System;

static class Program
{
    static (int index, T max) MaxBy<T>(this T[] arr) where T : IComparable<T>
    {
        T max = arr[0];
        int maxIndex = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            if (max.CompareTo(arr[i]) < 0)
            {
                max = arr[i];
                maxIndex = i;
            }
        }
        return (maxIndex, max);
    }
    static void Main()
    {
        var (i, x) = new[]{ 1, 20, 10, 20}.MaxBy();
        Console.WriteLine($"{i}: {x}");
        /* 出力
         * 1: 20
         */
    }
}

値の入れ替えも簡潔に記述できます。

static void Main()
{
    var x = 1;
    var y = 2;
    (y, x) = (x, y);
    Console.WriteLine($"x: {x}, y: {y}");
    /* 出力
    * x: 2, y: 1
    */
}

HashCode

ValueTupleGetHashCodeで使用されるHashCode構造体は自作の型でも使用できます。
複数の値から生成してくれるので、便利に使えそうです。

static void Main()
{
    Console.WriteLine(HashCode.Combine(1, 2, 3));
    /* 出力
    * -1480540106
    */
}

式中の変数宣言

is演算子やout演算子と変数宣言をセットで使えます。
out演算子との組み合わせは便利に使えることも多そうです。

static void Main()
{
    var arr = new int?[] { 2, null, 4, 6 };
    foreach (var item in arr)
        if(item is int num)
            Console.WriteLine(num);
    /* 出力
     * 2
     * 4
     * 6
     */

    var dic = new Dictionary<string, int>
    {
        { "foo",1 },
        { "bar",2 },
        { "hoge",10 },
    };
    dic.TryGetValue("foo", out var foo);
    dic.TryGetValue("bar", out var bar);
    dic.TryGetValue("bazz", out var bazz);
    dic.TryGetValue("hoge", out var hoge);
    Console.WriteLine($"{foo} {bar} {bazz} {hoge}");
    /* 出力
    * 1 2 0 10
    */
}

式形式のコンストラクタ、プロパティ

C# 6だと式形式で書なかった箇所です。

class MyClass
{
    private int x;
    public MyClass(int x) => this.x = x;
    public int X // 自動実装プロパティにすべきだが説明用
    {
        set => this.x = value;
        get => this.x;
    }
}

数値リテラル

0bから書くと2進数で書けます。
また、_で区切ることができます。

static void Main()
{
    Console.WriteLine(0b_1111_0000+1_000);
    /* 出力
    * 1240
    */
}

switch式

switchが式で使えます。いろいろ機能が多いので詳しくは割愛。

static void Main()
{
    Console.WriteLine(Console.ReadLine() switch
    {
        "foo" => 1,
        "hoge" => 2,
        _ => 10,
    });
}

Span

Span<T>構造体, ReadOnlySpan<T>構造体が追加されました。
簡単に言うと配列の一部だけを切り出して使用できる構造体です。

ライブラリの高速化に活用できそうです。

static void Main()
{
    var arr = new[] { 1, 2, 3, 4, 5, 6 };
    arr.AsSpan().Slice(1, 3).Reverse();
    Console.WriteLine(string.Join(' ', arr));
    /* 出力
        * 1 4 3 2 5 6
        */
}

ローカル関数

ローカル関数が使えます。コンテスト中に使うというよりはライブラリのなかの記述をスッキリさせるために使用するのが主になるかと思います。

C# 6でもラムダ式で同様のことはできましたが、再帰を記述する場合は一旦nullで初期化しておくなどの本質でない記述が必要でした。

static class GraphUtil
{
    public static int[] 強連結成分分解(this Node[] graph)
    {
        var sumi = new bool[graph.Length];
        Span<int> Dfs1(int index, Span<int> jun)
        {
            if (sumi[index])
                return jun;
            sumi[index] = true;
            foreach (var child in graph[index].children)
            {
                jun = Dfs1(child, jun);
            }
            jun[jun.Length - 1] = index;
            jun = jun.Slice(0, jun.Length - 1);
            return jun;
        }

        var jun = new int[graph.Length];
        var junsp = jun.AsSpan();
        for (int i = 0; i < graph.Length; i++)
            junsp = Dfs1(i, junsp);

        var res = NewArray(graph.Length, -1);
        bool Dfs2(int index, int group)
        {
            if (res[index] >= 0)
                return false;
            res[index] = group;
            foreach (var r in graph[index].roots)
                Dfs2(r, group);
            return true;
        }

        var g = 0;
        foreach (var i in jun)
            if (Dfs2(i, g))
                g++;
        return res;
    }
}

Range, Index

範囲アクセスによって配列やSpanの切り出しが簡単になります(Spanの方が効率が良いらしい)。

また、^1とすると配列やListの最後から1番目の要素というようにアクセスできます。

static void Main()
{
    var arr = new long[] { 1, 2, 3, 4, 5 };
    Console.WriteLine(string.Join(' ', arr[1..]));
    Console.WriteLine(string.Join(' ', arr[1..4]));
    Console.WriteLine(string.Join(' ', arr[1..^1]));
    Console.WriteLine(arr[^1]);
    Console.WriteLine(arr.ToList()[^1]);
    /* 出力
    * 2 3 4 5
    * 2 3 4
    * 2 3 4
    * 5
    * 5
    */
}

自作の型でも使えるので、たとえば下記のような累積和クラスで使用可能です。

using System;
using System.Collections.Generic;

class Sums
{
    static void Main()
    {
        var arr = new long[] { 1, 2, 3, 4, 5 };
        var sums = new Sums(arr);
        Console.WriteLine(sums[0..2]);
        Console.WriteLine(sums[1..5]);
        Console.WriteLine(sums[^0]);
        /* 出力
        * 3
        * 14
        * 15
        */
    }

    private long[] impl;
    public int Length { get; }
    public Sums(long[] arr)
    {
        this.Length = arr.Length;
        impl = new long[arr.Length + 1];
        for (var i = 0; i < arr.Length; i++)
            impl[i + 1] = impl[i] + arr[i];
    }
    public long this[int toExclusive] => impl[toExclusive];
    public long this[Range range] => impl[range.End.GetOffset(Length)] - impl[range.Start.GetOffset(Length)];
}

Arrayクラス

Fillメソッドが追加されました。

static void Main()
{
    var arr = new int[5];
    Array.Fill(arr, int.MaxValue);
    Console.WriteLine(string.Join(' ', arr));
    /* 出力
    2147483647 2147483647 2147483647 2147483647 2147483647
    */
}

unsafe

言語的な新機能ではないですが、unsafeが使えるようになっています。

static unsafe void Main()
{
    byte* arr = stackalloc byte[4];
    int* p = (int*)arr;
    *p = 1 + (3 << 8) + (7 << 16) + (15 << 24);
    Console.WriteLine($"{arr[0]} {arr[1]} {arr[2]} {arr[3]}");
    Console.WriteLine(*p);
    /* 出力
     * 1 3 7 15
     * 252117761
     */
}

Unsafeクラスも使えるようになっています。

static void Main()
{
    var arr = new byte[4];
    ref var num = ref System.Runtime.CompilerServices.Unsafe.As<byte, int>(ref arr[0]);
    num = 1 + (3 << 8) + (7 << 16) + (15 << 24);
    Console.WriteLine(string.Join(' ', arr));
    Console.WriteLine(num);
    /* 出力
     * 1 3 7 15
     * 252117761
     */
}

コンテスト中にunsafeコードをとっさに書くのは難しそうですが、自作ライブラリの高速化で有効活用できるかもしれません。

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

ASP.NET MVC5でjavascriptグリッドのTabulatorを使用して行操作する方法(reactiveData)

タイトルのとおり、ASP.NET MVC5でjavascriptグリッドのTabulatorを使用して行操作する方法(reactiveData)のサンプルコードを記載します。
無題.jpg

いろいろはまったのでメモとして。同じようなことを考えている人のご参考になれば幸いです。

このURLを参考にしました。
http://tabulator.info/docs/4.6/reactivity#reactive-data

Grid直接編集画面などで行操作する場合は、TabulatorオプションのreactiveDataを使用したほうが良いかなと思いました。

そのわけは、TabulatorでURLでsetData()したり、setSort()を使用すると、Gridのスクロール位置が初期化されてしまうからです。データのDB更新後は再セットする必要はありますが。
また、URLを直接指定せず、オブジェクトの配列の操作で管理したほうがラクということもあります。

  • サンプルコードを動かすためには、javascriptライブラリで下記のものが必要です。

1. tabulator.js
2. fetch-2.0.4.js (Tabulatorはfetchが3.0.0だと動かなかった)
3. promise-7.0.4.js

fetchとpromise(promiseはいろいろ複数ある)をそれぞれ見つけるのが難しかったのですが、下記URLのものを使用しています。
https://cdnjs.com/libraries/fetch/2.0.4
https://github.com/ForbesLindesay/promisejs.org/tree/master/polyfills/output

  • reactiveDataを使用する場合は、ajax URLでsetData()したりできなかったので、
    データを$.getJSON()で取得して、配列にしてからデータセットしてあげる必要があるみたいです。

  • reactiveDataはjavascriptのshift(), push(), unshift(), pop(), splice()によってトリガーされます。
    プログラムの動き上、行追加と行削除は以下の方法が必要です。
    行追加 splice()とpush()
    行削除 splice()
    ※行追加でpush()を使用する理由は 最終行を選択した時、splice()では行追加できないから。

  • TabulatorでreactiveDataを使用する場合は、TabulatorのaddRow()、updateRow()、DeleteRow()は使わない(使えない)ものと考えてよさそう。

下記がTabulatorで行追加・行削除をするサンプルコードです。
データ取得部分のgetJSON、データ登録の$.ajaxはサンプルコードでは省略してます。

※movable rowの時の順序変更に対応してないので後で見直しします。

index.cshtml
@model IEnumerable<WebApplication.Models.Member>

@{
    ViewBag.Title = "Index";
}

<div class="row">
    <div class="col col-md-12">
        <div id="table"></div>
    </div>
</div>

<div class="row">
    <div class="col col-md-12">
        <button type="button" class="btn" id="addRow">行追加</button>
        <button type="button" class="btn" id="deleteRow">行削除</button>
        <button type="button" class="btn" id="register">登録</button>
        <button type="button" class="btn" id="updateRow">選択行の更新(テスト用、ボタンでは実施しない)</button>
    </div>
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    <script>
         var tableData = null;
         var table = null;
         var rowPosition = null;

         (function index() {
             // 実際は$.getJSON()でデータ取得    
             // $.getJSON(url, null, function (response) {
                 tableData = [
                    { id: 1, name: "Billy Bob", age: "12", gender: "male", height: 1, col: "red", dob: "", cheese: 1 },
                     { id: 2, name: "Mary May", age: "1", gender: "female", height: 2, col: "blue", dob: "14/05/1982", cheese: true },
                     { id: 3, name: "Christine Lobowski", age: "42", height: 0, col: "green", dob: "22/05/1982", cheese: "true" },
                     { id: 4, name: "Brendon Philips", age: "125", gender: "male", height: 1, col: "orange", dob: "01/08/1980" },
                     { id: 5, name: "Margret Marmajuke", age: "16", gender: "female", height: 5, col: "yellow", dob: "31/01/1999" },
                 ];

                 table = createTable();
             // });
          })();

          function createTable() {
              return new Tabulator("#table", {
                  height: 300,
                  data: tableData,
                  movableRows: true,
                  reactiveData: true,
                  selectable: 1,
                  tooltips: true,
                  tooltipsHeader: true,
                  columns: [
                        { rowHandle: true, formatter: "handle", frozen: true, width: 30, minWidth: 30, frozen: true, },
                        {
                            title: "Name", field: "name", editor: "input", editorParams: {
                                elementAttributes: { maxlength: "255", }, 
                            }, validator: "string",
                        },
                        {
                            title: "Age", field: "age", editor: "number", editorParams: {
                                min: 0, max: 200,
                            }, validator: "min:0",
                        },
                        { title: "Favourite Color", field: "col", editor: "input", },
                        { 
                            title: "Date Of Birth", field: "dob", editor: "input", formatter: function (cell, formatterParams, onRendered) {
                            // return moment(cell.getValue()).format("YYYY-MM-DD HH:mm:ss");
                        }
                    }, 
                ],

                rowClick: function (e, row) {
                    rowPosition = row.getPosition();
                },
                validationFailed: function (cell, value, validators) {
                    // ここにValidation処理
                },
            });
        }

        $("#addRow").click(function () {
            if (tableData != null && rowPosition != null) {
                if ((tableData.length - 1) == rowPosition) {
                    tableData.push({ name: "Test(Add row bottom)", age: 32 });
                } else {
                    tableData.splice(rowPosition + 1, 0, { name: "Test(Add row)", age: 32 });
                }
            }
        });

        $("#deleteRow").click(function () {
            if (tableData != null && rowPosition != null) {
                if (confirm("行削除をしてもよろしいでしょうか?")) {
                    tableData.splice(rowPosition, 1);
                    rowPosition = null;
                }
            }
        });

        $("#updateRow").click(function () {
            if (tableData.length > 0 && rowPosition != null) {
                tableData.splice(rowPosition, 1, { name: "Test(Update row)", age: 32 });
            }
        });

        $("#register").click(function () {
            // オブジェクトの配列を$.ajax()で送信
        });
    </script>
}

実際はgetJSONや$.ajaxデータ送信、Validationの部分などのコードも作成はしてますが。続きを書くかも。

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