WebLog.cs(仮)

Migration

C#, EntityFramework, Migration
気が付いたら、マイグレーションで
Enable-Migrationsがいらなくなったのね
「Enable-Migrations is obsolete. Use Add-Migration to start using Migrations.」

Contextが複数ある時
・Add-Migration [Migration名] -Context [Context名]
・Update-Database -Context [Context名]

作成日:2018-05-13 / 更新日:2018-08-14

[C#]多言語化

C#, 多言語
社内で使っているアプリで、当初英語化する予定がなかったのに
途中から英語化というのはあるあるのような気がします。

それをネタに。
下記のようなアプリがあったとします。


フォームのタイトルが「たいとる」
ボタンが「表示」が設定されているとします。
この日本語アプリを英語対応してみます。

コントロール編


一般的には、フォームの言語設定を英語に切り替えて文字列を設定しなおせば
言語に応じて自動的に切り替わります。

フォームの言語を英語に切り替えます




あとは、英語の設定をするだけです。
たいとる→Title
表示→Show

ちなみに、言語の設定を行うと.resxファイルが追加されます。


ファイルをクリックするとリソースの画面に切り替わり中身が確認できます。




■設定した内容を確認
言語を切り替えるのは手間なので簡易的な方法で確認をしてみます。

フォームが表示するタイミングの言語設定に応じて、メッセージが切り替わるので
フォーム表示前に設定しておけば、その言語設定における表示が確認できます。(重要)

        [STAThread]
        static void Main()
        {
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en"); 
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }

※enを設定しました。




フォームのLanguageの「英語」と「英語(米国)」の違いについて


フォームのLanguageの一覧を見ると、「英語」と「英語(米国)」があります。



言語がen-USがどのようにして、文字列の解決を行うかというと
①英語(米国)[en-US]のリソースファイルを見に行きます(リソースファイル名.en-US.resx)
無ければ・・・
②英語[en]のリソースファイルを見に行きます(リソースファイル名.en.resx)
無ければ・・・
③規定値のリソースファイルを見に行きます(リソースファイル名.resx)

使い分けとしては、英語の中でも異なる部分がある時に個別に設定する感じです。
例えば、英語でもイギリス(en-GB)とアメリカ(en-US)に対応したいとします
ラベル01に「データベース」
ラベル02に「2階」と設定してあった時の英訳は

リソースファイル.en-GB.resx[英語(英国)]
ラベル01:database
ラベル02:first floor

リソースファイル.en-US.resx[英語(米国)]
ラベル01:database
ラベル02:second floor

共通な英訳を各国ごとに管理するのは手間です。
そのため、共通なものはリソースファイル.en.resxに記載します。

リソースファイル.en.resx
ラベル01:database

リソースファイル.en-GB.resx
ラベル02:first floor

リソースファイル.en-US.resx
ラベル02:second floor

各国のリソースファイルに情報がなければ、より上位のリソースファイルを参照する仕組みになっているのでなければen.resxを見に行くのでそこにあればよいのです。


以上のことから、日英表記だからといって、
わざわざフォームのLanguageを日本に設定する必要もないです。
大部分が日本であるなら、規定値に設定されていれば、
日本(日本) → リソースファイル.ja-JP.resxを探しに行き なければ
日本   → リソースファイル.ja.resxを探しに行き なければ
規定値 → リソースファイル.resxを探しに行くので。

※日本も日本と日本(日本)に分かれています。
ただ、英語圏と異なり日本語は1つしかないので、使い分けする必要性がないですけども。
基本的には、共通な内容は、カッコ無しに設定すればよいです。

コントロール以外のメッセージ


エラー時のメッセージなど、ダイアログで表示する内容も
リソースファイルに記載し、ファイル名が上記命名規則にしたがったものに
しておけば自動的に切り替えてくれます。

ボタンを押すと「はろーわーるど」「Hello World」と表示するとします。

■MessageList.resx ←規定値に相当

■MessageList.en.resx ←英語に相当


後は、ボタンのクリックイベントに リソースの文字列(ShowMessage)を取得して表示させるだけで自動的に切り替えてくれます
        private void BtnShow_Click(object sender, EventArgs e)
        {
            var msg = WindowsFormsApp1.Message.MessageList.ShowButton;
            MessageBox.Show(msg);
        }



動的に切り替える


仮想環境も増えてきたこともあって、デスクトップアプリといえ、クライアントPC上で実行するとも限らないわけで、別の方法で切り替えが必要になったりすると思います。

先ほどのアプリに日本語と英語のラジオボタンを追加します。


フォームロード時に、その時の言語設定に
応じてラジオボタンの設定をするようにします。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void BtnShow_Click(object sender, EventArgs e)
        {
            var msg = WindowsFormsApp1.Message.MessageList.ShowButton;
            MessageBox.Show(msg);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.SetLangRadioButton();
        }

        /// <summary>
        /// フォーム表示直後の言語を設定する
        /// </summary>
        private void SetLangRadioButton()
        {
            var lang = System.Globalization.CultureInfo.CurrentCulture.Name;

            if (lang.StartsWith("en"))
            {
                this.RbtnLangEn.Checked = true;
            }
            else
            {
                this.RbtnLangJa.Checked = true;
            }
        }
    }
}


