没有无参构造函数的类型如何使用Activator.CreateInstance(Type)?

21

在工作中阅读现有代码,我想知道这是如何工作的。我有一个在程序集中定义的类:

[Serializable]
public class A
{
    private readonly string _name;
    private A(string name)
    {
        _name = name;
    }
}

在另一个程序集中:

public void f(Type t) {
    object o = Activator.CreateInstance(t);
}

需要调用 f(typeof(A))

我原本期望会抛出一个关于无参数构造函数缺失的异常,因为在我的理解中,如果已经声明了构造函数,编译器就不会生成默认的公共无参构造函数。

这段代码运行在.NET 2.0下。

[编辑]对不起,我误读了实际的代码... 我提供的示例并没有说明问题。我接受了JonH的答案,因为它提供了一条很好的信息。


@Codesleuth - 已经有一段时间了,有时候记住所有这些规则会引起头痛。请参见下面的答案。 - JonH
4个回答

39
一种替代方案是:
object obj = System.Runtime.Serialization.FormatterServices
          .GetUninitializedObject(t);

这会在内存中创建对象,但不会运行任何构造函数。有点可怕。


9
那真的挺吓人的,正是我需要的!+1 - ashes999
1
@ashes 通常只被序列化器、代理(RPC)、ORM等使用。 - Marc Gravell
它在我的简单测试dll类型变量上运行良好。 但是在更复杂的dll中,包含多个类、方法等,你的代码崩溃了。 在mscorlib.dll中发生了未处理的System.MemberAccessException类型异常。 额外的信息:无法创建抽象类。 - humudu
@humudu,是的,即使绕过构造函数,你也不能创建抽象类型。这在运行时根本不被支持。如果你成功创建了一个抽象实例,那么一定是出现了非常严重的问题。 - Marc Gravell
太棒了,真是个厉害的技巧。谢谢啊! - Fernell85
显示剩余2条评论

14

参见:使用反射在C#中创建没有默认构造函数的类型的实例

关于C# 4.0,以下是对未来的展望:

Microsoft发布于2010年1月4日下午2:08
我们已经审查了您的错误,并确定您描述的行为是按设计而非漏洞。我们现在正在将此问题归档。感谢您使用Visual Studio和.Net Framework!

Activator.CreateInstance有很多重载,其中一个只接受一个类型参数,仅调用默认的无参数构造函数。像你的示例代码中那样采用可选参数的构造函数并不是默认构造函数。要调用它们,您需要:

  1. 使用接受参数数组的重载
  2. 将Type.Missing作为参数传递
  3. 在BindingFlags中指定OptionalParamBinding

这里有个例子:

Activator.CreateInstance(typeof(MyClassName),
 BindingFlags.CreateInstance |
 BindingFlags.Public |
 BindingFlags.Instance |
 BindingFlags.OptionalParamBinding,
 null, new Object[] {Type.Missing}, null);

感谢您的阅读。

苏伟韬
微软公司


1
所以也许Seb在其他地方定义了类A,或者加载它的程序集没有被更新。这听起来更像是一个项目/解决方案问题,而不是代码异常。 - Codesleuth
@Codesleuth - 很有可能。 - JonH

0

在编程中,有两个容易混淆的CreateInstance版本:一个接收System.Type类型的对象作为参数,另一个则接收一个T类型的类型参数

首先是第一个版本:

object Activator.CreateInstance(type As Type) { }

第二个:

T Activator.CreateInstance<T>() { }

第二个不适用于没有公共无参构造函数的类。

注意,几乎从来没有理由使用第二个方法;在任何情况下都最好在您的类上具有where T:new() 约束并简单地使用T 的构造函数。

MSDN documentation 同意:

通常,应用程序代码中没有使用CreateInstance 的必要, 因为类型必须在编译时已知。如果在编译时已知该类型,则可以使用普通实例化语法(在C#中使用new运算符,在Visual Basic中使用New,在C ++中使用gcnew)。

编辑:我可能说得太早了;似乎第一个版本也不起作用。


1
是的,第一个版本会抛出相同的错误:为该对象定义了无参数构造函数。 - Evan

0

这可能对你来说不可行,但最简单的方法是在类型后面传递参数列表,像这样:

Activator.CreateInstance(t, "name");

你需要考虑一下f()实际上想要做什么。它是否应该实例化类型A的对象?它还会实例化哪些其他类?

一个可能的解决方案是在f()中使用switch语句,为类A传递正确的参数到CreateInstance()。这种方法无法扩展,但对你来说可能不是问题。


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