- 投稿日:2021-02-28T23:44:28+09:00
C# - いろんな対策を取り込んだ RichTextBox テンプレ - 全角空白とタブと改行を表示【自分用】
C# - RichTextBox関連やりたいこと逆引きリファレンス【自分用】 - Qiita
上記の集大成(?)です。
画面キャプチャ
サンプルコード
全角空白だらけにすると、かなり動作が重い
using System; using System.Drawing; using System.Text; using System.Text.RegularExpressions; using System.Runtime.InteropServices; using System.Windows.Forms; internal class MyRichTextBox:RichTextBox { private const Int32 WM_PAINT = 0x000F; private static readonly int adjX = -2; private static readonly int adjY = 2; private int lineHeight; private RichTextBox dummyRichTextBox; // lineHeight算出用 //---------------------------------------------------------------- // Win32 // private const int EM_SETTABSTOPS = 0x00CB; private const long IMF_DUALFONT = 0x80; private const int WM_SETREDRAW = 0x0008; private const int WM_USER = 0x0400; private const int EM_SETLANGOPTIONS = WM_USER + 120; private const int EM_GETLANGOPTIONS = WM_USER + 121; private const int EM_GETTEXTEX = (WM_USER + 94); private const int EM_GETTEXTLENGTHEX = (WM_USER + 95); // Flags for the GETEXTEX data structure private const int GT_DEFAULT = 0; private const int GTL_CLOSE = 4; // Fast computation of a "close" answer private const int GTL_DEFAULT = 0; // Do default (return # of chars) class NativeMethods { [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTEX wParam, StringBuilder lParam); [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)] public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTLENGTHEX wParam, IntPtr lParam); } [StructLayout(LayoutKind.Sequential)] struct GETTEXTEX { public int cb; public int flags; public int codepage; public IntPtr lpDefaultChar; public IntPtr lpUsedDefChar; } [StructLayout(LayoutKind.Sequential)] struct GETTEXTLENGTHEX { public int flags; public int codepage; } // //---------------------------------------------------------------- private void EnsureDummyRichTextBox(bool forceSetFont) { if ( dummyRichTextBox == null ) { dummyRichTextBox = new RichTextBox(); dummyRichTextBox.Text = "\n"; dummyRichTextBox.Font = this.Font; } else if ( forceSetFont ) { dummyRichTextBox.Font = this.Font; } } void RecalcLineHeight(bool fontUpdated) { EnsureDummyRichTextBox(fontUpdated); int y1 = dummyRichTextBox.GetPositionFromCharIndex(dummyRichTextBox.GetFirstCharIndexFromLine(0)).Y; int y2 = dummyRichTextBox.GetPositionFromCharIndex(dummyRichTextBox.GetFirstCharIndexFromLine(1)).Y; lineHeight = y2 - y1; } protected override void OnFontChanged(EventArgs e) { RecalcLineHeight(true); SetFixFont(); SetTabStop(4); } protected override void WndProc(ref System.Windows.Forms.Message m) { base.WndProc(ref m); if ( m.Msg == WM_PAINT ) { RecalcLineHeight(false); using ( Graphics graphic = base.CreateGraphics() ) { OnPaint(new PaintEventArgs(graphic, base.ClientRectangle)); } } } protected override void OnPaint(PaintEventArgs pe) { Brush brush = Brushes.LightGray; string s = this.Text; int sLen = s.Length;//this.TextLength; Graphics g = pe.Graphics; int lineStartI = GetLineFromCharIndex(GetCharIndexFromPosition(new Point(0, 0))); int lineEndI = GetLineFromCharIndex(GetCharIndexFromPosition(new Point(pe.ClipRectangle.Width, pe.ClipRectangle.Height))); Regex regex = new Regex(@"[\t \r\n]"); // tab or 全角空白 or 改行 // 行ごとに処理 int nextCharI = -1; for ( int lineI = lineStartI; lineI <= lineEndI; lineI++ ) { int startCharI; if ( lineI == lineStartI ) { startCharI = GetFirstCharIndexFromLine(lineI); } else { startCharI = nextCharI; } nextCharI = GetFirstCharIndexFromLine(lineI+1); if (nextCharI < startCharI) { nextCharI = sLen; if ( startCharI == nextCharI ) { break; } } int lenOfLine = nextCharI - startCharI; Match match = regex.Match(s, startCharI, lenOfLine); while ( match.Success ) { Point point = this.GetPositionFromCharIndex(match.Index); //Point point2 = this.GetPositionFromCharIndex(match.Index+match.Length); //g.FillRectangle(brush, point.X, point.Y, point2.X - point.X, lineHeight); if ( s[match.Index] == '\t' ) { g.DrawString(">", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat()); } else if ( s[match.Index] == ' ' ) {//全角空白 g.DrawString("□", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat()); } else if ( s[match.Index] == '\r' ) { g.DrawString(@"\r", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat()); } match = match.NextMatch(); } /* // ちらつきと残骸が残る。実用レベルではない // // drawstringでtabの描画で調整がやっかいなので、1文字ずつGetPositionFromCharIndexで場所を取得して描画する Point pointNext = new Point(0,0); for ( int i=startCharI; i<startCharI+lenOfLine; i++ ) { Point point; if ( i == startCharI ) { point = this.GetPositionFromCharIndex(i); } else { point = pointNext; } pointNext = this.GetPositionFromCharIndex(i+1); if ( pointNext.X < 0 ) { continue; } if ( point.X >= this.Width ) { break; } g.DrawString(new string(s[i],1), this.Font, Brushes.Blue, adjX+point.X, adjY+point.Y, new StringFormat()); } */ } } public override string Text { get { var getLength = new GETTEXTLENGTHEX(); getLength.flags = GTL_CLOSE; //get buffer size getLength.codepage = 1200; //Unicode int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64()); var getText = new GETTEXTEX(); getText.cb = textLength + 2; //add space for null terminator if ( getText.cb < 2 ) { return ""; } // overflow getText.flags = GT_DEFAULT; getText.codepage = 1200; //Unicode var sb = new StringBuilder(getText.cb); NativeMethods.SendMessage(this.Handle, EM_GETTEXTEX, ref getText, sb); return sb.ToString(); } set { base.Text = value; } } public override int TextLength { get { var getLength = new GETTEXTLENGTHEX(); getLength.flags = GTL_DEFAULT; //Returns the number of characters getLength.codepage = 1200; //Unicode int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64()); if ( textLength < 0 ) { return 0; } // overflow return textLength; } } private void SetFixFont() { IntPtr lParam = NativeMethods.SendMessage(this.Handle, EM_GETLANGOPTIONS, new IntPtr(0), new IntPtr(0)); lParam = new IntPtr( ((long)lParam) & (~IMF_DUALFONT)); NativeMethods.SendMessage(this.Handle, EM_SETLANGOPTIONS, new IntPtr(0), lParam); } private void SetTabStop(int tabSize) { int[] tabarray = new int[] { tabSize*4 }; int wparam = tabarray.Length; IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length); Marshal.Copy(tabarray, 0, parray, tabarray.Length); NativeMethods.SendMessage(this.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray); } } class RichTextBoxSample : Form { MyRichTextBox txt; RichTextBoxSample() { ClientSize = new Size(600, 250); Controls.Add(txt = new MyRichTextBox(){ Dock = DockStyle.Fill, Multiline = true, WordWrap = false, AcceptsTab = true, ScrollBars = RichTextBoxScrollBars.Both, }); Font baseFont = new Font("MS ゴシック", 10); txt.Font = baseFont; } [STAThread] static void Main() { Application.Run(new RichTextBoxSample()); } }
- 投稿日:2021-02-28T17:25:48+09:00
C# - RichTextBox関連やりたいこと逆引きリファレンス【自分用】
文字位置情報の取得と設定
位置情報の取得
やりたいこと プロパティまたはメソッド名 参考サイト 位置座標 ⇒ 文字indexの取得 GetCharIndexFromPosition
文字index ⇒ 位置座標の取得 GetPositionFromCharIndex
文字index ⇒ 行番号indexの取得 GetLineFromCharIndex
dobon 行番号index ⇒ 行頭文字indexの取得 GetFirstCharIndexFromLine
現在行の行頭文字indexの取得 GetFirstCharIndexOfCurrentLine
選択位置の取得と設定
やりたいこと プロパティまたはメソッド名 参考サイト 選択開始位置(index)の取得1 SelectionStart
選択文字数の取得 SelectionLength
選択対象文字列(indexと文字数)の設定 Select
全選択 SelectAll()
書式設定
選択されている部分文字列に対して書式を設定します。※要注意2
やりたいこと プロパティまたはメソッド名 参考サイト 背景色を設定/取得 SelectionBackColor
文字の色を設定/取得 SelectionColor
フォントを設定/取得 SelectionFont
上付き下付きオフセットを設定/取得 SelectionCharOffset
dobon テキスト検索
やりたいこと プロパティまたはメソッド名 参考サイト 文字列検索 Find
パラメータ:
RichTextBoxFinds
ファイル操作
自前で読み込み処理を作っても問題はないが、下記が使えるかも。
やりたいこと プロパティまたはメソッド名 参考サイト 読み込み LoadFile
保存 SaveFile
パラメータ:
RichTextBoxStreamType
主要なイベント
やりたいこと イベント名 参考サイト テキストが変更されたとき(※要注意2) TextChanged
選択文字位置または文字数が変更されたとき SelectionChanged
その他いろいろ
コントロールのプロパティ
タブ幅の指定
using System.Runtime.InteropServices;
[DllImport("user32")] public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); private const int EM_SETTABSTOPS = 0x00CB; static IntPtr SetTabStop(TextBoxBase t, int tabSize) { int[] tabarray = new int[] { tabSize*4 }; int wparam = tabarray.Length; IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length); Marshal.Copy(tabarray, 0, parray, tabarray.Length); IntPtr ret = NativeMethods.SendMessage(t.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray); return ret; }フォントが勝手に変わる問題の対策
タブ幅設定より先に実施したほうがよい・・はず。
private const long IMF_DUALFONT = 0x80; private const int WM_USER = 0x0400; private const int EM_SETLANGOPTIONS = WM_USER + 120; private const int EM_GETLANGOPTIONS = WM_USER + 121; static IntPtr SetFixFont(TextBoxBase t) { IntPtr lParam = NativeMethods.SendMessage(t.Handle, EM_GETLANGOPTIONS, new IntPtr(0), new IntPtr(0)); lParam = new IntPtr( ((long)lParam) & (~IMF_DUALFONT)); return NativeMethods.SendMessage(t.Handle, EM_SETLANGOPTIONS, new IntPtr(0), lParam); }再描画の停止と再開
描画の再開についての補足
(別記事に引っ越すかも)
RichTextBoxに関しては、描画の再開がInvalidate()
やRedrawWindow
(Win32API)ではうまくいかない。(矢印キー操作は描画がされるが、マウス操作だと描画されないままになってしまう。)
this.ActiveControl = null;
と RichTextBox.Focus();
して強引に復帰させればいけているっぽいが、現物合わせなので根拠はない。オーナードロー
プログラム起点で
Select
で書式を変えると、色々と邪魔なイベントが発生してしまうため、自前で描画したほうが整合をとりやすいかもしれない。オーナードローは有力な選択肢と思っている。(もはやRichTextBox
である必要性がないかもしれない。。。3)IMEが勝手に確定する問題の対策
色々取り込んでみた結果
ほとんど
RichBoxText
の使い方をしていない気がするが。。。参考サイト
- RichTextBox Tips メニュー < C# 2005 Tips HOME < HIRO's.NET
- コントロール(System.Windows.Forms) - .NET Tips (VB.NET,C#...)
- RichTextBox Class (System.Windows.Forms) | Microsoft Docs
- Edit Control Messages - Win32 apps | Microsoft Docs
右端が選択されている場合、取得結果=文字数(=
Text.Length
)となりえるので注意。 ↩書式を変えるだけなのに
TextChanged
イベントが発生するようなので注意。(設定に依存?) ↩ただし、
TextBox
だと文字を入力しただけだと、WM_PAINT
メッセージが発火しない。 ↩
- 投稿日:2021-02-28T15:57:03+09:00
[c#] ConcurrentDictionaryを作って学ぶマルチスレッドデザインパターン
概要
スレッドセーフといった単語を聞いたことはありますか??
聞いたことはあっても、それがどういう状態かはっきりわかっているとは言えないなぁと思っているそこの あなた に向けた記事です。
今回の記事を読めば、スレッドがどういうもので、スレッドセーフとは何かがわかるようになるはずです。
今回の目標としてはスレッドセーフなDictionary、つまり標準ライブラリのConcurrentDictionaryを作ることです。
今回、採用する言語としてc#を選びました。
c#は言語レベルでマルチスレッドプログラミングをサポートしている言語で、async
やawait
、Task
などはc#erなら聞いたことや使ったことがあると思います。
これらの動作の根底にはマルチスレッドデザインパターンが使用されています。
コラムとしてこれらの動作について、スレッドレベルでほんわかと解説したいと思います。今回の記事はJavaで学ぶマルチスレッドデザインパターン を読んでc#バージョンをやろうと触発された結果なので、興味があればこの本もぜひ読んでみてください。
この記事でもちいている用語は、基本的に上の本からの引用です。記事一覧
- 投稿日:2021-02-28T15:54:14+09:00
Dictionaryをスレッドセーフにする
- 投稿日:2021-02-28T15:52:19+09:00
Dinctionaryをより効率的にスレッドセーフを実現する
- 投稿日:2021-02-28T15:48:37+09:00
async,await,taskの動きを理解する
[c#] ConcurrentDictionaryを作って学ぶマルチスレッドデザインパターンの最後の記事です。
未執筆
- 投稿日:2021-02-28T11:26:14+09:00
[C#] 継承/object型/ポリモーフィズム/インターフェース/例外処理について
クラスの継承
ClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { var employee = new Employee { Number = 368, FirstName = "ともや", LastName = "鈴木", HireDate = new DateTime(2016, 10, 3) }; Console.WriteLine("従業員番号{0}の{1}は、{2}年に入社しました", employee.Number, employee.FullName, employee.HireDate.Year); } } class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return LastName + FirstName; } } public string Email { get; set; } } //従業員クラス class Employee : Person //Personクラスを継承 { //従業員番号 public int Number { get; set; } //入社年月日 public DateTime HireDate { get; set; } } //顧客クラス class Customer : Person { //顧客ID public string Id { get; set; } //顧客ランク public int Rank { get; set; } //クレジットカード番号 public string CreditCardNumber { get; set; } } }継承におけるメソッド
ClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { var employee = new Employee { Number = 368, FirstName = "ともや", LastName = "鈴木", Email = "tomotomo@example.com", HireDate = new DateTime(2016, 10, 3) }; Console.WriteLine("従業員番号{0}の{1}は、{2}年に入社しました", employee.Number, employee.FullName, employee.HireDate.Year); //メソッドの呼び出し var person = new Person { FirstName = "ゆか", LastName = "佐々木", Email = "sasasa@example.com" }; person.Print(); employee.Print(); } } class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return LastName + FirstName; } } public string Email { get; set; } public void Print() //メソッドを追加 { Console.WriteLine($"名前: {FullName} ({Email})"); } } class Employee : Person { public int Number { get; set; } public DateTime HireDate { get; set; } } class Customer : Person { public string Id { get; set; } public int Rank { get; set; } public string CreditCardNumber { get; set; } } }メソッドのオーバーライド(上書き)
ClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { var employee = new Employee { Number = 368, FirstName = "ともや", LastName = "鈴木", Email = "tomotomo@example.com", HireDate = new DateTime(2016, 10, 3) }; employee.Print(); //オーバライドしたEmployeeクラスのPrintメソッドを呼び出す var person = new Person { FirstName = "ゆか", LastName = "佐々木", Email = "sasasa@example.com" }; person.Print(); //PersonクラスのPrintメソッドを呼び出す } } class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return LastName + FirstName; } } public string Email { get; set; } public virtual void Print() //virtualキーワードをつけるとサブクラスでオーバライドできる { Console.WriteLine($"名前: {FullName} ({Email})"); } } class Employee : Person { public int Number { get; set; } public DateTime HireDate { get; set; } // overrideキーワードでメソッドを上書きできる public override void Print() { Console.WriteLine($"{Number}:{FullName}({Email}) {HireDate.Year}年入社"); } } class Customer : Person { public string Id { get; set; } public int Rank { get; set; } public string CreditCardNumber { get; set; } } }object型
ToStringメソッドをオーバーライドするClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { //呼び出し var employee = new Employee() { Number = 368, FirstName = "ともや", LastName = "鈴木", Email = "tomotomo@example.com", HireDate = new DateTime(2016, 10, 3) }; var s = employee.ToString(); //オーバーライドしたToStringメソッドを呼び出す Console.WriteLine(s); var person = new Person { FirstName = "ゆか", LastName = "佐々木", Email = "sasasa@example.com" }; person.Print(); } } class Person { public string FirstName { get; set; } public string LastName { get; set; } public string FullName { get { return LastName + FirstName; } } public string Email { get; set; } public virtual void Print() { Console.WriteLine($"名前: {FullName} ({Email})"); } } class Employee : Person { public int Number { get; set; } public DateTime HireDate { get; set; } public override string ToString() //ToStringメソッドをオーバーライド { var s = $"{Number} {FullName} " + $"{HireDate.Year}年{HireDate.Month}月{HireDate.Day}日入社"; return s; } } class Customer : Person { public string Id { get; set; } public int Rank { get; set; } public string CreditCardNumber { get; set; } } }ポリモーフィズム
種類の異なるクラスを同一視する
ClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { VirtualPet pet1 = new FoodiePet("たべお"); VirtualPet pet2 = new CheerfulPet("ゲンキ"); VirtualPet pet3 = new SleepyPet("ねむお"); var pets = new List<VirtualPet>(); pets.Add(pet1); //3つのオブジェクトをvirtualPet型のリストに格納 pets.Add(pet2); //3つのオブジェクトをvirtualPet型のリストに格納 pets.Add(pet3); //3つのオブジェクトをvirtualPet型のリストに格納 foreach (var pet in pets) { pet.Eat(); pet.Play(); Console.WriteLine($"{pet.Name} 機嫌:{pet.Mood} エネルギー:{pet.Energy}"); } } } class VirtualPet { public string Name { get; private set; } public int Mood { get; set; } public int Energy { get; set; } //コンストラクター public VirtualPet(string name) { Name = name; Mood = 5; Energy = 100; } public virtual void Eat() //virtualキーワードを使っている { } public virtual void Play() { } public virtual void Sleep() { } } class FoodiePet : VirtualPet { public FoodiePet(string name) : base(name) //baseキーワードで継承元のコンストラクターを呼び出す { } public override void Eat() //overrideを使って上書き定義 { Mood += 3; Energy += 5; Console.WriteLine("FoodiePet.Eatメソッドが実行されました"); } public override void Play() //overrideを使って上書き定義 { Mood -= 1; Energy -= 10; Console.WriteLine("FoodiePet.Playメソッドが実行されました"); } public override void Sleep() //overrideを使って上書き定義 { Mood -= 1; Energy += 2; Console.WriteLine("FoodiePet.Sleepメソッドが実行されました"); } } class CheerfulPet : VirtualPet { public CheerfulPet(string name) : base(name) { } public override void Eat() { Mood += 0; //値は変化しない Energy += 5; Console.WriteLine("CheerfulPet.Eatメソッドが実行されました"); } public override void Play() { Mood += 3; Energy -= 10; Console.WriteLine("CheerfulPet.Playメソッドが実行されました"); } public override void Sleep() { Mood -= 1; Energy += 2; Console.WriteLine("CheerfulPet.Sleepメソッドが実行されました"); } } class SleepyPet : VirtualPet { public SleepyPet(string name) : base(name) { } public override void Eat() { Mood -= 1; Energy += 5; Console.WriteLine("SleepyPet.Eatメソッドが実行されました"); } public override void Play() { Mood -= 1; Energy -= 10; Console.WriteLine("SleepyPet.Playメソッドが実行されました"); } public override void Sleep() { Mood += 2; Energy += 2; Console.WriteLine("SleepyPet.Sleepメソッドが実行されました"); } } }抽象クラス
abstractの付いたクラスを抽象クラスという
・これを使用してvirtualPetクラスのインスタンスを生成できなくすることができる
abstract class VirtualPet
{
・・・・・
}抽象メソッド
・メソッドを抽象メソッドとした場合は、サブクラスでは必ず、overrideキーワードでメソッドを上書きする必要があるClassSampleabstract class VirtualPet { public string Name { get; private set; } public int Mood { get; set; } public int Energy { get; set; } //コンストラクター public VirtualPet(string name) { Name = name; Mood = 5; Energy = 100; } public abstract void Eat(); //abstractキーワードで抽象メソッドにする public abstract void Play(); //abstractキーワードで抽象メソッドにする public abstract void Sleep(); //abstractキーワードで抽象メソッドにする }インターフェース
ClassSampleusing System; using System.Collections.Generic; using System.Linq; namespace ClassSample { class Program { static void Main() { var pets = new List<IVirtualPet>(); var pet1 = new FoodiePet("たべお"); var pet2 = new CheerfulPet("ゲンキ"); var pet3 = new SleepyPet("ねむお"); pets.Add(pet1); pets.Add(pet2); pets.Add(pet3); foreach (var pet in pets) { pet.Eat(); pet.Play(); Console.WriteLine($"{pet.Name} 機嫌:{pet.Mood} エネルギー:{pet.Energy}"); } } } interface IVirtualPet //interfaceキーワードでインターフェースを定義 { string Name { get; } int Mood { get; set; } int Energy { get; set; } void Eat(); void Play(); void Sleep(); } class FoodiePet : IVirtualPet { public string Name { get; private set; } public int Mood { get; set; } public int Energy { get; set; } public FoodiePet(string name) { Name = name; Mood = 5; Energy = 100; } public void Eat() //インターフェースを実装する場合はoverrideは不要 { Mood += 3; Energy += 5; Console.WriteLine("FoodiePet.Eatメソッドが実行されました"); } public void Play() //インターフェースを実装する場合はoverrideは不要 { Mood -= 1; Energy -= 10; Console.WriteLine("FoodiePet.Playメソッドが実行されました"); } public void Sleep() //インターフェースを実装する場合はoverrideは不要 { Mood -= 1; Energy += 2; Console.WriteLine("FoodiePet.Sleepメソッドが実行されました"); } } class CheerfulPet : IVirtualPet { public string Name { get; private set; } public int Mood { get; set; } public int Energy { get; set; } public CheerfulPet(string name) { Name = name; Mood = 5; Energy = 100; } public void Eat() //インターフェースを実装する場合はoverrideは不要 { Mood += 0; Energy += 5; Console.WriteLine("CheerfulPet.Eatメソッドが実行されました"); } public void Play() //インターフェースを実装する場合はoverrideは不要 { Mood += 3; Energy -= 10; Console.WriteLine("CheerfulPet.Playメソッドが実行されました"); } public void Sleep() //インターフェースを実装する場合はoverrideは不要 { Mood -= 1; Energy += 2; Console.WriteLine("CheerfulPet.Sleepメソッドが実行されました"); } } class SleepyPet : IVirtualPet { public string Name { get; private set; } public int Mood { get; set; } public int Energy { get; set; } public SleepyPet(string name) { Name = name; Mood = 5; Energy = 100; } public void Eat() //インターフェースを実装する場合はoverrideは不要 { Mood -= 1; Energy += 5; Console.WriteLine("SleepyPet.Eatメソッドが実行されました"); } public void Play() //インターフェースを実装する場合はoverrideは不要 { Mood -= 1; Energy -= 10; Console.WriteLine("SleepyPet.Playメソッドが実行されました"); } public void Sleep() //インターフェースを実装する場合はoverrideは不要 { Mood += 2; Energy += 2; Console.WriteLine("SleepyPet.Sleepメソッドが実行されました"); } } }例外処理
例外をキャッチする
ClassSampleusing System; namespace ClassSample { class Program { static void Main() { try { var total = 1000; var line = Console.ReadLine(); var count = int.Parse(line); var ans = total / count; Console.WriteLine(ans); Console.WriteLine("正常終了"); } catch { Console.WriteLine("入力した値が正しくありません"); } } } }例外の種類を指定してキャッチする
ClassSampleusing System; namespace ClassSample { class Program { static void Main() { try { var total = 1000; var line = Console.ReadLine(); var count = int.Parse(line); var ans = total / count; Console.WriteLine(ans); Console.WriteLine("正常終了"); } catch (System.DivideByZeroException) { Console.WriteLine("ゼロは入力できません"); } catch (System.FormatException) { Console.WriteLine("数値を入力してください"); } catch (System.Exception) { Console.WriteLine("予期しないエラーが発生しました"); //全てキャッチできるが最後に書かないと他のエラーがキャッチできなくなるので注意が必要 } } } }例外オブジェクトの参照
ClassSampleusing System; namespace ClassSample { class Program { static void Main() { try { Book book = null; var title = book.Title; Console.WriteLine(title); } catch (Exception ex) { Console.WriteLine($"Type: {ex.GetType().Name}"); Console.WriteLine($"Message: {ex.Message}"); Console.WriteLine($"TargetSite: {ex.TargetSite}"); Console.WriteLine($"StackTrace: {ex.StackTrace}"); } } } class Book { public string Title { get; set; } public string Auther { get; set; } public int Pages { get; set; } public int Rating { get; set; } public Book(string title, string auther, int pages, int rating) { Title = title; Auther = auther; Pages = pages; Rating = rating; } } }例外を発生させる
スローした例外をキャッチするClassSampleusing System; namespace ClassSample { class Program { static void Main() { try { var bc = new BmiCalculator(); var bmi = bc.GetBmi(1.57, 49.5); Console.WriteLine(bmi); } catch (ArgumentException ex) //ArgumentExceptionだけをキャッチする { Console.WriteLine(ex.Message); } } } class BmiCalculator { public double GetBmi(double height, double weight) { if (height < 60.0 || 250 < height) { throw new ArgumentException("heightの指定に誤りがあります"); //例外をスローする } if (weight < 10.0 || 200.0 < weight) { throw new ArgumentException("weightの指定に誤りがあります"); //例外をスローする } var metersTall = height / 100.0; var bmi = weight / (metersTall * metersTall); return bmi; } } }try-finallyによる後処理
ClassSampleusing System; using System.IO; namespace ClassSample { class Program { static void Main() { try { ReadSample(); } catch { Console.WriteLine("ReadSampleでエラーが発生"); } } private static void ReadSample() { var file = new StreamReader("test.txt"); try { while (file.EndOfStream == false) { var line = file.ReadLine(); Console.WriteLine(line); } } finally { file.Dispose(); } } } }usingによる後処理
using文はIDisposableインターフェースを実装しているクラスのインスタンスを生成するときだけ使えるClassSampleusing System; using System.IO; namespace ClassSample { class Program { static void Main() { try { ReadSample(); } catch { Console.WriteLine("ReadSampleでエラーが発生"); } } private static void ReadSample() { using (var file = new StreamReader("test.txt")) { while (file.EndOfStream == false) { var line = file.ReadLine(); Console.WriteLine(line); } } //Disposeメソッドは書かれていないが、最後にfile.Disaposeが呼ばれる } } }
- 投稿日:2021-02-28T10:04:42+09:00
UnityでPytorchライクの機械学習ライブラリを作る。8日目:ロス関数とTensor操作
はじめに
今回はロス関数とTensorに対する操作(detach, reshapeなど)を実装したいと思います。
その前に
実装でロス関数をBackward処理を行う際に
Tensor loss = LossFunction(y, target) loss.Backward();と呼び出したいのですが、そのままだとBackward処理が連鎖していかない(起点となる処理が必要)ので、Tensorクラスで現在Backward関数としている関数をBackwardChainに変更し新たにBackwardを追加します。
Tensor.csnamespace Rein { [Serializable] public partial class Tensor { public void BackwardChain() { // BackFunctionが存在しない時は終了 if(this.BackFunction == null)return; this.UseCount--; // 他の関数に対しても出力している場合にはまだ勾配を計算しない if(this.UseCount != 0)return; this.BackFunction.Backward(); } public void Backward(){ // 一つの変数しか持たないことを確認する if (this.Size > 1)throw new InvalidSizeException($"expect size : 1, but actual : {this.Size}"); this.Grad[0] = 1.0; this.BackFunction.Backward(); } } }Loss関数の実装
それではいくつか主となるLoss関数を実装していきます。基本的にはLossの関数をLambdaで計算した後SumやMeanを計算することになります
MSELoss(二乗誤差)
これは以下のような関数です
Loss_{MSE}=\frac{1}{n}\sum_{i=1}^{n}(y_i-t_i)^2MSELossの実装
F.cs内部に直接LambdaFunctionとして実装していきます。
F.csnamespace Rein{ public static class F{ public static Tensor MSELoss(Tensor In){ return new Lambda( "MSELoss", (x) => x * x, (x) => 2 * x ).Forward(In)[0].Mean(); } } }HuberLossの実装
HuberLossは以下のような計算を行います。
f_{huber}(x) = \left\{ \begin{array}{ll} \frac{1}{2}x^2 & (-\delta \leq x \leq \delta) \\ \delta|x|-\frac{1}{2}\delta^2 & (x \lt -\delta \, or\, x \gt \delta) \end{array} \right.\\ L_{huber}=\frac{1}{n}\sum_{i=1}^{n}f_{huber}(y_i-t_i)実装
こちらも同様にF.csに加えていきます。
F.csnamespace Rein{ public static class F{ public static Tensor HuberLoss(Tensor left, Tensor right, R delta = 1.0){ R deltaSquare = delta * delta / 2; return new Lambda( "HuberLossFunction", new Func<R, R>((x) => x < -delta ? -delta * x - deltaSquare : (x > delta ? delta * x - deltaSquare : x * x / 2)), new Func<R, R>((x) => x < -delta ? -delta : (x > delta ? delta : x)) ).Forward(left - right); }public static Tensor HuberLoss(Tensor left, Tensor right, R delta = 1.0){ R deltaSquare = delta * delta / 2; return new Lambda( "HuberLossFunction", new Func<R, R>((x) => x < -delta ? -delta * x - deltaSquare : (x > delta ? delta * x - deltaSquare : x * x / 2)), new Func<R, R>((x) => x < -delta ? -delta : (x > delta ? delta : x)) ).Forward(left - right); } } }Tensorの操作
次はいくつかTensorの構造に作用する関数を実装していきたいと思います。Tensorの操作を行うメソッドでは基本的にShapeに作用するためDataの中身を変えないため、入力したTensorと同じインスタンスが出力されることとなります。
Detach
これはTensorの依存関係を切り離し、勾配の伝播を止める操作です。要は学習はさせないがネットワークの出力だけ欲しいという時に使う関数です。これをTensorの関数として実装したいのですが、一つ問題があります。
例えば以下のような形式で使用するとします。Tensor y = network(x).detach(); Tensor z = network(t); Tensor loss = (y - z) * (y - z); loss.Backward();ここでTensor yは独立したBackFuncを持たないTensorとなるのですが、network内部ではxが入力された時に計算グラフが作られ保存されているので、これらの関係を解消するためには一々yからグラフを遡る必要が出てきます。
そのため、残念ながらTensorの操作としてのDetach操作は断念せざるを得ません。
そこで、代わりにBaseFunctionに「勾配情報を保存しないForward」を定義します。これをPredictとします。実装(IFunction.csの追記)
まずIFunctionに対してPredictを追加します。
IFunction.csnamespace Rein.Functions { public interface IFunction { public Tensor[] Forward(params Tensor[] inputs); public Tensor[] Predict(params Tensor[] inputs); public void Backward(); public Tensor[] Parameters {get; } } }実装(BaseFuncttion.csの追記)
IFunctionに追加した関数の詳細をBaseFunctionで定義します。
BaseFunction.csnamespace Rein.Functions { public abstract class BaseFunction: IFunction { // ... public virtual Tensor[] Predict(params Tensor[] inputs){ return this.FunctionForward(inputs); } // ... } }これを使用することで、学習時に勾配を計算させないようにすることができます。PytorchのようにDetachをTensorの操作として呼び出したいなら、計算グラフの実装方法を変える必要があるようです。
Squeeze・Unsqueeze
SqueezeはTensorのある軸方向のサイズが1の時にその軸を消し次元を減らす操作で、
Unsqueezeは逆に次元を増やす操作です。これらも同様に関数クラスとして実装しTensorから呼び出せるようにしておきます。Squeezeの実装
Squeeze.csnamespace Rein.Functions.Process{ public class Squeeze: UnaryFunction{ private List<int> InShape; private int Dim; public Squeeze(int dim): base($"Squeeze-{dim}"){ this.Dim = dim; } protected override Tensor UnaryForward(Tensor tensor) { this.InShape = new List<int>(tensor.Shape); if(tensor.Shape[this.Dim] == 1)tensor.Shape.RemoveAt(this.Dim); return tensor; } protected override void UnaryBackward() { this.In.Shape = this.InShape; } } }Unsqueezeの実装
Unsqueezenamespace Rein.Functions.Process{ public class Unsqueeze: UnaryFunction{ private List<int> InShape; private int Dim; public Unsqueeze(int dim): base($"Unsqueeze-{dim}"){ this.Dim = dim; } protected override Tensor UnaryForward(Tensor tensor) { this.InShape = new List<int>(tensor.Shape); tensor.Shape.Insert(this.Dim, 1); return tensor; } protected override void UnaryBackward() { this.In.Shape = this.InShape; } } }Reshape
ReshapeでもSqueezeと同様にTensorのデータは変えずにShapeのみを入れ替えることになります。
実装
Reshape.csnamespace Rein.Functions.Process{ public class Reshape: UnaryFunction{ private List<int> OutShape; private List<int> InShape; public Reshape(List<int> shape): base($"Reshape-({string.Join(",", shape)})"){ this.OutShape = shape; } protected override Tensor UnaryForward(Tensor tensor) { // サイズ確認 if (this.OutShape.Aggregate((now, next) => now * next) != tensor.Size) throw new InvalidShapeException($"Expected Output Shape : ({string.Join(",", this.OutShape)}) ,Input Shape :({string.Join(",", tensor.Shape)})"); this.InShape = tensor.Shape; tensor.Shape = this.OutShape; return tensor; } protected override void UnaryBackward() { this.In.Shape = this.InShape; } } }Tensorクラスへの追加
ここまで実装したクラスのForwardをTensorから実行できるようにしておきます。
Tensor.Processing.csnamespace Rein { public partial class Tensor { public Tensor Detach(){ return new Detach().Forward(this); } public Tensor Squeeze(int dim){ return new Squeeze(dim).Forward(this); } public Tensor Unsqueeze(int dim = 0){ return new Unsqueeze(dim).Forward(this); } public Tensor Reshape(List<int> shape){ return new Reshape(shape).Forward(this); } } }これでTensor側でいつでも操作できるようになりました。
終わりに
今回は、ロス関数とTensorの操作関数を定義しました。ロス関数は他にもクロスエントロピーとかがよく使うと思いますが、現時点では使わなさそうなので必要になったら実装しようと思います。
次はOptimizerの実装を行います。
- 投稿日:2021-02-28T08:31:29+09:00
C# - CheckBoxのサンプル コードべた書き(Visual Studio不使用)/ClickイベントとCheckedChangedイベントの違い
目次: C# - Windows Formsでよく使うコントロールたち (Visual Studioなし環境向け) - Qiita
画面キャプチャ
Click
イベントとCheckedChanged
イベントの違い
CheckedChanged
イベントは、ユーザー操作だけでなく、プログラムからCheckBoxのChecked
プロパティを操作した場合もコールされる。Click
イベントは、ユーザー操作からは呼ばれる。「クリック(Click)」という名称だが、スペースキーでChecked
状態を操作しても呼んでくれる。プログラムからCheckBoxのChecked
プロパティを操作した場合はコールされない。確認用コード
確認用コードusing System; using System.Drawing; using System.Windows.Forms; class CheckBoxSample:Form { CheckBox checkBox; CheckBoxSample() { Text = "CheckBox sample"; Controls.Add(checkBox = new CheckBox(){ Location = new Point(10, 10), Size = new Size(150, 25), Text = "sample", }); // 用途に合わせて、どちらか一方を使うとよいかと思います。 checkBox.CheckedChanged += CheckBoxCheckedChanged; checkBox.Click += CheckBoxClick; Button button; Controls.Add(button = new Button(){ Location = new Point(10, 50), Size = new Size(150, 25), Text = "test", }); button.Click += (s,e)=>{checkBox.Checked = !checkBox.Checked;}; } // CheckedChangedイベントは、 // ユーザー操作だけでなく、 // プログラムからCheckBoxのCheckedプロパティを操作した場合もコールされる。 void CheckBoxCheckedChanged(object sender, EventArgs e) { // window title if ( checkBox.Checked ) { Text = "Checked"; } else { Text = "Not checked"; } } // Clickイベントは、 // ユーザー操作からは呼ばれる。 // (「クリック(Click)」という名称だが、スペースキーでCheck状態を操作しても呼んでくれる。) // プログラムからCheckBoxのCheckedプロパティを操作した場合はコールされない。 void CheckBoxClick(object sender, EventArgs e) { Console.WriteLine("clicked"); } [STAThread] static void Main() { Application.Run(new CheckBoxSample()); } }テンプレ
テンプレusing System; using System.Drawing; using System.Windows.Forms; class CheckBoxSample:Form { CheckBox checkBox; CheckBoxSample() { Text = "CheckBox sample"; Controls.Add(checkBox = new CheckBox(){ Location = new Point(10, 10), Size = new Size(150, 25), Text = "sample", }); // 用途に合わせて、どちらか一方を使うとよいかと思います。 //checkBox.Click += CheckBoxClick; checkBox.CheckedChanged += CheckBoxCheckedChanged; } //void CheckBoxClick(object sender, EventArgs e) void CheckBoxCheckedChanged(object sender, EventArgs e) { // window title if ( checkBox.Checked ) { Text = "Checked"; } else { Text = "Not checked"; } } [STAThread] static void Main() { Application.Run(new CheckBoxSample()); } }参考サイト
- 投稿日:2021-02-28T07:51:02+09:00
C# - Buttonのサンプル コードべた書き(Visual Studio不使用)
目次: C# - Windows Formsでよく使うコントロールたち (Visual Studioなし環境向け) - Qiita
画面キャプチャ
テンプレ
using System; using System.Drawing; using System.Windows.Forms; class ButtonSample:Form { ButtonSample() { var button = new Button(){ Location = new Point(10, 10), Size = new Size(150, 25), Text = "push", }; Controls.Add(button); button.Click += ButtonClick; // button.Click += (sender,e)=>{MessageBox.Show("clicked!");}; } void ButtonClick(object sender, EventArgs e) { MessageBox.Show("clicked!"); } [STAThread] static void Main() { Application.Run(new ButtonSample()); } }参考サイト
- 投稿日:2021-02-28T07:32:19+09:00
C# - ステータスバー(StatusStrip)のサンプル コードべた書き(Visual Studio不使用)
目次: C# - Windows Formsでよく使うコントロールたち (Visual Studioなし環境向け) - Qiita
画面キャプチャ
テンプレ
using System; using System.Drawing; using System.Windows.Forms; class StatusStripSample:Form { ToolStripStatusLabel statusLabel; StatusStripSample() { var statusStrip = new StatusStrip(); Controls.Add(statusStrip); statusStrip.Items.Add(statusLabel = new ToolStripStatusLabel(){ Text = "This is a sample.", }); } [STAThread] static void Main() { Application.Run(new StatusStripSample()); } }参考サイト
- 投稿日:2021-02-28T03:05:22+09:00
[C#] メソッド/コンストラクター/null/LINQについて
メソッド名は引数が違えば同じ名前でもOK
using System; namespace ClassSample { class Program { static void Main(string[] args) { var scores = new int[] { 55, 70, 43, 79, 17, 31, 48 }; var total = ArrayUtils.Total(scores); Console.WriteLine(total); var scores2 = new double[] { 5.8, 6.2, 5.9, 2.1, 6.6, 9.5, 3.8 }; var total2 = ArrayUtils.Total(scores2); Console.WriteLine(total2); } static class ArrayUtils { //①int型の配列の数値の合計を求める public static int Total(int [] numbers) { var total = 0; foreach (var n in numbers) { total += n; } return total; } // ②double型の配列内の数値の合計を求める public static double Total(double[] numbers) //同じメソッド名で引数が異なる { var total = 0.0; foreach (var n in numbers) { total += n; } return total; } } } }メソッドの省力可能な引数
ClassSampleusing System; namespace ClassSample { class Program { static void Main(string[] args) { var person = new Person { FirstName = "たかし", LastName = "小林" }; var name1 = person.AddTitle("先生"); //引数を指定して呼び出す var name2 = person.AddTitle(); //引数を省略して呼び出す Console.WriteLine(name1); Console.WriteLine(name2); } } class Person { public string FirstName { get; set; } public string LastName { get; set; } public string AddTitle(string title = "様") //titleは省力可能な引数 { return $"{LastName}{FirstName} {title}"; } } }コンストラクターの定義方法
ClassSampleusing System; namespace ClassSample { class Program { static void Main(string[] args) { var mypet = new VirtualPet(); //インスタンス生成時にコンストラクタが呼び出される Console.WriteLine($"Name: {mypet.Name}"); Console.WriteLine($"Mood: {mypet.Mood}"); Console.WriteLine($"Energy: {mypet.Energy}"); } } class VirtualPet { public string Name { get; set; } public int Mood { get; set; } public int Energy { get; set; } public VirtualPet() //コンストラクターの名前はクラスと同じにする { Name = "エイミー"; Mood = 5; Energy = 100; } } }引数のあるコンストラクター
ClassSampleusing System; namespace ClassSample { class Program { static void Main(string[] args) { Console.Write("名前を入力してください⇒"); var name = Console.ReadLine(); var mypet = new VirtualPet(name); //引数を指定したコンストラクターが呼ばれる Console.WriteLine($"Name: {mypet.Name}"); Console.WriteLine($"Mood: {mypet.Mood}"); Console.WriteLine($"Energy: {mypet.Energy}"); } } class VirtualPet { public string Name { get; set; } public int Mood { get; set; } public int Energy { get; set; } public VirtualPet(string name) //コンストラクターの名前はクラスと同じにする { Name = name; Mood = 5; Energy = 100; } } }プロパティに特別な動作を加える
class Book { public static int Count { get; set; } public static void ClearCount() { Count = 0; } public string Title { get; set; } public string Auther { get; set; } public int Pages { get; set; } int _rating; //フィールドの定義 public int Rating //プロパティの定義 { //getアクセサ、setアクセサー内は、メソッドと同様、複数行の処理が欠ける get //getアクセサーの定義 { return _rating; //参照時にこのコードが実行される } set //setアクセサーの定義 { if (value <= 1) //1以下の値はすべて1をセット { _rating = 1; } else if (value >= 6) //6以上の値はすべて5をセット { _rating = 5; } else { _rating = value; } } }構造体の定義方法
ClassSampleusing System; namespace ClassSample { //card構造体 struct Card { static void Main(string[] args) { var card = new Card('S', 8); //構造体もnew演算子でインスタンスを生成する card.Print(); if (card.Suit == 'D') { Console.WriteLine("ダイヤです"); } else { Console.WriteLine("ダイヤではありません"); } } public char Suit { get; private set; } //読み取り専用プロパティ public int Number { get; private set; } //読み取り専用プロパティ //コンストラクター public Card(char suit, int number) { Suit = suit; Number = number; } //メソッド public void Print() { var s = ""; switch (Suit) { case 'H': s = "ハート"; break; case 'D': s = "ダイヤ"; break; case 'S': s = "スペード"; break; case 'C': s = "クラブ"; break; } Console.WriteLine($"{s} {Number}"); } } }列挙型の定義方法
ClassSampleusing System; namespace ClassSample { enum CardSuit { Club, Spade, Heart, Diamond } //card構造体 struct Card { static void Main(string[] args) { var card = new Card(CardSuit.Heart, 8); card.Print(); if (card.Suit == CardSuit.Diamond) { Console.WriteLine("ダイヤです"); } else { Console.WriteLine("ダイヤではありません"); } } public CardSuit Suit { get; private set; } //SuitはCardSuit型のプロパティ public int Number { get; private set; } //コンストラクター public Card(CardSuit suit, int number) //第一引数ではCardSuit型を受け取る { Suit = suit; Number = number; } //メソッド public void Print() { var s = ""; switch (Suit) { case CardSuit.Heart: s = "ハート"; break; case CardSuit.Diamond: s = "ダイヤ"; break; case CardSuit.Spade: s = "スペード"; break; case CardSuit.Club: s = "クラブ"; break; } Console.WriteLine($"{s} {Number}"); } } }null
nullはオブジェクトが生成されなかった状態や、指定した条件に合うオブジェクトが見つからなかった状態を表すときによく使われるClassSampleusing System; namespace ClassSample { class Program { static void Main() { var book = GetBook(); //nullかどうかでGetBookが成功したかどうかを判断 if (book == null) { Console.WriteLine("bookオブジェクトは生成できませんでした"); } else { Console.WriteLine($"{book.Title} {book.Auther}"); } } private static Book GetBook() { var line = Console.ReadLine(); var items = line.Split(','); if (items.Length != 2) { return null; //入力したデータが正しくない場合はnullを返す } var book = new Book { Title = items[0], Auther = items[1], }; return book; } } class Book { public string Title { get; set; } public string Auther { get; set; } public int Pages { get; set; } public int Rating { get; set; } } }Listの使い方
ClassSampleusing System; using System.Collections.Generic; //Listを使う際に必要 namespace ClassSample { class Program { static void Main() { var lines = new List<string> { "おはよう", "こんにちは", "こんばんは" }; lines.Add("おやすみ"); //Listに追加する際はAddを使う lines.Add("さようなら"); lines.RemoveAt(2); //リストから指定の要素を削除する foreach (var s in lines) { Console.WriteLine(s); } var count = lines.Count; //リストの要素数を取得するにはCountを使う Console.WriteLine(count); lines.Clear(); //すべての要素を削除するときはClearメソッドを使う Console.WriteLine($"{lines.Count}"); } } }ClassSampleusing System; using System.Collections.Generic; namespace ClassSample { class Program { static void Main() { var books = new List<Book>(); //Book型のリストを生成 var book1 = new Book("吾輩は猫である", "夏目漱石", 620, 4); books.Add(book1); //1冊目を追加 var book2 = new Book("人間失格", "太宰治", 300, 5); books.Add(book2); //2冊目を追加 //省略もできる //books.Add(new Book("吾輩は猫である", "夏目漱石", 620, 4)); //books.Add(new Book("人間失格", "太宰治", 300, 5)); foreach (var book in books) { Console.WriteLine($"{book.Title} {book.Auther} {book.Pages} {book.Rating}"); } } } class Book { public string Title { get; set; } public string Auther { get; set; } public int Pages { get; set; } public int Rating { get; set; } public Book(string title, string auther, int pages, int rating) { Title = title; Auther = auther; Pages = pages; Rating = rating; } } }LINQ
Whereメソッドとラムダ式
例1
```C#:ClassSample
using System;
using System.Collections.Generic;
using System.Linq; // LINQを使うのに必要namespace ClassSample
{
class Program
{
static void Main()
{
var nums = new List { 12, 69, 38, 32, 63, 55, 71, 93, 39, 48 };var query = nums.Where(x => x >= 50); //Whereは条件に一致した要素を抜き出す foreach (var n in query) { Console.WriteLine(n); } } }}
```
例2ClassSampleusing System; using System.Collections.Generic; using System.Linq; // LINQを使うのに必要 namespace ClassSample { class Program { static void Main() { var words = new List<string> { "effect", "access", "condition", "sign", "profit", "line", "result" }; var query = words.Where(x => x.Length == 6); //長さ6の文字列だけ取り出す foreach (var word in query) { Console.WriteLine(word); } } } }Selectメソッド
Selectメソッドを使えばコレクションの各要素を別の値に変更できるClassSampleusing System; using System.Collections.Generic; using System.Linq; // LINQを使うのに必要 namespace ClassSample { class Program { static void Main() { var nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, }; var query = nums.Select(x => x * 2); //各要素を2倍する foreach (var n in query) { Console.WriteLine(n); } } } }各要素の長さを求める
var query = words.Select(x => x.Length);1つの要素を全て取り出す( ↓タイトルを取り出す)
var query = words.Select(x => x.Title);OrderByメソッド / OrderByDescendingメソッド
コレクション内のデータを指定した順番で取り出せる
ClassSampleusing System; using System.Collections.Generic; using System.Linq; // LINQを使うのに必要 namespace ClassSample { class Program { static void Main() { var nums = new int[] { 6, 4, 3, 2, 5, 1, 9, 8, 7, }; var query = nums.OrderBy(x => x); //小さい順に並べる foreach (var n in query) { Console.WriteLine(n); } } } }大きい順に並べる
var query = nums.OrderByDescending(x => x);複数のLINQメソッドを連結させることも可能
ドットでつなげる
var query = books.Where(x => x.Rating == 5).Select(x => x.Title);Take() で先頭から指定した個数を取り出す
ClassSampleusing System; using System.Collections.Generic; using System.Linq; // LINQを使うのに必要 namespace ClassSample { class Program { static void Main() { var nums = new int[] { 6, 4, 3, 2, 5, 1, 9, 8, 7, }; var query = nums.OrderByDescending(x => x) .Take(3); //先頭の3つを取り出す foreach (var n in query) { Console.WriteLine(n); } } } }Distinctは重複した要素を排除できる
var query = books.Where(x => x.Rating >= 4).Select(x => x.Auther).Distinct;Anyは条件を満たす要素が含まれているか判断できる
var any = nums.Any(x => x < 0); //マイナス値があるか調べるIEnumerableを配列に変換するにはToList / ToArrayメソッドを使用する
ToListClassSampleusing System; using System.Collections.Generic; using System.Linq; // LINQを使うのに必要 namespace ClassSample { class Program { static void Main() { var nums = new int[] { 62, 4, 34, 26, 15, 51, 97, 84, 72, }; var list = nums.Where(x => x <= 10) .ToList(); //Whereの結果をList<int>に変換する Console.WriteLine(list[0]); } } }ToArray()はString[]に変更できる
var array = words.OrderBy(x => x).Array();
- 投稿日:2021-02-28T02:07:07+09:00
【Unity】WaitUntilが正常に止まらないときに確認すること
はじめに
ゲーム内で準備が整うまで処理を停止するときに使う WaitUntil が正常に停止してくれない!!! となったので、備忘録を兼ねて書き残しておきます。
環境
- Windows 10
- Unity 2019.4.5f1(64-bit)
WaitUntil とは
WaitUntil は、
yield return new WaitUntil(() => bool値);
のように書き、bool 値が true になるまで処理を中断します。以下にサンプルとして、ゲームのスタート画面の例を示します。
WaitUntilSample.csusing System.Collections; using UnityEngine; public class WaitUntilSample : MonoBehaviour { void Start() { StartCoroutine("Sample"); } IEnumerator Sample () { Debug.Log("Waiting..."); yield return new WaitUntil(() => Input.GetKeyDown("space")); Debug.Log("Let's play!!!"); // 以下ゲーム開始の処理 } }ユーザーがスペースキーを押すまで、
yield return new WaitUntil(() => Input.GetKeyDown("space"));
の部分で処理が止まります。ゲーム開始の処理はユーザーがスペースキーを押すまで行われず、ユーザーを待つことが出来ます。
このほかにも、ノベルゲームでのセリフ送りやロード画面など、ユーザーの入力を待機させたいときや準備が整うまで処理を停止する場合は
WaitUntil
を使うと比較的綺麗に実装することができます。詰まったところ
以下のコードを書いたところ、コンソールにバグは出ないものの、偶数番目の
WaitUntil
は正常に作動しませんでした。WaitUntilTest.csusing System.Collections; using UnityEngine; public class WaitUntilTest : MonoBehaviour { void Start() { StartCoroutine("Sample"); } IEnumerator Sample () { Log("Waiting..."); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("first"); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("second"); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("third"); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("fourth"); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("fifth"); yield return new WaitUntil(() => Input.GetKeyDown("space")); Log("sixth"); } void Log(string message) { Debug.Log(message + " (" + Time.time.ToString("n4") + " s)"); } }スペースキーを 1 回押すと 2 つの出力を受けていることが分かります。
少々話はそれますが、さらに注意深く観察すると、同時にコンソールに出ているのにも関わらず時間が異なっていることも分かります。
Debug.Log
の処理はほぼ 0 秒で行われるため、それ以外の処理に時間がかかっているということです。実は、
WaitUntil
の判定には最低 1 フレームかかるという仕様があり、詳しくは先人の「UnityのWaitUntilは使わない」の記述に譲りますが、この仕様が影響していると考えられます。解決法
結論から言うと、
yield return null
をyield return new WaitUntil(() => Input.GetKeyDown("space"))
の後ろに書くと、予想通りに動きます。WaitUntilTest.csusing System.Collections; using UnityEngine; public class WaitUntilTest : MonoBehaviour { void Start() { StartCoroutine("Sample"); } IEnumerator Sample () { Log("Waiting..."); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("first"); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("second"); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("third"); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("fourth"); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("fifth"); yield return new WaitUntil(() => Input.GetKeyDown("space")); yield return null; Log("sixth"); } void Log(string message) { Debug.Log(message + " (" + Time.time.ToString("n4") + " s)"); } }やりました!成功です!
バグの原因究明
Input.GetKeyDown
は該当のキーを押すと、1 フレームの間ずっと true となります。ふつう、コードは 1 フレームの中で実行されるので、
WaitUntil
の条件を満たすと、即座(フレームを跨がず)に次のWaitUntil
まで到達します。ここで、1 フレームの間
Input.GetKeyDown
の値は常に true なので、2 つ目のWaitUntil
に到達した時点でも true を返します。その結果、2 つ目の
WaitUntil
はスペースキーを押さなくても突破されてしまうというわけでした。ちなみに、
WaitUntil
は一度止まってから動くまで最低でも 1 フレームかかるので、3 つ目が一瞬で突破されることはありません。したがって、処理を 1 フレーム分だけ中断させることができる
yield return null
をWaitUntil
の直後に書くことで、このバグをスマートに解決することができたというわけです。終わりに
バグと向き合ったときに既存の記事が無かったようなので自分で記事にしました。(もしあったらごめんなさい)
テンポを重視してところどころ説明を端折ってしまったため、内容は完全な初心者向けではなくなってしまったかもしれませんが、この記事で
WaitUntil
への理解が深まれば幸いです。