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

105
以以下课程为例:
class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

我希望您能使用反射创建此类型的实例:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

通常这样做是可以的,但是由于SomeType没有定义无参构造函数,调用Activator.CreateInstance将会抛出一个类型为MissingMethodException的异常,其信息为"未为此对象定义无参数构造函数。"是否有其他方法可以创建此类型的实例?如果我要给所有类都添加无参构造函数,那就太糟糕了。


3
FormatterServices.GetUninitializedObject 不允许创建未初始化的字符串。 您可能会遇到异常:System.ArgumentException: 无法创建未初始化的字符串。 请记住这一点。 - Bartosz Pierzchlewicz
谢谢提醒,但我已经在分别处理字符串和基本类型了。 - Aistina
4个回答

152
我最初在这里发布了这个答案here,但是这里重新印刷一下,因为这不是完全相同的问题,但是答案是一样的: FormatterServices.GetUninitializedObject()将创建一个实例而不调用构造函数。我通过使用Reflector并查找一些核心的.Net序列化类来找到了这个类。
我使用下面的示例代码进行了测试,看起来效果很好:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}

.NET 8 就在眼前,而且 FormatterService 的过时 也即将到来。
你可以使用 RuntimeHelpers.GetUninitializedObject,这个方法与 FormatterService.GetUninitializedObject 在底层调用上是一样的。

14
@JSBangs,很遗憾你否定了一个完全合理的答案。你和其他回答实际上没有回答问题。如果你觉得你有更好的答案,那就提供一个。但我提供的答案强调如何像其他序列化类一样使用这段代码中记录的类。 - Jason Jackson
21
FormatterServices (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatterservices.aspx) 并非未记录文档。 - Sandeep Datta
3
使用GetUninitializedObject似乎取决于你的具体目的:因为对象的新实例被初始化为零且未运行任何构造函数,所以该对象可能不表示该对象视为有效的状态。当前方法仅应用于反序列化,当用户打算立即填充所有字段时才应使用它。它不会创建未初始化的字符串,因为创建一个空的不可变类型实例没有任何作用。 - Cel
@Cal - 是的,这是一个非常强大的函数,应该谨慎使用。自从我发布这个代码以来,在过去的三年里,我只在一个地方使用过这个函数。 - Jason Jackson
哇,我一直在苦苦寻找这样的解决方案。现在我感到非常惊讶。感觉像是一个“黑客”技巧。 - Ergis
显示剩余3条评论

73

1
这个解决方案过于简单化问题了。如果我不知道我的类型,而且我说“只需在此类型变量中创建一个类型对象”怎么办? - kamii
@kamii 如果一个类既没有明确的类型,也不知道其所需参数,则您该如何使用它? - Mitulát báti

25

当我对(T)FormatterServices.GetUninitializedObject(typeof(T))的性能进行了基准测试时,发现速度较慢。与此同时,编译表达式可以带来巨大的速度提升,但只适用于具有默认构造函数的类型。因此,我采取了一种混合方法:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

这意味着创建表达式将被有效地缓存,并且只在类型第一次加载时产生惩罚。也会以高效的方式处理值类型。
调用它:
MyType me = New<MyType>.Instance();

注意,对于字符串,(T)FormatterServices.GetUninitializedObject(t)将失败。因此,特殊处理字符串以返回空字符串。

1
很奇妙,看一眼别人代码的一行就能节省一整天的时间。谢谢您,先生!出于性能原因,我找到了您的帖子,这个技巧真是太棒了 :) FormatterServices 和 Activator 类相比编译表达式表现不佳,可惜我们到处都能找到 Activators。 - jmodrak
关于您对字符串的特殊处理,我知道如果没有这个特殊处理,它会失败,但我想知道:它是否适用于所有其他类型?@nawfal - Sнаđошƒаӽ
很遗憾,给出的示例是基础的,而.NET有许多不同类型的类型。例如,如果您传递一个委托类型,您将如何给它一个实例?或者如果构造函数抛出异常,你能做些什么?有许多不同的处理方式。自从回答这个问题以来,我已经更新了我的库以处理更多的情况。目前还没有发布在任何地方。 - nawfal

4

这里有一些好的答案,但是在 .NET Compact Framework 中无法使用。这是一个可以在 CF.Net 上工作的解决方案...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}

1
这是我调用非默认构造函数的方式。我不确定是否真的想要创建一个没有调用任何构造函数的对象。 - Rory MacLeod
2
如果您正在编写自定义序列化程序,可能需要在不调用构造函数的情况下创建对象。 - Sandeep Datta
1
是的,这正是这个问题的确切使用情况场景 :) - Aistina
1
@Aistina 或许你可以把这个信息加到问题里面吗?大多数人都会反对在不调用构造函数的情况下创建对象,并且会花时间与你争论这个问题,但是你的使用情况确实证明了它的必要性,所以我认为这非常相关于问题本身。 - julealgon

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