反射调用抛出异常的构造函数遇到的问题:以System.RuntimeType为例
首先得澄清一个关键点:当构造函数抛出异常时,CLR其实不会返回一个完全有效的对象——你提到的“普通情况下对象仍会被构造”可能是一种误解,或者是某些调试场景下的特殊表现。不过回到你的核心问题:调用internal修饰的RuntimeType构造函数时,反射调用抛出的TargetInvocationException会包裹原始异常,同时未完成构造的实例会被直接丢弃,这确实是反射API的默认行为。
为什么会出现这种差异?
当你直接调用构造函数(如果能访问的话),CLR会先分配对象内存,再执行构造逻辑——如果构造函数中途抛异常,内存里的实例可能还存在,但它并没有完成初始化,也不会被返回给调用者;而反射调用(比如ConstructorInfo.Invoke()或Activator.CreateInstance())的框架逻辑会在构造失败后自动清理这个未完全初始化的对象,因为反射API默认假设:构造失败的实例是不可用的,不应该被传递到调用层。
如何获取这个未完全构造的实例?
如果你确实因为调试或研究需要拿到这个实例(强烈不建议在生产代码中这么做,因为未初始化的对象会处于不稳定状态),可以试试这两种方法:
1. 使用FormatterServices.GetUninitializedObject创建未初始化实例
.NET官方提供了这个API,可以直接创建一个不调用构造函数的对象实例,之后再手动调用构造函数:
using System; using System.Reflection; using System.Runtime.Serialization; Type runtimeType = typeof(RuntimeType); // 创建未初始化的实例(不执行构造函数) object instance = FormatterServices.GetUninitializedObject(runtimeType); try { // 获取内部构造函数并调用 ConstructorInfo ctor = runtimeType.GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); ctor.Invoke(instance, null); } catch (TargetInvocationException ex) { // 捕获包装的原始异常 Console.WriteLine($"构造函数抛出异常:{ex.InnerException.Message}"); // 此时instance已经存在,可继续操作(但风险极高) }
2. 非托管代码手动分配内存并调用构造函数
这种方法更底层,直接绕开反射的包装逻辑,但危险性也更高:
using System; using System.Reflection; using System.Runtime.InteropServices; public static unsafe object CreateInstanceWithRawMemory(Type type) { // 手动分配对象内存 IntPtr objPtr = Marshal.AllocHGlobal(Marshal.SizeOf(type)); try { // 获取构造函数的方法指针 ConstructorInfo ctor = type.GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); IntPtr ctorFuncPtr = ctor.MethodHandle.GetFunctionPointer(); // 转换为委托并直接调用构造函数 var ctorDelegate = (Action<IntPtr>)Marshal.GetDelegateForFunctionPointer(ctorFuncPtr, typeof(Action<IntPtr>)); ctorDelegate(objPtr); // 将指针转换为对象实例 return Marshal.PtrToStructure(objPtr, type); } catch (Exception ex) { Console.WriteLine($"构造异常:{ex.Message}"); // 即使构造失败,内存中的实例依然存在 return Marshal.PtrToStructure(objPtr, type); } finally { // 注意:不要释放内存,返回的对象还在使用 // Marshal.FreeHGlobal(objPtr); } }
最后提醒
RuntimeType是.NET运行时的核心内部类型,它的构造函数抛出NotSupportedException就是明确的信号:设计上就不允许用户直接实例化它。除非你在做极端的调试或研究工作,否则千万不要尝试实例化这个类型,否则可能导致不可预测的运行时问题。
内容的提问来源于stack exchange,提问作者MrLore

