以下のようなクラスがあるとき、インスタンスの作成にとても時間がかかってしまいます。
public class Hoge
{
private int v;
public int Value => v;
public Hoge()
{
// Application.isPlayingはメインスレッドで呼ばなければ例外が発生する
Debug.Log($"Application.isPlaying:{Application.isPlaying}");
// コンストラクタで重い処理をしている
v = HeavyFunc();
}
private int HeavyFunc()
{
// 重い初期化処理
Thread.Sleep(5000);
// 計算結果
return 42;
}
}
インスタンスを作成するときにメインスレッドで行ってしまうとUIが止まってしまうのでできれば別スレッドで作成したいところです。
しかし、コンストラクタ内部でApplication.isPlaying
にアクセスしてしまっているため通常はメインスレッドでしか作成することができません。
Task.Run(() =>
{
try
{
var h = new Hoge();
Debug.Log(h.Value);
}
catch (Exception e)
{
// UnityException: get_isPlaying can only be called from the main thread.
// Constructors and field initializers will be executed from the loading thread when loading a scene.
// Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
Debug.LogException(e);
}
});
このクラスを書いたのが自分であれば修正すればいいですがソースコードを修正できない場合もあります。
そのような場合、最終手段として
System.Runtime.Serialization.FormatterServices.GetUninitializedObject
を使うことでコンストラクタを呼ばずにインスタンスを作成することができます。
インスタンスさえ作成できれば後はリフレクションで何とかなります。
// privateなフィールド、メソッドにアクセスするためにリフレクションを使用する
var vInfo = typeof(Hoge).GetField("v", BindingFlags.NonPublic | BindingFlags.Instance);
var heavyFuncInfo = typeof(Hoge).GetMethod("HeavyFunc", BindingFlags.NonPublic | BindingFlags.Instance);
_ = Task.Run(() =>
{
try
{
// コンストラクタを呼ばずにインスタンスを作成
var hoge = FormatterServices.GetUninitializedObject(typeof(Hoge)) as Hoge;
// HeavyFuncを実行する
var result = heavyFuncInfo.Invoke(hoge, null);
// フィールドに値をセット
vInfo.SetValue(hoge, result);
// 正しくインスタンスが作成されている
Debug.Log(hoge.Value);
}
catch (Exception e)
{
Debug.LogException(e);
}
});