先ほども、記載した通りフォームアプリは、
オープンする時の言語によってフォームを表示する際の言語が決まるので

開いてしまうと、開きなおさない限り表示が変わらないです。
ということで、再表示する

#Form1.cs
using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public string CultureName { get; private set; } 

        public Form1()
        {
            InitializeComponent();
        }

        private void BtnShow_Click(object sender, EventArgs e)
        {
            var msg = WindowsFormsApp1.Message.MessageList.ShowButton;
            MessageBox.Show(msg);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.SetLangRadioButton();
        }

        /// <summary>
        /// フォーム表示直後の言語を設定する
        /// </summary>
        private void SetLangRadioButton()
        {
            var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;

            if (lang.StartsWith("en"))
            {
                this.RbtnLangEn.Checked = true;
            }
            else
            {
                this.RbtnLangJa.Checked = true;
            }
        }

        private void RbtnLangJa_Click(object sender, EventArgs e)
        {
            this.CultureName = "ja-JP";
            this.Close();
        }

        private void RbtnLangEn_Click(object sender, EventArgs e)
        {
            this.CultureName = "en-US";
            this.Close();
        }
    }
}


ラジオボタンのClickイベントで、Form1のインスタンス変数にカルチャー名を指定
そして、閉じる。

#Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Form1 f = null;
            do
            {
                f = new Form1();
                Application.Run(f);

                // カルチャー名がセットしてあればセットする
                if (f.CultureName != null)
                {
                    var culture = new System.Globalization.CultureInfo(f.CultureName, false);
                    System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
                }

                // カルチャー名がセット有 = 言語切り替えがあった = 再表示
            } while (f.CultureName != null);
        }
    }
}

Form1を呼んだ側では、Form1のインスタンス変数(CultureName)をチェックし
値があれば、言語切り替えがあったと判断し、言語を設定し再度開く。
無ければループ処理を抜ける。

ちなみに、Form1.cs中のSetLangRadioButtonでは、
System.Globalization.CultureInfo.CurrentCulture.Name でなく
System.Threading.Thread.CurrentThread.CurrentUICulture.Name を使用していることに注意

    class Program
    {
        static void Main(string[] args)
        {
            // 実行直後の各Culture名を表示
            ShowCultureName();

            // CultureNameを設定
            var culture = new System.Globalization.CultureInfo("en-US");
            System.Threading.Thread.CurrentThread.CurrentUICulture = culture;

            // 再度Culture名を表示
            ShowCultureName();
        }

        static void ShowCultureName()
        {
            var cultureName1 = System.Globalization.CultureInfo.CurrentCulture.Name;
            Console.WriteLine("Global:{0}", cultureName1);

            var cultureName2 = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
            Console.WriteLine("Thread:{0}", cultureName2);

            Console.WriteLine();
        }
    }


