有時我期望只是創建出對象,但是不要調用對象的構造方法,可以通過使用 FormatterServices 的 GetUninitializedObject 函數來實現只創建對象不調用構造函數方法
這個 FormatterServices.GetUninitializedObject 方法大部分是用在做序列化使用的,然而在很多 IOC 容器,也都使用此方法來創建對象,而通過其他方法拿到構造函數
在 WPF 的 XAML 創建對象,也有用到此方法,詳細請看 dotnet 讀 WPF 源代碼筆記 XAML 創建對象的方法
以下是一個實現的例子
Foo foo = null;
try
{
foo = (Foo) FormatterServices.GetUninitializedObject(typeof(Foo));
var constructorInfo = typeof(Foo).GetConstructor(new Type[0]);
constructorInfo!.Invoke(foo, null);
}
catch
{
}
class Foo
{
}
此方法可以用來處理在構造函數時,如果拋出了異常,但是此對象的 Dispose 需要被顯式調用的問題。因爲如果在構造函數拋出異常,那麼在 C# 代碼層面將拿不到此對象,也就無法調用對應的 Dispose 釋放
如以下代碼,可以看到 Foo 對象依然是空
private void F1()
{
Foo foo = null;
try
{
foo = new Foo();
}
catch
{
// 忽略
}
}
class Foo : IDisposable
{
public Foo()
{
throw new Exception("lindexi is doubi");
}
~Foo()
{
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
此時如果期望調用 Foo 對象的 Dispose 方法,將會因爲拿不到對象而無法調用
解決此方法的做法就是通過只創建對象而不調用構造的方法,先拿到對象然後再調用構造,如果構造出錯,依然還可以調用對象的 Dispose 方法
private void F2()
{
Foo foo = null;
try
{
foo = (Foo) FormatterServices.GetUninitializedObject(typeof(Foo));
var constructorInfo = typeof(Foo).GetConstructor(new Type[0]);
constructorInfo!.Invoke(foo, null);
}
catch
{
// 忽略
}
finally
{
try
{
foo?.Dispose();
}
catch
{
// 可以調用到 Dispose 方法
}
}
}
class Foo : IDisposable
{
public Foo()
{
throw new Exception("lindexi is doubi");
}
~Foo()
{
Dispose();
}
public void Dispose()
{
GC.SuppressFinalize(this);
throw new Exception($"lsj is doubi");
}
}
這個設計可以用來解決,如果對象的構造函數還沒完全完成,調用釋放函數將會拋出異常。如果沒有使用如上方法,那麼在釋放函數的異常將會在 GC 回收線程拋出,而讓應用程序退出
這就是爲什麼有很多容器和底層庫喜歡使用此方法創建對象的原因
可以通過如下方式獲取本文的源代碼,先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 11077dd21a4ee5314757536ca379ecca6956b040
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
獲取代碼之後,進入 HojeneceabuHallwhallhebo 文件夾
FormatterServices.GetUninitializedObject(Type) Method (System.Runtime.Serialization)