- 投稿日:2020-01-12T23:43:00+09:00
ポケモン剣盾の乱数調整作業自動化
はじめに
ポケモン剣盾の乱数調整作業(+その他)の自動化のためのハードとソフトを作りました.
乱数調整の日付変更の作業などを自動でやりたいという人におすすめです.大量の日付変更(一万回くらい)も可能になります.
多少の電子工作の知識を必要とします.
注意:ここにあるものはすべて自己責任にて行ってください.モチベーション
- さびたコイルさん(@rusted_coil)がポケモン剣盾用乱数調整ツール 1-Star Seed Searchを公開してくださっているためレイド乱数ができるようになった.
- シード特定のための作業や最後の乱数消費作業はすべて手動でやるのは少し大変なので,自動化したい.
- @SobassyさんがArduinoを使ってSwitchを操作する方法を紹介してくださっている.
- 乱数調整は何種類か操作が必要なので,Arduinoのファームウェアを書き込みなおす必要性がある.それは面倒なのでPCとArduinoを接続してPCのプログラムからArduinoを操作すれば楽になりそう.
概要
- ArduinoはHID化してSwitchにコントローラとして接続できる.このコントローラをPCのソフトウェアから操作したいので,ArduinoのUARTで通信する.
- ArduinoはPCから受け取ったボタン命令をただ実行するだけで,内部に「日付を進める」などの操作を記述しない.操作を作る時,ボタンの間のdelayを調整することになるが,こうすることでいちいちArduinoに書き込む必要がない.
- PC側のプログラムはGUIを作りたかったのでVisualStudioでフォームアプリケーションを作った.
必要なもの
- .NET環境のあるWindowsPC (Windows 10を推奨 それ以外で動作確認していないので)
- Arduino Leonard 若しくはその互換
- 自分はこれを買いました
- FTDI FT232 (USBシリアル変換)
- ブレッドボード,ジャンパ線若しくはユニバーサル基板
- 詳しくは後述
- ユニバーサル基板を使うならはんだごて,はんだなども必要
- SwitchとArduinoをつなぐUSBケーブル (A - microB等)
- PCとArduinoをつなぐUSBケーブル (A - microB等)
手順1 Arduino+FT232基板の制作
- Arduino LeonardのUARTをFT232に接続します.
- 図のように接続してください.
- 注意点
- TX, RXはクロスするように接続(TX同士,RX同士を接続しないように)
- FT232側にCTS, RTSなどのピンがあるかもしれないが,何も接続しなくてよい
- ArduinoのUARTの電圧は5Vなので,FT232側で5Vに設定する(5Vと3.3Vを切り替えるスイッチが存在する.ない場合はそもそも5Vのものを買う)
自分は以下のようにユニバーサル基板にはんだ付けして接続しました.
ブレッドボードでもいいですが個人的には場所をとる上に接触不良しやすいのではんだ付けをお勧めします.手順2 Arduinoのファームウェアの書き込み
- 1. Arduino IDEをインストールする
- ここにアクセスし,JUST DOWNLOADを押す.(お財布に余裕のある方はCONTRIBUTE & DOWNLOAD)
- 2. Switch操作用ライブラリをインストールする
- 3. Arduino STLをインストールする
- ここから最新のZIPをダウンロードし,解凍する
- 2と同様に
[Arduinoがインストールされたディレクトリ]/libraries
の中に配置する- 4.
[Arduinoがインストールされたディレクトリ]/hardware/arduino/avr/boards.txt
を開き,285, 286行目を以下のように変更する.leonardo.vid=0x0f0d leonardo.pid=0x0092同ファイルの311, 312行目を以下のように変更する.
leonardo.build.vid=0x0f0d leonardo.build.pid=0x0092
- 5. Arduinoに書き込むファームウェアをダウンロードする.
- ここにアクセスし,
Clone or download
→Download ZIP
して解凍- Arduino IDEを開き,
PokemonSWSHAutomation-master\arduino_firmware\pokemon_automation_arduino_firmware\pokemon_automation_arduino_firmware.ino
を開く.- 6. ArduinoをUSBケーブルでPCに接続する
- 7. Arduino IDEでボードとポートを以下のように選択
- ポート番号はPCによって変わります.
- 8. 図に示した書き込みボタンをクリックし,待つ.正しく書き込めたら図のように「ボードへの書き込みが完了しました」というメッセージが出る.
手順3 Switch,PC,Arduinoを接続し,PCプログラムを起動する
- 1. Switchのホーム画面からコントローラー→持ちかた/順番を変えるに進み,コントローラを待機する状態にする.
- 2. ArduinoとSwitchを接続する.FT232のUSBポートと間違えないように注意.ファームウェアが正しく書き込まれていればコントローラーとして認識される.
- このときFT232側をPCに先に繋いでいると給電されている可能性があるため,PCとの接続は切っておいたほうが良い.
- 3. FT232とPCを接続し,手順2でダウンロードした
PokemonSWSHAutomation-master\PokemonAutomation\PokemonAutomation\bin\Release\PokemonAUtomation.exe
を実行する.以下,ソフトの使い方の説明.使い方説明
- 初めにポートを選択してFT232に接続する.
- 他にUSBデバイスをつないでいない場合(COMXが他にない場合)は起動と同時に繋がっている.もし複数ある場合はクリックしてプルダウンし,FT232のポートを選択すると繋がる.
- 正しく繋がっていれば,コントローラ部分のボタンを押せばSwitchを操作できる.例えばHomeを押せばHome画面へ行くはず.
- 日付を設定する
- まずSwitchの設定の
インターネットで時間を合わせる
をOFFにしておく- 今のSwitchの時間をソフトの右上の部分に設定する.クリックしてプルダウンすると年月日が選択できる.日を決定するとその日付に設定される.ボックス下の
Date
に設定された日付が表示されるので,それで確認する.日付進めボタン(シード特定用)の使い方
この状態から
+1 Day
+3 Days
+4 Days
を押すとそれぞれ1日,3日,4日進む.
- シード特定では
+3 Days
を押しまくることになるこのボタンはトグルボタンとなっているので,もう一度クリックすると一連の動作を終えた後停止する.
日付進めボタン(シード特定後)の使い方
- ランクバトルを行い,対戦を終えた後インターネットの接続を切りローカル通信の状態にする.これでSwitchの日付を変更するとゲーム内で日付が変更された判定となる.
- コントローラーパネルのボタンを押してこの画面まで進める.
- カーソルが
現在の日付と時刻
に合っていることを確認
+ N Days
ボタンの左のボックスに進めたい日付を入れ,+ N Days
ボタンをクリックすると,日付の変更が行われる.
- これをワイルドエリアでやると高確率でポケモンがエラーを吐いて終了するので,ワイルドエリアにいない方が良い.また,それでも落ちる可能性はあるので,小分けにしてセーブを挟んだ方が安全.
- このボタンはトグルボタンとなっているので,もう一度クリックすると一連の動作を終えた後停止する.
Loto ID(IDくじ)ボタンの使い方
- ランクバトルを行い,対戦を終えた後インターネットの接続を切りローカル通信の状態にする.
- ポケモンセンターのロトミの前までくる.Aボタンを押したらロトミに話しかけられる状態にする.
- その状態でLoto IDボタンを押すと日付を変更し,IDくじを引くことを繰り返す.
- このボタンはトグルボタンとなっているので,もう一度クリックすると一連の動作を終えた後停止する.
最後に
- 投稿日:2020-01-12T23:42:38+09:00
C# GUIアプリケーションからPythonスクリプトを実行する
はじめに
Pythonには機械学習をはじめとする優れたライブラリがたくさんある。一方C#はGUIアプリケーションの開発に広く利用されている言語である。したがってPythonスクリプトをC#アプリケーションから呼び出すことができれば、C#アプリケーション開発者にとって便利であるし、何よりGUIアプリケーションの幅も広がるはずだ。そこで今回、C#のGUIアプリケーションからPythonスクリプトを呼び出す方法について調べ、プロトタイプを作成してみた。
環境
- Windows10
- C#
- Python
開発したいプロトタイプ
開発したいプロトタイプの要件を以下に洗い出してみた。
- Pythonのパス、実行ディレクトリ(Working Directory)、Pythonスクリプトを指定し、実行することができる。
- 実行に長時間かかる場合を考慮し、途中で処理をキャンセルすることができる。
- 実行に長時間かかる場合に進捗状況が分かるよう、全ての標準出力、標準エラー出力をGUIのTextBoxに表示する。
- 以前に書いた 標準入出力を介してMOLファイルをSMILESに変換する のように、標準入力を受け取るPythonスクリプトの場合、ファイルを介さずにGUI側のデータをPythonスクリプトに渡すことにより、実行結果を手軽に受け取ることができる。このため標準入力も指定できるようにしたい。
- Pythonスクリプトの終了コードにより、正常終了かエラー終了かを判定し、MessageBoxにより表示する。
できたもの
画面
画面の説明
- Python Pathには、python.exeの場所をフルパスで指定する。Anacondaの場合は、Anacondaの仮想環境のpython.exeの場所を調べて指定する。
- Working Directoryには、Pythonスクリプトの実行ディレクトリを指定する。引数にファイルパスを指定する場合は、ここを起点とする相対パスで記載することもできる。
- Python Commandには、Pythonスクリプトの場所をフルパスで指定する。また引数があればそれも指定する。
- Standard Input には、Pythonスクリプトの標準入力に渡したいデータを入力する。標準入力を使わないPythonスクリプトの場合、無視される。
- Standard Output and Standard Errorには、Pythonスクリプトの全ての標準出力、標準エラー出力が表示される。
- 「Execute」ボタンにより処理を開始し、「Cancel」ボタンにより処理をキャンセルすることができる。
ソース
ソースは以下の通りだ。とても長くなったが、編集が面倒なためそのまま張り付ける(手抜き)。デザイン側のコードは両略した。GUI部品のオブジェクトの変数名は、コードから読み取ってほしい。ソースの解説は次項で説明する。
using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; namespace PythonCommandExecutor { public partial class Form1 : Form { private Process currentProcess; private StringBuilder outStringBuilder = new StringBuilder(); private int readCount = 0; private Boolean isCanceled = false; public Form1() { InitializeComponent(); } /// <summary> /// Textboxに文字列追加 /// </summary> public void AppendText(String data, Boolean console) { textBox1.AppendText(data); if (console) { textBox1.AppendText("\r\n"); Console.WriteLine(data); } } /// <summary> /// 実行ボタンクリック時の動作 /// </summary> private void button1_Click(object sender, EventArgs e) { // 前処理 button1.Enabled = false; button2.Enabled = true; isCanceled = false; readCount = 0; outStringBuilder.Clear(); this.Invoke((MethodInvoker)(() => this.textBox1.Clear())); // 実行 RunCommandLineAsync(); } /// <summary> /// コマンド実行処理本体 /// </summary> public void RunCommandLineAsync() { ProcessStartInfo psInfo = new ProcessStartInfo(); psInfo.FileName = this.textBox2.Text.Trim(); psInfo.WorkingDirectory = this.textBox3.Text.Trim(); psInfo.Arguments = this.textBox4.Text.Trim(); psInfo.CreateNoWindow = true; psInfo.UseShellExecute = false; psInfo.RedirectStandardInput = true; psInfo.RedirectStandardOutput = true; psInfo.RedirectStandardError = true; Process p = Process.Start(psInfo); p.EnableRaisingEvents = true; p.Exited += onExited; p.OutputDataReceived += p_OutputDataReceived; p.ErrorDataReceived += p_ErrorDataReceived; p.Start(); // 標準入力への書き込み using (StreamWriter sw = p.StandardInput) { sw.Write(this.textBox5.Text.Trim()); } //非同期で出力とエラーの読み取りを開始 p.BeginOutputReadLine(); p.BeginErrorReadLine(); currentProcess = p; } void onExited(object sender, EventArgs e) { int exitCode; if (currentProcess != null) { currentProcess.WaitForExit(); // 吐き出されずに残っているデータの吐き出し this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false))); outStringBuilder.Clear(); exitCode = currentProcess.ExitCode; currentProcess.CancelOutputRead(); currentProcess.CancelErrorRead(); currentProcess.Close(); currentProcess.Dispose(); currentProcess = null; this.Invoke((MethodInvoker)(() => this.button1.Enabled = true)); this.Invoke((MethodInvoker)(() => this.button2.Enabled=false)); if (isCanceled) { // 完了メッセージ this.Invoke((MethodInvoker)(() => MessageBox.Show("処理をキャンセルしました"))); } else { if (exitCode == 0) { // 完了メッセージ this.Invoke((MethodInvoker)(() => MessageBox.Show("処理が完了しました"))); } else { // 完了メッセージ this.Invoke((MethodInvoker)(() => MessageBox.Show("エラーが発生しました"))); } } } } /// <summary> /// 標準出力データを受け取った時の処理 /// </summary> void p_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e) { processMessage(sender, e); } /// <summary> /// 標準エラーを受け取った時の処理 /// </summary> void p_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e) { processMessage(sender, e); } /// <summary> /// CommandLineプログラムのデータを受け取りTextBoxに吐き出す /// </summary> void processMessage(object sender, System.Diagnostics.DataReceivedEventArgs e) { if (e != null && e.Data != null && e.Data.Length > 0) { outStringBuilder.Append(e.Data + "\r\n"); } readCount++; // まとまったタイミングで吐き出し if (readCount % 5 == 0) { this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false))); outStringBuilder.Clear(); // スレッドを占有しないようスリープを入れる if (readCount % 1000 == 0) { Thread.Sleep(100); } } } /// <summary> /// キャンセルボタンクリック時の動作 /// </summary> private void button2_Click(object sender, EventArgs e) { if (currentProcess != null) { try { currentProcess.Kill(); isCanceled = true; } catch (Exception e2) { Console.WriteLine(e2); } } } private void button3_Click(object sender, EventArgs e) { // 標準入力エリアのクリア this.textBox5.Clear(); // 標準出力エリアのクリア this.textBox1.Clear(); } } }ソース解説
基本的には参考文献の寄せ集めになるのだが、説明を以下に記載する。
- RunCommandLineAsyncメソッド内でProcessクラスによりPythonスクリプトを実行している。
p.Start()
以降は処理が非同期になるため、これ以降UIを操作する場合は、UIスレッドから実行しないと怒られてしまう。this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
のような呼び出しがところどころあるのはこのためである。p.EnableRaisingEvents = true;
,p.Exited += onExited;
により、プロセス終了時にonExitイベントハンドラが実行されるため、ここに後始末的な処理や、終了コードの判定、完了ダイアログの表示等を記載している。- キャンセルについては、キャンセル時に実行されるイベントハンドラの中でProcessクラスのKillメソッドを呼び出している。するとonExitイベントハンドラが実行され、通常終了時と同じになるため、それ以外に特別なことはしていない。
- 標準入力にデータを食わせるところは、
using (StreamWriter sw = p.StandardInput)
から始まるところでやっている。- 標準出力、標準エラー出力の取り出しについては、
p.OutputDataReceived += p_OutputDataReceived;
,ErrorDataReceived += p_ErrorDataReceived;
によりそれぞれのイベントハンドラで受け取った標準出力、標準エラー出力を処理するようにしている。p.BeginOutputReadLine();
,p.BeginErrorReadLine();
により1行出力がある度にそれぞれのイベントハンドラが実行されるため、その中でTextBoxへの出力を行っている。1行毎にTextBoxに書き出すと大量の出力があるアプリの場合にGUIの処理に時間がかかる可能性もあるため、ある程度まとめて出力する等の工夫を行っている。実行例① 出力の多いPythonスクリプト(途中でエラー)を実行する
以下はサンプルとして作成した、ある程度標準出力や標準エラー出力の多いPythonスクリプトを実行した例である。標準エラーに出力されたエラーメッセージもTextBoxに出力され、かつ終了コードによりエラーのMessageBoxが表示されていることが分かる。
実行例② 標準入力を介してPythonスクリプトを実行する
以下は、「標準入出力を介してMOLファイルをSMILESに変換する 」のスクリプトを本GUIを通して実行した図である。簡単なPythonスクリプトを書くだけでC#とPythonの処理結果の受け渡しができることを実感してもらえると思う。
おわりに
- async/awaitを使った方法で当初進めていたが、UIのデッドロックらしき現象が発生し、丸一日かけても解決できなかったため断念した。
- Pythonスクリプトの標準出力に進捗情報を出力することによって、C#側でプログレスバーによる進捗表示も簡単に行えると思う。
- 動作もまずまず安定しているため、このプロトタイプをベースに今後、C#からPythonの便利な機能をガンガン使い、魅力的なアプリを作ってみたい。
参考文献
- 投稿日:2020-01-12T21:08:44+09:00
ローカルでpaizaスキルチェックを解けるようにしてみた。(C#)
今回やること
C#でpaizaのスキルチェックをローカル環境でも解けるようにする。テストケースもしっかり試せるように作ります
(正しいかどうかのテストコードはまだできていません。)
ローカル環境でできることの利点
- 補完機能が使える
- 使い慣れたテキストエディタが使える
- テンプレートが作れるので、
using宣言
を毎回書く手間が省ける手順
- mono(C#の実行環境)のインストールをする
- runファイルをカレントディレクトリ(プロジェクト直下)に作る
chmod +x run
でrun
に実行権限を与えるmain.cs
をフォルダ直下に作るrunファイルの中身は以下の通りにしてください。
#!/bin/bash mcs ../main.cs main.cs -main:test mono ../main.exe
main.cs
は以下の通りにしてくださいusing System; using System.IO; using static Paiza; public class test{ public static void Main() { var input = Console.In; Console.SetIn(File.OpenText("case1.txt")); try { Paiza.Main(); } finally { Console.SetIn(input); } Console.ReadKey(); } }実行の仕方
- Templateフォルダをコピーし、Templateフォルダの名前を問題番号などに改名する
main.cs
にスキルチェックのコードを書くcase1.txt
にテストケースをコピーする- 実行したいフォルダ内に移動する
../run
を実行するTemplateフォルダ内の
main.cs
は以下のようにしてください//使うusingのみでOKです。 using System; using System.Collections.Generic; public class Paiza{ public static void Main(){ } }Templateフォルダの
case1.txt
は、paizaスキルチェックの問題のテストケースの形式をそのままコピーするだけでOKです。最後に
paizaで自分が書いたコードをGitHubのプライベートリポジトリなどで管理することによって、コードを振り返ったりすることが簡単にできるようになるので、ページを行ったり来たりすることなく、振り返りができます!
- 投稿日:2020-01-12T15:05:11+09:00
.NET Core 汎用ホスト(GenericHost)コンフィグの読み込み
このドキュメントの内容
2019/12 に.NET Core 3.1 が正式リリースされました。2020/11 には .NET 5.0 のリリースが予定されており、そろそろ新しいインフラストラクチャへの移行を考え始めています。
手始めとして、汎用ホスト(GenericHost)関連の情報を再整理していきます。まずはコンフィグの読み込みについてまとめます。このドキュメントの内容は .NET Core 3.1 で確認しています。
既定の構成ファイル(appsettings.json)
実行フォルダに appsettings.json という名前の JSON ファイルが存在する場合、HostBuilder によって自動的に読み込まれます。HostingEnvironment.EnvironmentName で表される環境変数に対応する構成ファイルがある場合にはその構成ファイルも読み込まれます。
HostingEnvironment.EnvironmentName の値が "Deveropment" であり、実行フォルダに次の二つの構成ファイルが存在する場合、同一キーの値は appsettings.Deveropment.json に記載されている値が適用されます。appsettings.json{ "Database": { "ConnectionString": "connectionString for sampleDb", "Name": "sampleDb" } }appsettings.Deveropment.json{ "Database": { "ConnectionString": "connectionString for debugDb", "Name": "debugDb" } }HostBuilderContext.Configuration
読み込まれた構成ファイルの内容は
HostBuilderContext
クラスの Configuration プロパティに格納されます。キーバリュー型のディクショナリに格納され、ネストは Database:ConnectionString のようにコロンで表されます。
このスクリーンショットでは 9 つの Provider が読み込まれていることがわかります。後述する環境変数やコマンドライン引数など様々なコンフィグが一括管理されており、サービス構成時や実行時に参照することができます。
実装例
IServiceCollection
インターフェースの Configure メソッドで指定した型のインスタンスにバインドさせることができます。.NET Core の JSON パーサーによってバインドされます。サービスの構成例// using Microsoft.Extensions.Hosting; // using Microsoft.Extensions.Options; var builder = Host.CreateDefaultBuilder(args) // サービスの構成 .ConfigureServices((HostBuilderContext context, IServiceCollection services) => { // コンフィグを登録 services.Configure<SampleOption>(context.Configuration); // JSONパーサーによってバインドできない内容の場合は独自の後処理を実装します。 services.Configure<SampleOption>((SampleOption option) => { // 独自の後処理 option.Database.ConnectionString = ModifyConnectionString(option); }); // 次のどちらかの方法でサービスを登録 // 既定のバインディング // コンフィグのインスタンスは SampleService クラスのコンストラクタを通じて受け取ります。 services.AddSingleton<IService, SampleService>(); // 既定のバインディングに加えて独自の処理を組み込む必要がある場合、 // IServiceProvider.GetRequiredService メソッドでインスタンスを取得できます。 // 次のコードはコンフィグインスタンスの注入しか行っていませんので、結果的に上と同じ内容になります。 services.AddSingleton<IService, SampleService>((IServiceProvider provider) => { var option = provider.GetRequiredService<IOptions<SampleOption>>(); return new SampleService(option); }); });SampleOptionクラスpublic class SampleOption { public SampleDbConfig Database { get; set; } } public class SampleDbConfig { public string Name { get; set; } public string ConnectionString { get; set; } }SampleServiceクラスinternal interface IService { void Execute(); } internal class SampleService : IService { // サービスの定義に従って IServiceCollection に登録されたコンフィグのインスタンスが注入されます。 public SampleService(IOptions<SampleOption> options) { } }任意の構成ファイル
appsettings.json 以外の JSON 構成ファイルを使用する場合、
IConfigurationBuilder
インターフェースの AddJsonFile メソッドで読み込みます。appsettings.json に同じキーが存在する場合、後から読み込まれたこちらのファイルの値が適用されます。サービスの構成例// using Microsoft.Extensions.Hosting; // using Microsoft.Extensions.Options; var builder = Host.CreateDefaultBuilder(args) // コンフィグの構成 .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) => { // 実行フォルダの MyAppSettings.json を構成に追加します。 // ファイルが存在しない可能性がある場合は optional に true を指定します。 builder.AddJsonFile($"MyAppSettings.json" , optional: true , reloadOnChange: true ); builder.AddJsonFile($"MyAppSettings.{context.HostingEnvironment.EnvironmentName}.json" , optional: true , reloadOnChange: true ); }) // サービスの構成(前述している内容と同じですので割愛しています) .ConfigureServices((HostBuilderContext context, IServiceCollection services) => { // コンフィグを登録 services.Configure<SampleOption>(context.Configuration); // サービスを登録 services.AddSingleton<IService, SampleService>(); });環境変数
環境変数を用いて構成ファイルの値を上書きするには、キーを一致させます。
環境変数は Host.CreateDefaultBuilder メソッドの既定の動作で読み込まれます(「既定の構成ファイル」の項のスクリーンショットの context.Configuration.Providers[3])。構成ファイルに同じキーが存在する場合、後から読み込まれたほうの値が適用されます。
アプリケーション固有のキーの値をバインドさせるには、キー名にそのアプリケーションを表すプレフィクスを付加し、
IConfigurationBuilder
インターフェースの AddEnvironmentVariables メソッドで読み込むのが便利です。なお、AddEnvironmentVariables メソッドで読み込んだ環境変数は新しく context.Configuration.Providers に追加されます。.NET Core の既定の環境変数はプレフィクス DOTNET_ が付加されたキーで定義されています。
例えば、キー DOTNET_ENVIRONMENT の値は HostingEnvironment.EnvironmentName プロパティの値にバインドされ、前述の構成ファイルの読み込みなどに使用されています。サービスの構成例// using Microsoft.Extensions.Hosting; // using Microsoft.Extensions.Options; var builder = Host.CreateDefaultBuilder(args) // コンフィグの構成 .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) => { // 指定したプレフィクス(この例では "SAMPLEAPP_")で始まる環境変数を構成に追加します。 // プレフィクスが除かれたキーで読み込まれます。 builder.AddEnvironmentVariables(prefix: "SAMPLEAPP_"); }) // サービスの構成(前述している内容と同じですので割愛しています) .ConfigureServices((HostBuilderContext context, IServiceCollection services) => { // コンフィグを登録 services.Configure<SampleOption>(context.Configuration); // サービスを登録 services.AddSingleton<IService, SampleService>(); });VisualStudio のデバッグ設定に次のように設定されている場合、"Database:ConnectionString" には前述した構成ファイルの値ではなくこの環境変数の値が適用されます。"Database:Name" は設定されていませんので構成ファイルの値が適用されます。
コマンドライン引数
コマンドライン引数を用いて構成ファイルの値を上書きする場合も、キーを一致させます。
コマンドライン引数は Host.CreateDefaultBuilder メソッドの既定の動作で読み込まれます(「既定の構成ファイル」の項のスクリーンショットの context.Configuration.Providers[4])。構成ファイルや環境変数に同じキーが存在する場合、後から読み込まれたほうの値が適用されます。
追加の構成ファイルや環境変数を読み込む場合でコマンドライン引数の値を最も優先するには、最後に
IConfigurationBuilder
インターフェースの AddCommandLine メソッドで読み込む必要があります。サービスの構成例// using Microsoft.Extensions.Hosting; // using Microsoft.Extensions.Options; var builder = Host.CreateDefaultBuilder(args) // コンフィグの構成 .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) => { // 追加の構成ファイル builder.AddJsonFile($"MyAppSettings.json", optional: true); // 追加の環境変数 builder.AddEnvironmentVariables(prefix: "SAMPLEAPP_"); // 最後にコマンドライン引数を読み込んで上書き builder.AddCommandLine(args); }) // サービスの構成(前述している内容と同じですので割愛しています) .ConfigureServices((HostBuilderContext context, IServiceCollection services) => { // コンフィグを登録 services.Configure<SampleOption>(context.Configuration); // サービスを登録 services.AddSingleton<IService, SampleService>(); });コマンドライン引数は「キー=値」の形式で指定します。"Database:ConnectionString" には前述した構成ファイルの値ではなくこのコマンドライン引数の値が適用されます。"Database:Name" は設定されていませんので構成ファイルの値が適用されます。
コマンドライン引数を指定して起動SampleApp.exe Database:ConnectionString=connectionString
まとめ
構成ファイル・環境変数・コマンドラインによるコンフィグを統合し、特定の型のインスタンスにバインドすることができます。実行環境に依存する値や呼び出し時に指定する値を整理し、適切な箇所で設定するように設計しましょう。
- 投稿日:2020-01-12T12:15:05+09:00
UnityでParticleSystemをInstantiateしてStartRotationを設定する話
UnityでParticleSystemのSizeを軸ごとに変えてその向きをスクリプトから変えたかった。
その時に引っかかった所のメモ。Prefab化したParticleSystemのGameObjectをRotateしても変わらない
Instantiate(p, Vector3.up , transform.rotation).Play();とか、
ParticleSystem pa = Instantiate(p, Vector3.up , Quaternion.identity); pa.transform.Rotate(transform.eularAngles.z);みたいなのはエディタから見ると回転しているように見えますが、Size over LifetimeのX, Yには影響がありません。
ParticleSystemのstartRotationを変えます。直接startRotationを設定する方法はObsolete
pa.startColor = c.color; pa.startRotation = transform.eulerAngles.z;ではなく、一度mainを持ってから設定します。
var ma = pa.main; ma.startColor = c.color; ma.startRotation = transform.eulerAngles.z;ma.startRotationへの書き込みはラジアン
ma.startRotation = transform.eulerAngles.z; Debug.Log(transform.eulerAngles.z + " : " + pa.startRotation);これで見ると一見一致しているように見えますが、InspectorでstartRotationを見ると5桁になります。
ma.startRotation = transform.eularAngles.z / 180f * Mathf.PI; ma.startRotation = transform.eulerAngles.z / 57.29578f;transform.eularAnglesの回転方向と逆
反転します。
ma.startRotation = -transform.eulerAngles.z / 57.29578f;結局
ParticleSystem pa = Instantiate(p, transform.rotation * Vector3.up * rat , Quaternion.identity); var ma = pa.main; ma.startColor = c.color; ma.startRotation = -transform.eulerAngles.z / 57.29578f; pa.Play();Unityバージョン
2019.2.17f1
2020.1.0a18参考にした記事
https://qiita.com/lucifuges/items/e08388a77748168b3b78
https://qiita.com/UnagiHuman/items/caaa1585f7ee201dca7b公式リファレンス
https://docs.unity3d.com/ja/2017.4/ScriptReference/ParticleSystem.html
https://docs.unity3d.com/ja/2017.4/ScriptReference/ParticleSystem.MainModule.html
https://docs.unity3d.com/ScriptReference/ParticleSystem.MainModule-startRotation.html
- 投稿日:2020-01-12T11:46:15+09:00
C#高速/最適化メモ
概要
色々な高速化などの噂について検証してみました
探せばいくらでもあるforとforeachの速度などは載せていませんなるべく二進数換算で小さい値に
2進数換算をした時に、1の数がなるべく少ない方が高速らしいという噂を聞いてやってみた
結果
+n 二進数換算 結果 +m%(+2基準) +2 10 138459ms 0% +127 1111111 148959ms +7% +128 10000000 139022ms +0.4% コード
sample.csvar sw = new System.Diagnostics.Stopwatch(); //----------------- // 計測開始 sw.Start(); // ★処理A for(int i = 0; i < 100000000; i++) { int num = 0; for(int j = 0; j < 1000; j++) { num += 2; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Aにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); sw.Reset(); sw.Start(); // ★処理B for (int i = 0; i < 100000000; i++) { int num = 0; for (int j = 0; j < 1000; j++) { num += 127; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Bにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); sw.Reset(); sw.Start(); // ★処理C for (int i = 0; i < 100000000; i++) { int num = 0; for (int j = 0; j < 1000; j++) { num += 128; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Cにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); System.Threading.Thread.Sleep(10000); }補足
1000回の足し算を1億回やってこの程度(+7%)なのであまり気にしなくていいかもしれない
あと1000回の足し算を1000回程度ならPCの調子か、+128の方が遅いという結果になることも多少あったので、気休め程度に。ビットシフトを使う
2の乗数で割り算をするとき、ビットシフトを使うと高速らしいという話を検証
結果
やり方 結果 +n%(/=基準) /= 20798ms 0% = num/2 20660ms -1% num>>=1 4429ms -79% コード
sample.csfor(int i = 0; i < 10000000; i++) { int num = int.MaxValue; while(num > 2) { num /= 2; //この部分を変更 } }補足
処理時間が5分の1……!
/=2とnum=num/2の差については誤差らしいというのを次項で説明num/=2とnum=num/2
VisualStudioでは逆アセンブリという機能があり、それでデコンパイル状態のコード(アセンブリ言語?)を見ることができる
そこで見てみると、同じらしいnum /= 2; mov eax,dword ptr [ebp-44h] mov ecx,2 cdq idiv eax,ecx mov dword ptr [ebp-44h],eax num2 = num2 / 2; mov eax,dword ptr [ebp-48h] mov ecx,2 cdq idiv eax,ecx mov dword ptr [ebp-48h],eax全く同じ処理をしているので、全く同じ速度ということになるはずということで、誤差ということが判明
構造体とクラス
構造体の方が早いらしい。ただし一定以上のバイト数になるとクラスの方がいいらしい
結果
バイト数 構造体 クラス 処理時間(構造体を100%としたとき) 4 1731ms 5407ms 312% 16 1725ms 5916ms 342% 20 1808ms 6341ms 350% 32 1898ms 7399ms 390% 40 2076ms 7178ms 345% 44 9946ms 8416ms 85% 60 10366ms 8296ms 80% 64 8557ms 9155ms 107% 104 11142ms 10512ms 94% 128 8568ms 12236ms 143%
補足
構造体だと2の乗数で処理速度が上がるらしい
場合と数が大きいほどclassを使うメリットがあり、ぎりぎりまで高速化したい場合はstructか
ただ、さまざまなところで見た16bitが境目という話はなかったし、40~44の間で劇的に構造体が遅くなる理由は見当もつかないので、この話題について掘り下げるのは避けたいと思うその他メモ
・UnityのgameObject.tag=="hoge"より、gameObject.CompareTag("hoge")の方が早いらしい。gameObject.tagのアクセスが処理を食うとか
・stringは極力StringBuilderを使った方が早い
・ifとswitchはifの方が基本早い
・多分岐では上から順に出現頻度が多い条件を書くことで比較回数を減らすことができる
- 投稿日:2020-01-12T11:46:15+09:00
C#高速化メモ
概要
色々な高速化などの噂について検証してみました
探せばいくらでもあるforとforeachの速度などは載せていませんなるべく二進数換算で小さい値に
2進数換算をした時に、1の数がなるべく少ない方が高速らしいという噂を聞いてやってみた
結果
+n 結果 +m%(+2基準) +2 138459ms 0% +127 148959ms +7% +128 139022ms +0.4% コード
sample.csvar sw = new System.Diagnostics.Stopwatch(); //----------------- // 計測開始 sw.Start(); // ★処理A for(int i = 0; i < 100000000; i++) { int num = 0; for(int j = 0; j < 1000; j++) { num += 2; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Aにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); sw.Reset(); sw.Start(); // ★処理B for (int i = 0; i < 100000000; i++) { int num = 0; for (int j = 0; j < 1000; j++) { num += 127; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Bにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); sw.Reset(); sw.Start(); // ★処理C for (int i = 0; i < 100000000; i++) { int num = 0; for (int j = 0; j < 1000; j++) { num += 128; } } // 計測停止 sw.Stop(); // 結果表示 Console.WriteLine("■処理Cにかかった時間"); Console.WriteLine($" {sw.ElapsedMilliseconds}ミリ秒"); System.Threading.Thread.Sleep(10000); }補足
1000回の足し算を1億回やってこの程度(+7%)なのであまり気にしなくていいかもしれない
あと1000回の足し算を1000回程度ならPCの調子か、+128の方が遅いという結果になることも多少あったので、気休め程度に。ビットシフトを使う
2の乗数で割り算をするとき、ビットシフトを使うと高速らしいという話を検証
結果
やり方 結果 +n%(/=基準) /= 20798ms 0% = num/2 20660ms -1% num>>=1 4429ms -79% コード
sample.csfor(int i = 0; i < 10000000; i++) { int num = int.MaxValue; while(num > 2) { num /= 2; //この部分を変更 } }補足
処理時間が5分の1……!
/=2とnum=num/2の差については誤差らしいというのを次項で説明num/=2とnum=num/2
VisualStudioでは逆アセンブリという機能があり、それでデコンパイル状態のコード(アセンブリ言語?)を見ることができる
そこで見てみると、同じらしいnum /= 2; mov eax,dword ptr [ebp-44h] mov ecx,2 cdq idiv eax,ecx mov dword ptr [ebp-44h],eax num2 = num2 / 2; mov eax,dword ptr [ebp-48h] mov ecx,2 cdq idiv eax,ecx mov dword ptr [ebp-48h],eax全く同じ処理をしているので、全く同じ速度ということになるはずということで、誤差ということが判明
構造体とクラス
構造体の方が早いらしい。ただし一定以上のバイト数になるとクラスの方がいいらしい
結果
バイト数 構造体 クラス 処理時間(構造体を100%としたとき) 4 1731ms 5407ms 312% 16 1725ms 5916ms 342% 20 1808ms 6341ms 350% 32 1898ms 7399ms 390% 40 2076ms 7178ms 345% 44 9946ms 8416ms 85% 60 10366ms 8296ms 80% 64 8557ms 9155ms 107% 104 11142ms 10512ms 94% 128 8568ms 12236ms 143%
補足
構造体だと2の乗数で処理速度が上がるらしい
場合と数が大きいほどclassを使うメリットがあり、ぎりぎりまで高速化したい場合はstructか
ただ、さまざまなところで見た16bitが境目という話はなかったし、40~44の間で劇的に構造体が遅くなる理由は見当もつかないので、この話題について掘り下げるのは避けたいと思うその他メモ
・UnityのgameObject.tag=="hoge"より、gameObject.CompareTag("hoge")の方が早いらしい。gameObject.tagのアクセスが処理を食うとか
・stringは極力StringBuilderを使った方が早い
・ifとswitchはifの方が基本早い
・多分岐では上から順に出現頻度が多い条件を書くことで比較回数を減らすことができる
- 投稿日:2020-01-12T04:03:22+09:00
Base64 encode を C# で実装してみる
Base64 の目的
8bitで表現される任意の値が設定されたバイト列を、安全に通信するためにテキストで表現できる形式に変換すること。
Base64 encode の変換形式
8bitのバイト列を6bitずつ取り出し、[a-zA-Z0-9+/] に割り当て直す。
つまり、24 bit = 8 bit * 3 のバイト列は 24 bit = 6 bit * 4 のバイト列に変換される
変換の概要
8bit の列を 6bit の列に分割する
bbbbbbbb bbbbbbbb bbbbbbbb ↓ bbbbbb bbbbbb bbbbbb bbbbbb6bit の列を定義した変換表を使って変換する
0b000001 = 'A' 0b000010 = 'B' 0b000011 = 'C' ... 0b111111 = '/'3バイトに満たない列には'='を埋める
Base64 の問題点
割り当て直す [a-zA-Z0-9+/] のうち、[a-zA-Z0-9] はどういう環境でも安全に扱えるが、のこりの [+/] については環境によってはそのまま使えず別途エンコードする必要がある。たとえば '/' はパス区切りには使えず、URL表現だと問題が発生するし、'+' も同様にURL表現で問題が発生する可能性がある。
ゆえに[+/]を別の安全に使えそうな文字に置き換えるとかする必要がある(典型的にはURLEncodeによって数値参照形式にするなど)
C#による実装
似たロジックはSMF(Standard MiIDI Format)における8bitの列から7bitずつを取り出す方法だが、SMFだとC#のバイト列とはエンディアンが逆なのでそのままは使えない。
参考: https://qiita.com/ringorou/items/5e2384f7cf226d9e648a
考え方
変換対象のバイト列の長さは不定だが、8bit から 6bit ずつ取り出せばよいので 24bit のみ考えればよい。
1.第1バイトを2ビット右シフトした値と 0b111111 の積 a を格納
2. 第1バイトを4ビット左シフトした値と 0b111111 の積 b を格納
3. 第2バイトを4ビット右シフトした値とbの和を 0b111111 の積をとった値 c を格納
4. 第2バイトを2ビット左シフトした値と 0b111111 の積を d を格納
5. 第3バイトを6ビット右シフトした値とdの和を 0b111111 の積をとった値 e を格納
6. 第3バイトと 0b111111の積 f を格納
7. 格納した値 a, c, e, f の列を変換表を使って変換する
8. 余った位置には'=' を追加する実装
// 変換表 static readonly string map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; public static string Encode(byte[] source) { // エンコード後の長さを計算し、余る長さを求める var encodedLength = source.Length * 8 / 6; var remain = 0; if ((source.Length * 8 % 6) > 0) { encodedLength++; remain = (4 - encodedLength % 4); encodedLength += remain; } var encoded = new char[encodedLength]; int offset = 0; byte next = 0; var n = 0; var m = 0; int i; // 変換の本体 while (n < source.Length) { next = 0; for (i = 0; i < 3 && n < source.Length; ++i, ++n) { offset = (i + 1) * 2; encoded[m++] = map[(next | (source[n] >> offset)) & 0b111111]; next = (byte)((source[n] << (6 - offset)) & 0b111111); } encoded[m++] = map[next]; } if (remain > 0) { for (var p = encodedLength - remain; p < encodedLength; ++p) { encoded[p] = '='; } } return new string(encoded); }その他
- 当然、組み込みの関数を使った方が効率はよい。
- 例えば '+','/' を別の文字に置き換える場合でも組み込み関数の出力を Replace等で置き換えた方が早い。
- 上のロジックでnew string()するのはいかがなもの、と思うがStringBuilderに変換結果を入れても最終的には同じことになるので意味はない
- 他に高速化、効率化するポイントはあると思うけどとりあえずの目的は達成できる
以下はそれぞれ Convert.ToBase64String, それの'+/'をReplaceで変換するもの、この記事のロジックでのベンチマーク結果。
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated ConvertBase64 249.4 ns 560.1 ns 30.70 ns 0.0758 - - 120 B ConverBase64Replace 310.4 ns 404.5 ns 22.17 ns 0.1049 - - 165 B OriginalBase64 543.8 ns 982.8 ns 53.87 ns 0.1216 - - 192 B
- 投稿日:2020-01-12T03:43:51+09:00
【Unity】ToggleGroup内のToggleを取得するには?
はじめに
Unityでラジオボタンを作るときに活躍するToggleGroup。
複数のToggleをグルーピングして、選択Toggleを択一(排他)にする機能を提供してくれます。ところがこのToggleGroup、少し曲者です。
というのも、管理しているすべてのToggleを知る手段を提供していないのです。1これは特に問題にならないことも多いですが、例えば、ToggleGroupから選択肢一覧を知りたいときなどに困ります。(結局自分で別管理することになったり…。)
今回はそんなときにどうするのがよいか、考えてみます。案
主に2つの方法が考えられます。
1. Toggle側からアプローチする
Toggleはgroupという自身が所属するToggleGroupを表すプロパティをもっています。
それならば、シーンにあるToggleを集めてそのgroupを調べればいい…。例として下記のようなコードで実現できます。// using System.Linq;が必要 /// <summary> /// ToggleGroupに所属するToggleを取得 /// </summary> /// <param name="toggleGroup"></param> /// <returns></returns> private IEnumerable<Toggle> GetTogglesOf(ToggleGroup toggleGroup) { var toggles = GameObject.FindObjectsOfType<Toggle>(); return toggles.Where(x => x.group == toggleGroup); }この取り方はToggleGroupからのアプローチでない故の分かりにくさが欠点かもしれません。
あとは検索コストがかかるため少し無駄を感じます。2. リフレクションで取り出す
無理やり何とかしたいときの常套、リフレクションです。
実はuGUIのソースコードはBitbucketで公開されているため、内部でどうToggleをもっているかを知ることができます。
該当のソースコードを見てみるとList<Toggle>
型のm_Toggles
という名前でprivateフィールドでもっていることが分かりました。フィールド名が分かってしまえば、下記のような拡張メソッドを作ることでいけます。2
ToggleGroupExtensions.csusing System.Collections.Generic; using System.Reflection; using UnityEngine.UI; /// <summary> /// トグルグループ拡張 /// </summary> public static class ToggleGroupExtensions { /// <summary> /// ToggleGroupがもつToggleのFieldInfo /// </summary> private static FieldInfo _togglesFieldInfo = null; static ToggleGroupExtensions() { _togglesFieldInfo = typeof(ToggleGroup).GetField("m_Toggles", BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); if (_togglesFieldInfo == null) { throw new System.Exception("Not compatible with the current version of ToggleGroup."); } } /// <summary> /// トグル一覧を取得 /// </summary> /// <param name="self"></param> /// <returns></returns> public static IEnumerable<Toggle> GetToggles(this ToggleGroup self) { return (_togglesFieldInfo.GetValue(self) as List<Toggle>); } }使うときはこんな感じになります。
foreach (var item in _toggleGroup.GetToggles()) { Debug.Log(item.name); }なお、注意点として、リフレクションの性質上ToggleGroupの内部実装が変わると互換性を失う可能性があります。
おわりに
今回紹介した方法はどちらも素直なやり方という感じはしませんね。
どうせ機能拡張などを見越しているなら、自分でToggleGroupに代わる実装するのがベストなのかもしれません。(中身見れるので参考にもできますしね…。)
もちろん選択されているToggleは取得できます(ActiveToggles)。でもなんで戻り値が単一じゃなくコレクションなんだろう…? ↩
本当はToggleGroup.togglesのようにプロパティで書きたいところですが、現時点(C#7.3)には拡張プロパティはないらしいです。 ↩
- 投稿日:2020-01-12T00:37:28+09:00
Unityでのメモリ管理
UnityとGC
Tech Academyのメンターさんからメモリ管理を気にして実装できたら中堅という風に言われた(やりたい処理を実装できるのは当たり前で)。
C#では基本的にガベージコレクション(GC)というやつがいらなくなったメモリをいい感じに解放しているらしい。
しかし、Unityでシーン再生中にGCが発生すると数フレーム飛ぶため、アクションゲームではシーン再生中にGCを発生させないようにしないといけないらしい。GCを発生させる条件とアクションゲームのリアルタイム性を損なわない対策
上のリンク先の記事によると、主なGC発生条件は「メモリが許容されるしきい値を超える場合」であり、これを防げばよいみたい。
で、極論対策としてはそのシーンを開始時にすべてのメモリ領域の確保を終わらせることみたい。
いや、無理じゃね。実用に耐えうる手段また調べよう・・・
メモリ利用状況の確認
メンターさんに聞いた話しだととりあえずProfilerというやつでやるらしい。
ただ、具体的にどの部分でめっちゃメモリ食ってるとかはわかんないので、
Profilerで確認しながら実装していかないとまずいらしい。
一通り実装し終わったからさぁメモリの負荷みますか、だと詰むらしい。
メモリの限界値としては、経験上システムメモリの半分くらいに押さえておいた方がよいらしい。iPhoneだと800MBくらいが目安か、というところ。
- 投稿日:2020-01-12T00:12:39+09:00
Unityで特定の日時にローカル通知する
はじめに
Unity Mobile Notifications Package
こちらのiOS/Androidでプッシュ通知を実装できるパッケージを使ってiOSのプッシュ通知を行います。
インストール
『Package Manager』から『Mobile Notifications』をインストールします。
使い方
今回は2020年1月1日20時20分に通知を送る設定にしてます。
特定の日時に送る場合は『iOSNotificationCalendarTrigger』を参照します。PushTest.csusing System.Collections; using System.Collections.Generic; using Unity.Notifications.iOS; using UnityEngine; using System; public class PushScript : MonoBehaviour { // Start is called before the first frame update void Start() { } IEnumerator RequestAuthorization() { using (var req = new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge, true)) { while (!req.IsFinished) { yield return null; } string res = "\n RequestAuthorization: \n"; res += "\n finished: " + req.IsFinished; res += "\n granted : " + req.Granted; res += "\n error: " + req.Error; res += "\n deviceToken: " + req.DeviceToken; Debug.Log(res); } } // ボタンが押された際に呼び出される関数 public void OnClickBtn() { var calendarTrigger = new iOSNotificationCalendarTrigger() { Year = 2020, Month = 1, Day = 1, Hour = 20, Minute = 20, Repeats = false }; var notification = new iOSNotification() { // You can optionally specify a custom identifier which can later be // used to cancel the notification, if you don't set one, a unique // string will be generated automatically. Identifier = "_notification_01", Title = "Title", Body = "Scheduled at: 2020年1月1日20時20分", Subtitle = "This is a subtitle, something, something important...", ShowInForeground = true, ForegroundPresentationOption = (PresentationOption.Alert | PresentationOption.Sound), CategoryIdentifier = "category_a", ThreadIdentifier = "thread1", Trigger = calendarTrigger, }; iOSNotificationCenter.ScheduleNotification(notification); } }
- 投稿日:2020-01-12T00:01:42+09:00
C++とC#のクラスの扱い方の違い
過去に実務でC++を触っていたときにクラスをnewでインスタンス化→要らなくなったらdeleteで削除というのを徹底していた。
で、最近Unity学習で触っているC#ではそういえばdeleteなんて書いたことねぇ・・・とふと思った。
※調べてみると、C#でのクラスインスタンスはclassの変数にnullをセットすると
C#のガベージコレクションという機能がもう使われてないと判断して削除してくれるらしい。C++、C#のクラスにおけるインスタンス化、利用、削除の流れを書いたコードは以下。
やっぱり色々なんか使い方違う・・・C++でのクラスインスタンス〜削除のイメージ// クラスをインスタンス化し、クラスのポインタ型変数にインスタンスのポインタを格納 ClassA* classA = new ClassA(); // int型のmember_aをコンソールへ出力 printf("%d¥n", classA->member_a); // インスタンスを削除 delete classA;C#でのクラスインスタンス化→削除のイメージ// クラスをインスタンス化し、クラスの変数(あれ、ポインタじゃない?)に格納 ClassA classA = new ClassA(); // int型のmember_aをUnityのコンソールへ出力 Debug.Log(classA.member_a); // インスタンスを削除 classA = null;一番の違和感は、C++ではクラスをnewするとそのインスタンスのポインタが返ってきて、それをクラスのポインタ型変数に代入して、アロー演算子とかいうやつでアクセスする、という形なのに、C#ではポインタとか使った記憶がない。ちょっとまって。C#ではnewしたときに何が返ってくるの・・・?
そもそもC#ではポインタを使うことが非推奨であるらしい(重大なバグにつながるかららしい。のっぴきならない事情があれば無理やり使うこともできるみたいだが)。・・・ということはそもそもnewしてもポインタなんかかえってきてるわけなさそう。
じゃあなにが返ってくるのか。それは・・・newした対象のデータ型によるらしく。C#の世界では、データの型に応じて、値型もしくは参照型に区分され、扱われるらしい。それはそのデータ型の変数を宣言したときも同等。
newしたときに値型で返ってくる newしたときに参照型で返ってくる - 構造体
- 列挙型- string
- object
- 配列
- クラス
- インターフェース
- デリゲート
クラスは参照型という扱いになるんですね。