Global:ja-JP
Thread:ja-JP

Global:ja-JP
Thread:en-US

と、言うことでGlobalはPCのカルチャーを取得するのに対し
Threadの方は文字通り、アプリ内で設定したカルチャーが取得できる。


おまけ


多言語の切り替えを実現といえば、ここまでとしたいのですが
現実的なことを言うとちょっと使い勝手が悪いのでいくつか細工します。

・言語切り替えをClickイベントに設定
 →切り替えじゃなくてClickで反応する
・動的切り替えをフォームを開きなおすことで実現
 →フォームが開くたびにちょこちょこ移動する

# Form1.cs
using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public string CultureName { get; private set; } 

        public Form1()
        {
            InitializeComponent();
        }

        private void BtnShow_Click(object sender, EventArgs e)
        {
            var msg = WindowsFormsApp1.Message.MessageList.ShowButton;
            MessageBox.Show(msg);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.SetLangRadioButton();

            // 言語切り替えラジオボタンの初期値を設定した後にチェンジイベントを設定
            this.RbtnLangEn.CheckedChanged += RbtnLangEn_CheckedChanged;
            this.RbtnLangJa.CheckedChanged += RbtnLangJa_CheckedChanged;
        }

        private void RbtnLangJa_CheckedChanged(object sender, EventArgs e)
        {
            this.CultureName = "ja-JP";
            this.Close();
        }

        private void RbtnLangEn_CheckedChanged(object sender, EventArgs e)
        {
            this.CultureName = "en-US";
            this.Close();
        }

        /// <summary>
        /// フォーム表示直後の言語を設定する
        /// </summary>
        private void SetLangRadioButton()
        {
            // System.Globalization.CultureInfo.CurrentCulture.Nameではなく
            // System.Threading.Thread.CurrentThread.CurrentUICulture.Nameに
            // 変わっていることに注意
            //var lang = System.Globalization.CultureInfo.CurrentCulture.Name;
            var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;

            if (lang.StartsWith("en"))
            {
                this.RbtnLangEn.Checked = true;
            }
            else
            {
                this.RbtnLangJa.Checked = true;
            }
        }
    }
}

Changeイベントを使う時は、最初っから指定しておくと、デフォルト値を指定した際に
値が変化したので閉じるという残念な状態になるので、初期値を設定した後にイベントを設定するなどの対処が必要かなと


# Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Form1 f = null;
            int cacheX = 0;
            int cacheY = 0;
            bool flagChangeLang = false;
            do
            {
                f = new Form1();
                if (flagChangeLang)
                {
                    // 再表示の時は、前回の位置を再現する
                    f.StartPosition = FormStartPosition.Manual;
                    f.Location = new System.Drawing.Point(cacheX, cacheY);
                }
                Application.Run(f);

                // カルチャー名がセットしてあればセットする
                if (f.CultureName != null)
                {
                    var culture = new System.Globalization.CultureInfo(f.CultureName, false);
                    System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
                    cacheX = f.Left;
                    cacheY = f.Top;
                    flagChangeLang = true;
                }

                // カルチャー名がセット有 = 言語切り替えがあった = 再表示
            } while (f.CultureName != null);
        }
    }
}

切り替え時、フォーム位置をキャッシュしておき、開始位置をマニュアルにして値を設定とかですかね。


細かいところは気になるけどここまで。
作成日:2018-03-31 / 更新日:2018-04-15

右詰め、左詰め

C#
ちょいちょい忘れるのでメモ

■半角スペース詰め
using System;

namespace SampleCode
{
    class Program
    {
        static void Main(string[] args)
        {
            // 数値
            // [左詰め ][ 右詰め][詰め無し]
            int n = 1;
            for(int i = 0; i<= 5; i++)
            {
                n *= 10;
                Console.WriteLine("[{0, -7:d}][{0, 7:d}][{0}]", n);
            }
            Console.WriteLine();

            // 文字列
            // [左詰め ][ 右詰め][詰め無し]
            n = 1;
            for(int i = 0; i <= 5; i++)
            {
                n *= 10;
                string s = n.ToString();
                Console.WriteLine("[{0}][{1}][{2}]", s.PadRight(7), s.PadLeft(7), s);
            }
        }
    }
}


