- 投稿日:2019-07-08T22:10:19+09:00
[C#]ファイルに文字列/バイナリデータを書き込み
やりたいこと
文字列やバイナリのデータをファイルに書いたりファイルに読んだりはよくやることだが、その都度適当なコードを書いていたので、一度自分の簡易的な定型文を作っておきたい。
サンプルコード
Program.csusing System; using System.IO; using System.Linq; namespace ConsoleApp4 { class Program { static string TargetStringFilePath = @"C:\temp\myfile\TargetString.txt"; static string TargetBinaryFilePath = @"C:\temp\myfile\TargetBinary.txt"; static void Main(string[] args) { WriteStringToFile(TargetStringFilePath, "書き込みます。\r\nあいうえお。\r\n"); Console.WriteLine(ReadStringFromFile(TargetStringFilePath)); var bin_in = Enumerable.Range(1, 10).Select(x => (byte)x).ToArray(); WriteBinaryToFile(TargetBinaryFilePath, bin_in); var bin_out = ReadBinaryFromFile(TargetBinaryFilePath); Console.ReadLine(); } // 文字列をファイルに書き込み public static void WriteStringToFile(string path, string data) { var dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (var fs = new FileStream(path, FileMode.Create)) using (var sw = new StreamWriter(fs)) { sw.Write(data); } } // 文字列をファイルから読み込み public static string ReadStringFromFile(string path) { string data = string.Empty; if (File.Exists(path)) { using (var fs = new FileStream(path, FileMode.Open)) using (var sr = new StreamReader(fs)) { data = sr.ReadToEnd(); } } return data; } // バイナリデータをファイルに書き込み(書き込み先のフォルダがない場合は作成する) public static void WriteBinaryToFile(string path, byte[] data) { var dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (var fs = new FileStream(path, FileMode.Create)) using (var sw = new BinaryWriter(fs)) { sw.Write(data); } } // バイナリデータをファイルから読み込み public static byte[] ReadBinaryFromFile(string path) { if (File.Exists(path)) { using (var fs = new FileStream(path, FileMode.Open)) using (var sr = new BinaryReader(fs)) { int len = (int)fs.Length; byte[] data = new byte[len]; sr.Read(data, 0, len); return data; } } return null; } } }
- 投稿日:2019-07-08T22:10:19+09:00
[C#]ファイルに文字列/バイナリデータを読み書きする
やりたいこと
文字列やバイナリのデータをファイルに書いたりファイルに読んだりはよくやることだが、その都度適当なコードを書いていたので、一度自分の簡易的な定型文を作っておきたい。
サンプルコード
Program.csusing System; using System.IO; using System.Linq; namespace ConsoleApp4 { class Program { static string TargetStringFilePath = @"C:\temp\myfile\TargetString.txt"; static string TargetBinaryFilePath = @"C:\temp\myfile\TargetBinary.txt"; static void Main(string[] args) { WriteStringToFile(TargetStringFilePath, "書き込みます。\r\nあいうえお。\r\n"); Console.WriteLine(ReadStringFromFile(TargetStringFilePath)); var bin_in = Enumerable.Range(1, 10).Select(x => (byte)x).ToArray(); WriteBinaryToFile(TargetBinaryFilePath, bin_in); var bin_out = ReadBinaryFromFile(TargetBinaryFilePath); Console.ReadLine(); } // 文字列をファイルに書き込み public static void WriteStringToFile(string path, string data) { var dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (var fs = new FileStream(path, FileMode.Create)) using (var sw = new StreamWriter(fs)) { sw.Write(data); } } // 文字列をファイルから読み込み public static string ReadStringFromFile(string path) { string data = string.Empty; if (File.Exists(path)) { using (var fs = new FileStream(path, FileMode.Open)) using (var sr = new StreamReader(fs)) { data = sr.ReadToEnd(); } } return data; } // バイナリデータをファイルに書き込み(書き込み先のフォルダがない場合は作成する) public static void WriteBinaryToFile(string path, byte[] data) { var dir = Path.GetDirectoryName(path); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } using (var fs = new FileStream(path, FileMode.Create)) using (var sw = new BinaryWriter(fs)) { sw.Write(data); } } // バイナリデータをファイルから読み込み public static byte[] ReadBinaryFromFile(string path) { if (File.Exists(path)) { using (var fs = new FileStream(path, FileMode.Open)) using (var sr = new BinaryReader(fs)) { int len = (int)fs.Length; byte[] data = new byte[len]; sr.Read(data, 0, len); return data; } } return null; } } }
- 投稿日:2019-07-08T20:28:18+09:00
構造体の中身が変化したときのReactivePropertyの動作
主文
構造体は値型だから値が変わらないと変化にならないよ気をつけようね
構造体
public struct TestStruct { public int amount; public bool flag; }例1
以下のコードは1秒毎に
Struct has been changed
が出力される(厳密には1/200の確率で)
構造体の値が変わるから当然ReactiveProperty<TestStruct> teststruct = new ReactiveProperty<TestStruct>(new TestStruct()); void Start () { teststruct.Subscribe(_=>{ Debug.Log("Struct has been changed."); }); Observable.Interval(System.TimeSpan.FromSeconds(1f)).Subscribe(_=> { var s = teststruct.Value; s.amount = Random.Range(1,100); s.flag = Random.Range(0,2) == 0; teststruct.Value = s; }); }例2
これも1秒毎に
Struct has been changed
が出力される(厳密には1/200の確率で)
構造体の値が変わるから当然ReactiveProperty<TestStruct> teststruct = new ReactiveProperty<TestStruct>(new TestStruct()); void Start () { teststruct.Subscribe(_=>{ Debug.Log("Struct has been changed."); }); Observable.Interval(System.TimeSpan.FromSeconds(1f)).Subscribe(_=> { teststruct.Value = new TestStruct(){ amount = Random.Range(1,100), flag = Random.Range(0,2) == 0 }; }); }例3
この場合は1回しか出ない(初期値の代入時のみ)
初期値が1,falseで、毎回更新のたびに1,falseのものを入れてるから変化がないとの判断ReactiveProperty<TestStruct> teststruct = new ReactiveProperty<TestStruct>(new TestStruct(){amount = 1, flag = false}); void Start () { teststruct.Subscribe(_=>{ Debug.Log("Struct has been changed."); }); Observable.Interval(System.TimeSpan.FromSeconds(1f)).Subscribe(_=> { var s = teststruct.Value; s.amount = 1; s.flag = false; teststruct.Value = s; }); }例4
これも1回しか出ない
値が変わらないとOnNextが送られないReactiveProperty<TestStruct> teststruct = new ReactiveProperty<TestStruct>(new TestStruct(){amount = 1, flag = false}); void Start () { teststruct.Subscribe(_=>{ Debug.Log("Struct has been changed."); }); Observable.Interval(System.TimeSpan.FromSeconds(1f)).Subscribe(_=> { teststruct.Value = new TestStruct(){amount = 1, flag = false}; }); }値が変わらなくても必ず通知して欲しい
ReactiveProperty.SetValueAndForceNotify
を使えば良いReactiveProperty<TestStruct> teststruct = new ReactiveProperty<TestStruct>(new TestStruct(){amount = 1, flag = false}); void Start () { teststruct.Subscribe(_=>{ Debug.Log("Struct has been changed."); }); Observable.Interval(System.TimeSpan.FromSeconds(1f)).Subscribe(_=> { teststruct.SetValueAndForceNotify(new TestStruct(){amount = 1, flag = false} ); }); }
- 投稿日:2019-07-08T18:48:11+09:00
UIAutomationでDataGridViewCellの値を取ってみるコード[短小コード]
目的
- C#で作られたFormアプリとかを外部操作したい!
- 今お仕事で課題になってるやつを解決できそうな感じなのでおまとめ。
UIAutomation
する相手はForm
にDataGridView
がデーンと置いてあるだけのバカチョンを前提にしています。- なんか妙に読める情報1が少ないとこなのでこんなのでも助けになるはず。
- .NET 4.8から正式に対応するようになったらしいです。コードの内容も変わるのかな?
コード
// このコードを実装したアプリが立ち上がったタイミングでDataGridViewに登録されている値のみ表示できるはず /// <param name = "parent"> UIAutomationする対象フォーム</param> private List<List<string>> ReadDataGridViewCellValues(AutomationElement parent) { var rows = new List<List<string>>(); // Find the DataGridView in question // AutomationIDはSPY++とかで調べる感じで var datagrid = parent.FindFirst( TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "dataGridView1")); // Find all rows from the DataGridView var loginLines = datagrid.FindAll(TreeScope.Descendants, PropertyCondition.TrueCondition); foreach (AutomationElement loginLine in loginLines) { var row = new List<string>(); var loginLinesDetails = loginLine.FindAll( TreeScope.Descendants, new PropertyCondition( AutomationElement.ControlTypeProperty, ControlType.Custom) ); for (var i = 0; i < loginLinesDetails.Count; i++) { var targetText = loginLinesDetails[i].FindFirst( TreeScope.Descendants, new PropertyCondition(AutomationElement.ClassNameProperty, "TextBlock")); const int automationPropID_dgvCellValue = 30045; // 地道な調査で突き止めたID。環境によって違ったりしたらごめんね。 var temp = (string)loginLinesDetails[i].GetCurrentPropertyValue( AutomationProperty.LookupById(automationPropID_dgvCellValue)); // GetCachedPropertyValue()てのもあって、気にはなるけどそっちを使うとInvalidOperationExceptionを投げられちゃう string cellValue = string.Empty; if( temp != null) cellValue = temp; row.Add(cellValue); } rows.Add(row); } return rows; };参考
UI Automation not working for DataGridView | stackoverflow
Getting full contents of a Datagrid using UIAutomation | stackoverflow
C# Windows Formアプリの部品DataGridViewに外部プロセスからアクセスしたい | stackoverflow環境
- Visual Studio 2015 SP1
- Windows7 64bit
英語で結構引っかかるけど英語が出来ないので ↩
- 投稿日:2019-07-08T18:48:11+09:00
UIAutomationでDataGridViewCellの値を取ってみるコード[短小ソース]
目的
- C#で作られたFormアプリとかを外部操作したい!
- 今お仕事で課題になってるやつを解決できそうな感じなのでおまとめ。
UIAutomation
する相手はForm
にDataGridView
がデーンと置いてあるだけのバカチョンを前提にしています。- なんか妙に読める情報1が少ないとこなのでこんなのでも助けになるはず。
- .NET 4.8から正式に対応するようになったらしいです。コードの内容も変わるのかな?
コード
// このコードを実装したアプリが立ち上がったタイミングでDataGridViewに登録されている値のみ表示できるはず /// <param name = "parent"> UIAutomationする対象フォーム</param> private List<List<string>> ReadDataGridViewCellValues(AutomationElement parent) { var rows = new List<List<string>>(); // Find the DataGridView in question // AutomationIDはSPY++とかで調べる感じで var datagrid = parent.FindFirst( TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "dataGridView1")); // Find all rows from the DataGridView var loginLines = datagrid.FindAll(TreeScope.Descendants, PropertyCondition.TrueCondition); foreach (AutomationElement loginLine in loginLines) { var row = new List<string>(); var loginLinesDetails = loginLine.FindAll( TreeScope.Descendants, new PropertyCondition( AutomationElement.ControlTypeProperty, ControlType.Custom) ); for (var i = 0; i < loginLinesDetails.Count; i++) { var targetText = loginLinesDetails[i].FindFirst( TreeScope.Descendants, new PropertyCondition(AutomationElement.ClassNameProperty, "TextBlock")); const int automationPropID_dgvCellValue = 30045; // 地道な調査で突き止めたID。環境によって違ったりしたらごめんね。 var temp = (string)loginLinesDetails[i].GetCurrentPropertyValue( AutomationProperty.LookupById(automationPropID_dgvCellValue)); // GetCachedPropertyValue()てのもあって、気にはなるけどそっちを使うとInvalidOperationExceptionを投げられちゃう string cellValue = string.Empty; if( temp != null) cellValue = temp; row.Add(cellValue); } rows.Add(row); } return rows; };参考
UI Automation not working for DataGridView | stackoverflow
Getting full contents of a Datagrid using UIAutomation | stackoverflow
C# Windows Formアプリの部品DataGridViewに外部プロセスからアクセスしたい | stackoverflow環境
- Visual Studio 2015 SP1
- Windows7 64bit
英語で結構引っかかるけど英語が出来ないので ↩
- 投稿日:2019-07-08T15:52:48+09:00
バージョン番号(string)の比較 (C#)
やりたいこと
- バージョン番号のようなドット区切り十進表記の文字列を比較したい。
- 一括比較したいだけなので、
System.Version
クラスを使ってまではやりたくない。コード
自前実装
/// <summary>ドット区切り数字列を比較する</summary> public static int CompareVersionString (this string a, string b) { var aSeries = a.Split ('.'); var bSeries = b.Split ('.'); int aNum, bNum; for (var i = 0; i < aSeries.Length; i++) { if (i >= bSeries.Length) { return 1; } aNum = 0; int.TryParse (aSeries [i], out aNum); bNum = 0; int.TryParse (bSeries [i], out bNum); if (aNum > bNum) { return 1; } else if (aNum < bNum) { return -1; } } return (aSeries.Length == bSeries.Length) ? 0 : -1; }こっそりと
System.Version
を使う/// <summary>ドット区切り数字列を比較する</summary> public static int CompareVersionString (this string a, string b) { return (new System.Version (a)).CompareTo (new System.Version (b)); }
- 比較のためにだけVersionクラスを使います。(本末転倒)
using system;
を書かない辺りが「こっそり」です。使用例
#if UNITY_* Debug.Log ("1.1.12".CompareVersionString ("1.1.12.0")); #else Console.WriteLine ("1.1.12".CompareVersionString ("1.1.12.0")); #endif // -1
- 実際には
UNITY_*
という記述はできません。仕様
- 以下の説明では、ドットで区切られた個々の数字列を"位"と称します。
- 一般に、"メジャー"、"マイナー"、"ビルド"、"リビジョン"などと呼ばれるもののことです。
- 各"位"を数値化するルールは、
int.TryParse ()
に準じます。
- 数値化できなかった場合は
0
と見なします。- 双方の文字列の左端は、同じ大きさの"位"であるものと見なし、右に行くほど段階的に小さくなるものとします。
- 「左端は必ず"メジャー"で、次は"マイナー"である」ということです。
- "位"が存在しない場合は、
0
よりも"小さい"値と見なします。(上記の使用例を参照)- 比較の結果を、
(a < b) => -1
、(a == b) => 0
、(a > b) => 1
で返します。参考
- Unityのインスペクタで設定される"Version*"文字列(Application.version)をマスタデータとして扱いたかったので、こういうやり方をしています。
- 通常は、素直にVersionクラスを使うことをお勧めします。
public static readonly Version BundleVersion = new Version (Application.version);
- 投稿日:2019-07-08T14:31:04+09:00
リファクタリングに最適なクソコード
クソコード
練習などにお使いください
kusoko-do.rbusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; using よくわからんやつ = UnityEngine.MonoBehaviour; using 真偽値 = System.Boolean; using 数値 = System.Int32; using 虚無 = System.Action; using 位置 = UnityEngine.Vector3; public class senpaikuso : よくわからんやつ { /* * SENPAIKUSO-零1 1秒毎に移動状態が切り替わるオブジェクト * start状態の時、x方向に零.1f/frameずつ進みます。 * ただし、1秒後に停止、2秒後には移動再開、3秒後は移動開始...のように1秒が経過する毎に移動/停止が切り替わる。 * なお、実行環境はアメリカで、日本語は使用できないものとする。 */ private const 真偽値 真 = true; private const 真偽値 偽 = false; private const 数値 一秒 = 60; private const 数値 零 = 0; private const 数値 Ⅲ = 3; private const 数値 Ⅱ = 2; private const 数値 Ⅰ = 1; private 位置 今いる場所 { get { return transform.position; } set { transform.position = value; } } private void Start() { 移動 = new 移動状態(); 移動.移動OK = 真; 移動.移動NG = 偽; 移動.待機OK = 偽; 移動.待機NG = 真; } private void Update() { int フレーム = 時間 = 時間 + Ⅰ; if (時間 == 一秒) { { 時間 = 零; if (移動.移動OK && 移動.待機NG) { 移動.移動OK = 偽; 移動.移動NG = 真; 移動.待機OK = 真; 移動.待機NG = 偽; } else if (移動.移動NG && 移動.待機OK) { 移動.移動OK = 真; 移動.移動NG = 偽; 移動.待機OK = 偽; 移動.待機NG = 真; } } } else { 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); switch (移動.移動OK == 真) { case 真: switch (移動.待機NG == 真) { case 真: 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.移動NG == 真) { case 真: switch (移動.待機OK == 真) { case 真: 今いる場所 -= new 位置(移動速度 / (Ⅲ * Ⅱ), 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.待機OK == 真) { case 真: switch (移動.移動NG == 真) { case 真: 今いる場所 -= new 位置(移動速度 / (Ⅲ * Ⅱ), 零, 零); break; case 偽: break; } break; case 偽: break; } switch (移動.待機NG == 真) { case 真: switch (移動.移動OK == 真) { case 真: 今いる場所 += new 位置(移動速度 / Ⅲ, 零, 零); break; case 偽: break; } break; case 偽: break; } } } [Serializable] public struct 移動状態 { public 真偽値 移動OK; public 真偽値 移動NG; public 真偽値 待機OK; public 真偽値 待機NG; } [SerializeField] public float 移動速度 = 1; public class kotae { string 答え = "そんな物無いよ"; } [SerializeField] 移動状態 移動 = new 移動状態(); int 時間 = 零; }
- 投稿日:2019-07-08T11:45:32+09:00
MagicOnion のストリーミング接続を複数シーンで持ち越す場合のTips
はじめに
MagicOnion についてはこちらをご覧ください。
一般的なマルチプレイゲームでは、マッチング→メインのゲーム→リザルト といったふうに、複数のシーンでストリーミング接続を繋ぎっぱなしにする事が多いと思います。
その場合、1シーンで使う場合に比べ気をつける必要がある点がいくつかあるので、それぞれに対して自分がやっている対応を紹介します。Receiver
接続全体を1つのインターフェースにまとめることになるので、シーンごとに一部のメソッドしか使わないことになります。
一方、IHogeHubReceiver を実装するクラスでは当然すべてのメソッドを定義しなければいけません。
このままでは非常に扱いづらいのですべてのメソッドを UniRx の Observable に変換してしまうと良さそうです。
インターフェイスの明示的実装を使えば同名のPublicなプロパティが生やせます。例public interface IHogeHubReceiver { void OnHoge(); void OnFuga(int i); void OnPiyo(int i, int j); } public class HogeHubReceiver : IHogeHubReceiver { // 引数なしは UniRx.Unit に public IObservable<Unit> OnHoge => _onHoge; private Subject<Unit> _onHoge = new Subject<Unit>(); void IHogeHubReceiver.OnHoge() => _onHoge.OnNext(Unit.Default); // 1引数はそのまま public IObservable<int> OnFuga => _onFuga; private Subject<int> _onFuga = new Subject<int>(); void IHogeHubReceiver.OnFuga(int i) => _onFuga.OnNext(i); // 2引数以上はタプル化 public IObservable<(int i, int j)> OnPiyo => _onPiyo; private Subject<(int i, int j)> _onPiyo = new Subject<(int i, int j)>(); void IHogeHubReceiver.OnPiyo(int i, int j) => _onPiyo.OnNext((i, j)); }これを手動で書くのはめんどくさくてしょうがないのでエディタ拡張で自動生成しましょう。
StreamingHubReceiverCreator.csusing System; using System.Linq; using System.Text.RegularExpressions; using System.IO; using UnityEditor; /// <summary> /// StreamingHubReceiverのインターフェースから具象型を生成するエディタ拡張 /// </summary> public static class StreamingHubReceiverCreator { /// <summary> /// StreamingHubReceiverのインターフェースから具象型を生成する /// すべてのメソッドはIObservableに変換される /// </summary> [MenuItem("Assets/Create/StreamingHubReceiver")] private static void CreateStreamingHubReceiver() { var interfaceName = Selection.activeObject.name; var source = File.ReadAllText(AssetDatabase.GetAssetPath(Selection.activeObject)); // using追加 if (!source.Contains("using System;")) source = "using System;\r\n" + source; if (!source.Contains("using UniRx;")) source = "using UniRx;\r\n" + source; // クラス名変換 source = Regex.Replace(source, "interface I(.*Receiver)", "class $1 : I$1"); // 各メソッド変換 source = Regex.Replace(source, "( *)void (.*)\\((.*)\\);", m => { var space = m.Groups[1].Value; var name = m.Groups[2].Value; var privateName = "_" + Char.ToLower(name[0]) + name.Substring(1); var args = m.Groups[3].Value; var genericArgs = "Unit"; var argNames = "Unit.Default"; // 引数の個数に応じてデータ構造を変更 var x = Regex.Matches(args, "(?:(.+?[^,]) (.+?)(?:,|$))").Cast<Match>().ToArray(); if (x.Length == 1) { genericArgs = x[0].Groups[1].Value; argNames = x[0].Groups[2].Value; } else if (x.Length > 1) { // タプル化 genericArgs = "(" + args + ")"; argNames = "(" + string.Join(", ", x.Select(y => y.Groups[2].Value)) + ")"; } return $"{space}public IObservable<{genericArgs}> {name} => {privateName};\r\n" + $"{space}private Subject<{genericArgs}> {privateName} = new Subject<{genericArgs}>();\r\n" + $"{space}void {interfaceName}.{name}({args}) => {privateName}.OnNext({argNames});"; }); var fileName = Selection.activeObject.name.Substring(1) + ".cs"; var classPath = Directory.EnumerateFiles(".", fileName, SearchOption.AllDirectories).FirstOrDefault(); File.WriteAllText(classPath ?? "Assets/" + fileName, source); AssetDatabase.Refresh(); } /// <summary> /// 選択しているアイテムがスクリプトファイルかどうかを判別する /// </summary> /// <returns>スクリプトファイルかどうか</returns> [MenuItem("Assets/Create/StreamingHubReceiver", isValidateFunction: true)] private static bool ValidateCreateStreamingHubReceiver() => Selection.activeObject is MonoScript; }切断
接続が不要になったら切断するというは大事で、これをしないと Unity エディタがすぐフリーズします。
単独のシーンで使うならシーンで使うスクリプトの OnDestroy で切断すれば良いですが、
複数シーンの場合は、接続を管理するオブジェクトを作り DontDestroyOnLoad に設定しておくのが良いでしょう。public class ConnectionHolder : MonoBehaviour { public IHogeHub Client { get; private set; } public HogeHubReceiver Receiver { get; } = new HogeHubReceiver(); // さっき作ったやつ private Channel _channel; public static ConnectionHolder Create() { // DontDestroy化 var gameObject = new GameObject("ConnectionHolder"); DontDestroyOnLoad(gameObject); // 接続 var holder = gameObject.AddComponent<ConnectionHolder>(); holder._channel = new Channel("host:port", ChannelCredentials.Insecure); holder.Client = StreamingHubClient.Connect<IHogeHub, IHogeHubReceiver>(holder._channel, holder.Receiver); return holder; } private void OnDestroy() { Client.DisposeAsync(); _channel.ShutdownAsync(); } }
- はじめのシーンでは Create を呼ぶ
- 以降のシーンでは Find で探してくる
- 最後のシーンで Destroy する
というイメージです。
ついでに参加者の保持管理もこのクラスでやってしまうと後のシーンで使いやすくて便利です。