【C#】stringが参照型だって知ってた?
最近、stringが参照型だということを知って
そう言われるとそうじゃないとおかしいよなー!という納得と
参照型だったらおかしくね!?と思う事があったのでまとめます
- 参照型と値型の違い
- 値型と参照型の分類
- 参照型のstringの挙動はどうなってるの?
- string型にはコンストラクタが存在していた!
- なぜstring型は参照型なのに値型みたいに扱おうとしているの?
参照型と値型の違い
araramistudio.jimdo.com
リンク丸投げですみません!
上記リンクはメモリが図示されていて分かりやすかったのでご参考にしてください
簡単に説明すると
値型は、変数の中に値が格納されていて、
参照型は、変数の中に値が格納されているアドレスが格納されています。
実際にソースの動きで見てみると分かりやすいかも
private void SetValueTest() { int testInt = 0; // 値型変数代表 StringBuilder testStrb = new StringBuilder("000"); // 参照型変数代表 AddValues(testInt, testStrb); } private void AddValues(int testInt2, StringBuilder testStrb2) { testInt2+= 222; testStrb2.Append("222"); }
例として、値型・参照型それぞれの変数を定義。
外部関数の引数として呼び出して関数内で値を変更。
結果、2つの変数にどのような変化が起きるか検証してみます
以下が結果になります
testInt = 0 testStrb ="000222"
違いが分かるでしょうか?
値型の「testInt」は外部関数で変更があっても変わらず0のまま。
参照型の「testStrb」は外部の変更に影響されて"000222"になってます。
つまり、
値型は引数として外部関数に渡される際に新しい変数が作成される。
そのため、外部関数で変更があっても影響しない。
参照型は引数としてアドレスが外部関数に渡される。
※同じアドレスが格納された別の変数「testStrb2」が作成されるという事です
外部関数の変更は「testStrb2」に格納されたアドレスの中身に対する変更になる。
つまり、「testStrb」に格納されたアドレスの中身に対する変更でもあるのです。
そのため、外部関数で変更があると、元の変数も影響を受けるという事です。
これが、値型と参照型の違いとなっています。
値型と参照型の分類
ufcpp.net
上記サイトの「C# の型の分類」という図がとても分かりやすいです。
この分類を見ると、
文字列型(string型)は参照型になってる!!
なんてこった!/(^o^)\
今までなんとなくで使ってたstringですが、
経験上stringは値型だと考えて組み込みを行ってました!
これには本当に驚いたとともに、様々な疑問が浮かんできました・・・
なんとなく解決しましたのでそれを以下にまとめます
参照型のstringの挙動はどうなってるの?
参照型・値型の違いを説明する時に用いたコードを
string型の変数で実行してみましょう
private void SetValueTest() { string testStr = "000"; // 文字列型変数 AddValues(testStr); } private void AddValues(string testStr ) { testStr += "222"; }
関数の構成は全く同じです。
以下が結果になります
testStr ="000"
外部関数の変更の影響を受けてません・・・
挙動としては値型の挙動になってます。
そうなんです!普段stringを使用していると分かると思うのですが
stringの挙動は値型と同じ挙動をしているのです。不思議です・・・
stringが参照型なのに挙動が値型の理由を以下で掘り下げていきます。
string型にはコンストラクタが存在していた!
docs.microsoft.com
上記サイトに、string型のコンストラクタに関する説明が書かれています。
中でも、以下の記述に注目です!
「String(Char[])
String クラスの新しいインスタンスを、
指定した文字配列で示された Unicode 文字に初期化します。」
簡単にソースで説明すると、
string testStr = "000"; string testStr2 = testStr ;
こうやって記述することは
string testStr = "000"; string testStr2 = new string (testStr);
という事と同義になる。という事です。
普通の参照型変数は変数をコピーするとアドレスをコピーするのですが。
string型変数をコピーすると
別アドレスにコピー元のアドレスの中身をコピーして、
コピー後の変数はコピー後のアドレスになっているという事です。
参照型なのに無理やり値型の挙動を再現している感じですね。。。
つまり、
string型はコンストラクタによって、
変数コピーの際は値型の挙動をとる!という事です。
これが参照型のstringが値型のような挙動をとる理由ですね。
なぜstring型は参照型なのに値型みたいに扱おうとしているの?
※ここから先は事実じゃないです。ただの暇つぶしの考察です
まず大前提として、string型の中身はchar型の配列です。
つまり、int型等の値型と違って中身のサイズが不定になってしまいます。
これが、string型が参照型にならざるを得ない理由だと思います。
しかし、参照型は気軽に扱う標準搭載の型としては使い勝手が悪すぎる!
そして、string型はクラス型のように複数の不定の型の集合体ではなく、
char型の配列と、決まった型となっています。
なので、コントラクタを使って値型の挙動を再現できるのだと思います。
以上が参照型云々の話でした。
長々と書いてしまいましたが
string型のコントラクタを発見する場でには
もっと長々とした道中があったのでご勘弁ください。。。
【Unity】Textの文字を動く虹色にする
UnityのTextオブジェクトは、タグによってある程度自由な表現ができます。
今回はタグのテストみたいなもので文字を動く虹色に出来るスクリプトを作りました。
実際の挙動
こんな感じです。
Textに記入した文字に自動でタグ付けをして一文字づつ色を変えます。
追加機能で、Inspectorの設定で
方向転換・変色スピードの設定変更もできる様にしています。
ソースコード
スクリプトファイルをGitHubにアップしてますのでご自由にどうぞ
github.com
落とすのがメンドイ方は以下にソースを記入しますのでコピペして下さい
public class TextRainbow : MonoBehaviour { // Inspectorで設定する変数 public bool IsTokeiMawari = true; public int ChangeSpeed = 5; // 変換管理変数 public static int _ChangeCnt = 0; public static int _NijiStartId = 0; // 変換管理定数 public const string Df_Tag_Hedder = "<color=#Value>"; public const string Df_Tag_Footer = "</color>"; // 虹色の中身 ※ 2桁づつで区切って[00 00 00]=[ R, G, B ]の値になっている // ※ この配列の要素を追加・編集したらオリジナルの動く色が作れる public static string[] Df_ColorTag = new string[] { "ff0000", "ffff00", "00ff00", "00ffff", "0000ff", "ff00ff", }; /// <summary> /// 毎割り込みイベント /// </summary> void Update() { // 設定したスピードに合わせて色変更処理を呼ぶ _ChangeCnt--; if (_ChangeCnt <= 0) { _ChangeCnt = ChangeSpeed; SetTextColorChange(IsTokeiMawari, this.GetComponent<Text>()); } } /// <summary> /// テキスト色を変える処理(毎割り込み) /// </summary> /// <param name="TxtSet">変更するテキストオブジェクト</param> public static void SetTextColorChange(bool IsVecLR,Text TxtSet) { // テキストの文字を取得※Tag文字を取り除く string textNoTag = TxtSet.text; textNoTag = textNoTag.Replace(Df_Tag_Footer, ""); for (int i_ColorId = 0; i_ColorId < Df_ColorTag.Length; i_ColorId++) { textNoTag = textNoTag.Replace(Df_Tag_Hedder.Replace("Value", Df_ColorTag[i_ColorId]), ""); } // 一文字ずつ色を設定 int setColorId = _NijiStartId; StringBuilder textSet = new StringBuilder(); for (int i_Word = 0; i_Word < textNoTag.Length; i_Word++) { textSet.Append(Df_Tag_Hedder.Replace("Value", Df_ColorTag[setColorId])); textSet.Append(textNoTag.Substring(i_Word, 1)); textSet.Append(Df_Tag_Footer); if(IsVecLR) { setColorId--; if (setColorId <0) { setColorId = Df_ColorTag.Length-1; } } else { setColorId++; if (setColorId >= Df_ColorTag.Length) { setColorId = 0; } } } // 次回の開始色を更新 _NijiStartId++; if (_NijiStartId >= Df_ColorTag.Length) { _NijiStartId = 0; } // テキスト文字を変更 TxtSet.text = textSet.ToString(); } }
ソースの中で、「虹色の中身」の部分に注目してください。
この配列が設定する文字の色です。
現在は虹に見える様に6色に設定しているのですが、
自由に要素を増やしたり、色データの値を変更すれば
オリジナルの配色にできる様になってます。
使い方
1)虹にしたいTextオブジェクトにスクリプトファイルを追加
2)オブジェクトのTextコンポーネントの
「Rich Text」項目にチェックが入ってなければチェックを入れる
3)ビルドしたら虹になるので、スピード・方向を設定すればOK!
Textのタグのついて
Textに記入している文字を「<○○>表示文字<○○>」のように囲むと
囲んだ文字(表示文字)が変化して表示されるという機能です。
【例】文字「AAA」を赤色でサイズを50にして表示する
<size=50><color=#ff0000>AAA</color></size>
※上記をTextにコピペ!
こんな感じで、AAAをタグでくくることで表示が変更されます
タグの詳細は以下のリファレンスを参照
docs.unity3d.com
以上です。
正直、文字を綺麗に動かすなら「TextMeshPro」一択です!
しかし、単調な動きならスクリプトで汎用化させておくと
疲れた時に楽できるのでとても重宝します。。。
P.S.初めてGIF貼り付けしてみたけど面倒くさすぎて泣けた・・・
【python】tryの中で呼び出した関数がエラーになった場合の挙動
pythonの組み込みで必ず使用するtryを使った例外処理の話です。
tryの中で呼び出した関数でエラー
tryの中で外部関数を呼び出した場合、
・外部関数でエラーになった時は呼び出し元のtryが動いてくれるのか?
・ちゃんとexcept以下の処理が動くのか?
・外部関数で外部関数を呼び出した時も同じ挙動になるのか?
・外部関数の中でtryが組み込まれていた場合はどうなるのか?
これらの挙動を簡単なソースで確認してみました。
【外部にtryなし】外部でエラーが起きた場合の挙動
A )外部の外部の外部でエラーが起きた場合
import sys #****************************************************************************** # 呼び出す外部関数_1st def SetDisp_01(): print("01") SetDisp_02() #****************************************************************************** # 呼び出す外部関数_2nd def SetDisp_02(): print('02') SetDisp_03() #****************************************************************************** # 呼び出す外部関数_3rd def SetDisp_03(): print('03') print(1+"")# こいつがエラーになる行( int+str でエラー) #****************************************************************************** # メインルーチン try: SetDisp_01() print("正常") except: print("エラー") print(sys.exc_info()) pass
このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→SetDisp_02を呼ぶ→SetDisp_03を呼ぶ→エラー
という挙動になるように組み込みました。
実行結果
01 02 03 エラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12308>)
結果を見る限り、
外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
ちゃんとエラー発生後にexcept以下の処理が実行されている
という事がわかりました。
B )外部でエラーが起きた場合、その後の処理は中断されるのか?
import sys #****************************************************************************** # 呼び出す外部関数_1st def SetDisp_01(): print("01") print(1+"")# こいつがエラーになる行( int+str でエラー) SetDisp_02() #****************************************************************************** # 呼び出す外部関数_2nd def SetDisp_02(): print('02') #****************************************************************************** # メインルーチン try: SetDisp_01() print("正常") except: print("エラー") print(sys.exc_info()) pass
このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→エラー(の後、SetDisp_02を呼ぶ)
という挙動になるように組み込みました。
実行結果
01 エラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB126C8>)
結果を見る限り、
外部でエラーになってもエラー発生後にすぐ
except以下の処理が実行されているから、エラー後の処理は行われない。
という事がわかりました。
まとめ
・外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
・ちゃんとエラー発生後にexcept以下の処理が実行されている
・外部でエラーになってもエラー発生後にすぐ
except以下の処理が実行されているから、エラー後の処理は行われない。
【外部にtryあり】外部でエラーが起きた場合の挙動
A )外部のtryの中でエラーが起きた場合
import sys #****************************************************************************** # 呼び出す外部関数_1st def SetDisp_01(): try: print("01") print(1+"")# こいつがエラーになる行( int+str でエラー) except: print("子01のエラー") print(sys.exc_info()) pass SetDisp_02() #****************************************************************************** # 呼び出す外部関数_2nd def SetDisp_02(): try: print("02") print(1+"")# こいつがエラーになる行( int+str でエラー) except: print("子02のエラー") print(sys.exc_info()) pass #****************************************************************************** # メインルーチン try: SetDisp_01() print("正常") except: print("親のエラー") print(sys.exc_info()) pass
このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの中でエラー発生)
という挙動になるように組み込みました。
実行結果
01 子01のエラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12788>) 02 子02のエラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B08>) 正常
B )外部のtryの外でエラーが起きた場合
import sys #****************************************************************************** # 呼び出す外部関数_1st def SetDisp_01(): try: print("01") print(1+"")# こいつがエラーになる行( int+str でエラー) except: print("子のエラー") print(sys.exc_info()) pass SetDisp_02() #****************************************************************************** # 呼び出す外部関数_2nd def SetDisp_02(): print('02') print(1+"")# こいつがエラーになる行( int+str でエラー) #****************************************************************************** # メインルーチン try: SetDisp_01() print("正常") except: print("親のエラー") print(sys.exc_info()) pass
このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの外でエラー発生)
という挙動になるように組み込みました。
実行結果
01 子のエラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B48>) 02 親のエラー (<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B88>)
まとめ
A)とB)の結果を比較して、
・親tryの中で呼んだ外部関数のtry(以下、子try)の中でエラーが起きたら、
そのエラーは子tryの中で完結するので親tryから見たら正常扱いとなる。
・子tryはその中で完結しているので、子try外でのエラーは親tryのエラーとなる。
そのため、エラー発生時に処理は中断されて親tryのexcept以下の処理が実行される。
以上です。
組み込んでて色々気になることを検証しているのですが、
説明がめんどくさくて記事に出来ない事ばっかりなので難しいですね。
今回もこんなに記事が長くなるとは思いませんでした。。。まとめ力が成長しない。。。
【Unity】バージョンアップ時にプロジェクトが開けなくなった
昨日、unityを起動したら急に発生した現象です。
アホみたいなことを繰り返してようやく復旧できたので
対処法をまとめておきます。
(ネットに全く参考記事が無くて孤独死しそうでした・・・)
バグった現象
Unityプロジェクト起動後に、
今まで問題なかったプロジェクトのコンポーネントがほぼエラー状態になっていた。
※自作スクリプトではなくUnityに元からあるものもエラーになっていた
色々確認していったのですが、
何かしらが原因でUnityEngineのdll参照が壊れているのかなー?
ぐらいしか分かりませんでした。
原因(予想)
原因は分からないのですが、心当たりがいくつかあるのでリスト化
・UnityHubの方のバージョンアップ通知があったので更新して
更新後にプロジェクトを開いた。
※これが原因だと思ってます。
でも、エディターでもないHubの更新でプロジェクトが壊れるとは思えない・・・
・当該PJは、当初2017バージョンのUnityエディタで制作していて
1ヶ月前ぐらいに2019バージョン(2019.2.11)に切り替えていた
※切り替え後も上手く動いていて、2019バージョンで
アプリリリースも行ってますので関係ないとは思う
上記の2つの要因を掛け合わせて予想した原因は、
UnityHubの更新を行ったことで
2017verエディタで作成したプロジェクトの2019verへの互換性がなくなった!
という事だと思ってます。
対応したこと(失敗編)
結局直りませんでしたが、行ったことをまとめておきます
・PCを再起動
→変わらず・
・UnityHub、Unityエディターを再インストール
→変わらず・・
・最新のエディター(2019.2.14)をインストールしてPJ(プロジェクト)を開く
→変わらず・・・(*_*;
・壊れたPJのAssetsフォルダ全てをエクスポートして新規作成PJにインポート
→新規作成PJは問題なく動くが、インポートしたとたん
「'csc'は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません」
というエラーが出てビルドできなかった・・・(; ・`д・´)
・PJのバックアップ(2017のエディターに対応)を引っ張り出してきて
2019バージョンのエディタで起動
→起動したPJが壊れていた・・・(*´з`)
この対応で、
「もしかしてHubの更新が原因で
2019verのエディタと2017verエディタの互換性がなくなったんじゃ・・・?」
と思いつきました。
対応したこと(解決編)
・PJのバックアップ(2017のエディターに対応)を2018.4.13のエディタで起動
2019バージョンのエディタで起動したら壊れたPJですが、
2018バージョンなら壊れずに正常に起動できました。
※2017バージョンでも起動できましたが、
APIレベル28以上の基準が満たせないので2018バージョンで起動
あとは、エディター周りの設定を一度削除してしまっていたので
AndroidSDK、NDKの環境を整えて上手くビルドできる様になりました。
分かったこと(大事!)
・UnityHubの更新でもバージョンの互換性は変化する
・バージョンごとに互換性があるため、安易に対応エディタのver更新をしない
・バックアップは偉大・・・( ..)φメモメモ
・2017verのプロジェクトと2018verのプロジェクトは互換性がある
いじょうです。
同じ境遇の方がいましたらご参考にしてみてください!
usingクラス取得ツール作った
namespaseを利用してクラス名を書かずに
別クラスのメソッド等を呼び出す方法がとても便利なのですが、
「using static ○○○」をスクリプトファイルごとに書くのが面倒くさい!
ということで、簡易的にツール化したのでご紹介します。
クラス名を書かずに別クラスのメソッド等を呼び出す方法
下記URLで説明してますので、ぜひご一読ください・・・
【C#6】別スクリプトの関数・定数を簡潔に呼び出す方法 - のにっき
ツール置き場
仕様書も置いているので一緒に落として確認してみてください。
使い方 ※仕様書抜粋
1)まずは、Assetsフォルダのパスを記入(ファイルのD&DでOK!)
2)「検索開始」ボタン押下で「using static ○○○」リストを出力!
※そのまま貼り付けれる形式でリスト化されてます
3)namespaseごとに、表示の切り替えができる様になってます
4)パスの記入内容は「保存する」にチェックしておくと、次回起動で
パスが記入された状態で開かれるのでとても便利です。
以上です。
ぜひ使ってみてご意見・ご感想などいただけたら嬉しいです!
○進数を○進数に変換する関数
私の会社は16進数のデータをいじくりまわすことがある。
いちいち「Convert」を使って変換するのもめんどくさいので
各進数を別の進数に変換する関数を作成しました。
ソースコード
/// <summary> /// 進数変換モード /// </summary> public enum Md_Conv { c10to2 = 0, c10to16, c16to2, c16to10, c2to10, c2to16 }; /// <summary> /// ○進数を○進数に変換 /// </summary> /// <param name="Mode">進数変換モード</param> /// <param name="SetWord">変換文字</param> /// <returns>変換後の文字※桁合わせしてる</returns> public static string Get_ConvertNum(Md_Conv Mode, string SetWord) { string setNumOff = SetWord.Trim(); string cnv = ""; switch (Mode) { case Md_Conv.c10to2: cnv = Convert.ToString(int.Parse(setNumOff), 2); cnv = cnv.PadLeft(8, '0'); break; case Md_Conv.c10to16: cnv = Convert.ToString(int.Parse(setNumOff), 16); cnv = "0x" + cnv.PadLeft(2, '0').ToUpper(); break; case Md_Conv.c16to10: cnv = Convert.ToInt32(setNumOff, 16).ToString(); break; case Md_Conv.c2to10: cnv = Convert.ToInt32(setNumOff, 2).ToString(); break; case Md_Conv.c16to2: cnv = Convert.ToInt32(setNumOff, 16).ToString(); cnv = Convert.ToString(int.Parse(cnv), 2); cnv = cnv.PadLeft(8, '0'); break; case Md_Conv.c2to16: cnv = Convert.ToInt32(setNumOff, 2).ToString(); cnv = Convert.ToString(int.Parse(cnv), 16); cnv = "0x" + cnv.PadLeft(2, '0').ToUpper(); break; } return cnv; }
使用例
▼ テストソース
// テスト用変数作成 int Num_10sin = 4095; string Num_2sin = "0111"; string Num_16sin = "FFFF"; string Num_16sin2 = "0x00FF"; // 各進数表記に変換 string Result = ""; Result = "◆ 10進数「Num_10sin ("+ Num_10sin + ")」を変換\r\n"; Result += "16進数:" + Get_ConvertNum(Md_Conv.c10to16, Num_10sin.ToString()) + "\r\n"; Result += " 2進数:" + Get_ConvertNum(Md_Conv.c10to2, Num_10sin.ToString()) + "\r\n"; Result += "◆ 2進数「Num_2sin (" + Num_2sin + ")」を変換\r\n"; Result += "16進数:" + Get_ConvertNum(Md_Conv.c2to16, Num_2sin) + "\r\n"; Result += "10進数:" + Get_ConvertNum(Md_Conv.c2to10, Num_2sin) + "\r\n"; Result += "◆ 16進数「Num_16sin (" + Num_16sin + ")」を変換\r\n"; Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin) + "\r\n"; Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin) + "\r\n"; Result += "◆ 16進数「Num_16sin2 (" + Num_16sin2 + ")」を変換\r\n"; Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin2) + "\r\n"; Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin2) + "\r\n"; MessageBox.Show(Result, "出力結果");
▼ 出力結果
関数概要
上記の感じで「16・10・2」進数の相互変換を行うことができます。
注意点としては、
10進数だろうが何だろうが文字列型でやり取りしている点です。
なので、数値型を引数として渡す場合はテストデータのように「ToString()」で文字列に直してください。
また、16進数の戻り値の表記は「0x」が頭について大文字になってます。
また、関数内の「cnv.PadLeft」の引数で0埋めの桁数を調節できます。
Scroll View内に高さが可変のノードを作る
ScrollViewの要素の高さを可変にしたい!
ScrollViewの要素にScrollViewを入れたい!という方にオススメ
設定を変えるだけで意外と簡単にできたので方法をまとめておきます
ヒエラルキーの内容
下図の感じでオブジェクトを設定します
「Scrl_Parent -> Content_P」の中にノード(P)を追加していくスクロールがあり、
そのノード(P)の中に、ノード(C)を追加していく。という仕様になります。
高さが可変のノードとは
ノード(C)が追加されるたびにノード(P)の高さが調節されるという仕様です。
スクロールのノードの高さがノードごとに可変であるという事がポイントです。
各オブジェクトの 設定
まずは、いつも通りScrollViewを作ってください。
その上で変更のある点は下記の2点です。
・親スクロールのContentオブジェクトの「○○ Layout Group」の
「Control Child Size」にチェックを入れる
・親スクロールに追加するノードに「○○ Layout Group」を追加する
▼ 下図の設定は「ヒエラルキーの内容」項目のオブジェクト名と
照らし合わせて参考にしてください
以上で、ScrollView内のノードに高さが可変のノードを作成することができます。