[10     ][     10][10]
[100    ][    100][100]
[1000   ][   1000][1000]
[10000  ][  10000][10000]
[100000 ][ 100000][100000]
[1000000][1000000][1000000]

[10     ][     10][10]
[100    ][    100][100]
[1000   ][   1000][1000]
[10000  ][  10000][10000]
[100000 ][ 100000][100000]
[1000000][1000000][1000000]



■0詰め
using System;

namespace SampleCode
{
    class Program
    {
        static void Main(string[] args)
        {
            // 数値
            // [左詰め ][ 右詰め]
            int n = 1;
            for(int i = 0; i<= 5; i++)
            {
                n *= 10;
                Console.WriteLine("[{0,-7:d7}][{0,7:d7}]", n);
            }
            Console.WriteLine();

            // 文字列
            // [左詰め ][ 右詰め][詰め無し]
            n = 1;
            for(int i = 0; i <= 5; i++)
            {
                n *= 10;
                string s = n.ToString();
                Console.WriteLine("[{0}][{1}]", s.PadRight(7, '0'), s.PadLeft(7, '0'));
            }
        }
    }
}

[0000010][0000010]
[0000100][0000100]
[0001000][0001000]
[0010000][0010000]
[0100000][0100000]
[1000000][1000000]

[1000000][0000010]
[1000000][0000100]
[1000000][0001000]
[1000000][0010000]
[1000000][0100000]
[1000000][1000000]



using System;

namespace SampleCode
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("[{0,-10:00000}]", 123); //[00123     ](-10d5と一緒)
            Console.WriteLine("[{0, 10:00000}]", 123); //[     00123](10d5と一緒) 

            Console.WriteLine("[{0, 0:0.000}]",    123.45); //123.450(0:f3と一緒)
            Console.WriteLine("[{0, 0:0000.000}]", 123.45); //0123.450
        }
    }
}

作成日:2017-12-28

エラトステネスの篩

C#, アルゴリズム
エラトステネスの篩
エラトステネスの篩 (エラトステネスのふるい、英: Sieve of Eratosthenes) は、指定された整数以下の全ての素数を発見するための単純なアルゴリズムである。古代ギリシアの科学者、エラトステネスが考案したとされるため、この名がある。


小さい方から平方根の値までをループし
選んだ値より後ろの要素に対し割り切れたら除外し割り切れず残ったものが素数というアルゴリズム。

ふるいにかけていく様子そのもので、わかりやすいアルゴリズムですね。
wikipediaにあるgifがとてもわかりやすいです。

C#でシンプルに実装すると下記のようになります。
using System;
using System.Collections.Generic;
using System.Linq;

namespace SampleCode
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = GetPrimeNumberList(120);

            data.ForEach(d => Console.Write("{0} ", d));

            Console.WriteLine();
        }

        static List<int> GetPrimeNumberList(int MaxNumber)
        {
            var data = new List<int>();

            // 2未満は即終了
            if(MaxNumber < 2) { return data; }
   
            // 2から最後の数字までのリストを作成する
            data = Enumerable.Range(2, MaxNumber - 1).ToList<int>();

            int i = 0;
            while(data[i] <= Math.Sqrt(MaxNumber))
            {
                for (int j = data.Count - 1; i < j; j--)
                {
                    // 割り切れる時は削除
                    if(data[j] % data[i] == 0) { data.RemoveAt(j); }
                }
                i++;
            }
            
            return data;
        }
    }
}


実行結果
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 


うちの環境だと
10000 までの計算で0.005秒
100000 までの計算で0.268秒
1000000までの計算で8.614秒


