通过Activator.CreateInstance创建一个可空对象返回null

11
我正在创建一个将小脚本转化为dll的系统。当我试图将可空值类型作为参数的默认值时,遇到了问题。
问题是我需要在编译器内创建用户选择的可空值类型的实例并将其设置为常量。
不幸的是,每当我使用 Activator.CreateInstance(NullableType) (其中NullableType是从用户输入创建的类型)时,返回值总是null。例如,仅执行以下代码:
object Test = Activator.CreateInstance(typeof(Nullable<int>));
Console.WriteLine(Test == null);

返回true。这仅适用于Nullables。在C#中创建的结构,即使是通用结构,也可以正常创建。如果我从DotNetReflector复制可空结构(并删除TypeDependencyAttribute,TargetPatchingOptOutAttribute和ThrowHelper调用,因为我无法访问它们),它会显示上面的False。

发生了什么?是否有其他可能的方法可以在运行时不知道通用参数的情况下创建可空对象?

2个回答

6

来自这篇MSDN博客

以前从未预期过Activator.CreateInstance会返回null;但是通过这个DCR,当创建Nullable类型的实例但没有提供非null T值时,它将返回null。例如,Activator.CreateInstance(typeof(Int32?))将返回null。

问题与如何将Nullable<T>变量装箱有关,如博客文章所解释。


这回答了我两个问题中的一个。此外,在看到你的回答前几分钟,我通过MSDN文章找到了这个答案:http://msdn.microsoft.com/en-us/library/ms228597.aspx。那么问题来了:在不知道其参数直到运行时的情况下如何创建可空类型,并将其传递给ParameterBuilder.SetConstant?即使将null可空类型传递给SetContstant也会因博客和MSDN文章中引用的原因而失败。 - Kevin Fee
我在研究中偶然发现了一个类似的问题。不过他们的情况略有不同:https://dev59.com/NnI_5IYBdhLWcg3wMf9_ - M.Babcock
我不知道这是否能回答你的第二个问题,但是你可以使用反射(https://dev59.com/03I95IYBdhLWcg3w8y2N)在运行时动态创建符合你要求的泛型类型,并将结果动态类型传递给`Activator.CreateInstance`,就像你在问题中展示的那样。我实际上有这方面的经验,如果你需要帮助,请告诉我们。 - M.Babcock

3
问题在于ParameterBuilder.SetConstant,而非Activator.CreateInstanceSetConstant是一个用于定义可选参数默认值的函数,并需要提供具体值作为常量。对于引用类,null是一个有效的具体值,但对于在创建Nullable<>之前的值类,null不是一个有效的值。例如,如何将null转换为int? 因此,SetConstant会检查值类型以查看传递作为常量的具体值是否为null,并抛出一个ArgumentException作为比在值类中将null取消拆装时获得的NullReferenceException更具描述性的错误。
Nullable<>的情况下,null现在是值类的有效具体值。正如使用Activator.CreateInstance所看到的那样,Nullable<>本身是一个值类,并且将null取消打包成Nullable<int>是有意义的。这就是SetConstant出现错误的地方:它没有考虑到这一点,并为实际上不是错误的事情抛出了一个“描述性”的错误。由于缺乏来自Microsoft的错误修复,任何对带有null Nullable<>SetConstant的调用都将不得不实现由不正确条件保护的行为。这意味着必须使用反射来深入了解ParameterBuilder的私有方法和字段。这是我编写的代码,只处理这种情况。在不出现错误的情况下应使用标准的SetConstant函数。
//ModuleBuilder module : a parameter passed into the containing function, the module which is being built (which contains the method that has the optional parameter we are setting the constant for)
//ParameterBuilder optParam : A parameter passed into the containing function, the parameter that is being built which is to have the null default value.
MethodInfo method = typeof(TypeBuilder).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
    .Where(m => m.Name == "SetConstantValue" && m.GetParameters().Length > 0 && m.GetParameters()[0].ParameterType.Name == "RuntimeModule")
    .Single();
var RuntimeHandle = typeof(ModuleBuilder).GetMethod("GetNativeHandle", BindingFlags.NonPublic | BindingFlags.Instance);
method.Invoke(null, new object[]
{
    RuntimeHandle.Invoke(module, new object[]{}),
    optParam.GetToken().Token,
    0x12,
    null
});

我已向微软报告了这个漏洞。他们回应说它不会在.NET 3.5中修复,但已添加到内部漏洞数据库。
更新:
该漏洞已在.NET 4.0中修复。ParameterBuilder.SetConstant现在有一个分支到constant == null条件,该条件检查值类型是否是从Nullable<>派生的通用类型,仅在不是时引发异常。

模块和optParam是什么意思? - Erwin Mayer
  1. 非常疯狂的黑客技巧。
  2. Nullables 在对象形式中为空 - 这是设计上的考虑。
  3. 请提供更多描述。
- Vlad
是的,这是一个疯狂的黑客。它是为了解决ParameterBuilder.SetConstant具有if(value == null) throw new ArgumentException(...)而设计的。ParameterBuilder.SetConstant实际使用的本机代码(它是私有的,我正在上面反射)对于可为空的值为null是可以接受的,因为这是按设计来的。只是公开的托管函数包含了一个错误,并需要绕过。该错误已经被修复,所以除非你的目标是3.5或更低版本,否则不再需要此答案。 - Kevin Fee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接