エラトステネスの篩でよくみかける計算量を減らす方法で
2の倍数と3の倍数をふるいの対象からはずす方法を見かけます。

6列で考えた時

2列目8以降は2の倍数だから素数無し
3列目9以降は3の倍数だから素数無し
4列目4以降は2の倍数だから素数無し
6列目6以降は2の倍数だから素数無し

結局6n+7と6n+11だけ考えればよいことになります。
そのコードがGetPrimeNumberList_02です
using System;
using System.Collections.Generic;
using System.Linq;

namespace SampleCode
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            sw.Start();
            var data = GetPrimeNumberList_02(1000000);
            sw.Stop();

            Console.WriteLine(sw.Elapsed);
        }

        static List<int> GetPrimeNumberList_02(int MaxNumber)
        {
            var data = new List<int>();

            // 7未満の処理
            if (MaxNumber < 2) { return data; }
            data.Add(2);
            if (MaxNumber == 2) { return data; }
            data.Add(3);
            if (MaxNumber < 5) { return data; }
            data.Add(5);
            if(MaxNumber < 7) { return data; }
            
            // 7以上最後までの数字のうち6n+7, 6n+11のリストを作成する
            int n = 0;

            while (true)
            {
                int v = 6 * n + 7;
                if (MaxNumber < v) { break; }
                data.Add(v); 

                v = 6 * n + 11;
                if (MaxNumber < v) { break; }
                data.Add(v);

                n++;
            }

            int i = 0;
            while (data[i] <= Math.Sqrt(MaxNumber))
            {
                for (int j = data.Count - 1; i < j; j--)
                {
                    // 割り切れる時は削除
                    if (data[j] % data[i] == 0) { data.RemoveAt(j); }
                }
                i++;
            }

            return data;
        }
    }
}


最初の実装だと
1000000あたり8.6140秒かかっていたのが
1000000あたり4.9379秒に短縮されました
作成日:2017-12-26 / 更新日:2017-12-26

Fisher–Yates shuffle(Knuth shuffle)

C#, アルゴリズム
フィッシャー–イェーツのシャッフル
有限集合からランダムな順列を生成するアルゴリズムである。言い換えると、有限列をランダムな別の(シャッフルされた)順序の有限列に並べ直す方法である。


平たく言うと、配列に数字を入れて未シャッフル要素数分で乱数を発生させシャッフル対象の要素を選定し、末尾または先頭から要素を入れ替えていくことを繰り返す操作のことを言う


例えば
12345

という数字の配列があると
1~5の乱数を発生させ2が出たら
2番目の要素(2)とシャッフル未確定最後尾と入れ替える。
この時点では一つ確定していないので最後尾は(5)

15342

2は確定とする。
残り1534の4要素で同じことを行う
1~4の乱数を発生させ1が出たら
1番目の要素(1)とシャッフル未確定最後尾(4)と入れ替える。

45312

残り453の3要素で同じことを行う
1~3の乱数を発生させ2が出たら
2番目の要素(5)とシャッフル未確定最後尾(3)と入れ替える。

43512

残り43の2要素で同じことを行う
1~2の乱数を発生させ2が出たら
2番目の要素(3)とシャッフル未確定最後尾(3)と入れ替える。

つまり、乱数最大値が出たらシャッフルは発生しないことを意味する。
最後の要素になったらシャッフルしようがないのでここで終了。

コードにしたらこんな感じですね
using System;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            var num = Enumerable.Range(1, 20).ToList<int>();

            // シャッフル
            Random r = new Random();
            for (int i = num.Count - 1; 0 < i; i--)
            {
                int rand = r.Next(i);

                // 乱数が最大値と同じ時、シャッフル元とシャッフル先が同一となるため何もしない
                if(rand == i) { continue; }

                int temp = num[i];
                num[i] = num[rand];
                num[rand] = temp;
            }

            // 結果表示
            num.ForEach(n => Console.Write("{0} ", n));
            Console.WriteLine();
        }
    }
}

作成日:2017-12-10
NextPage