为什么c#编译器在使用new()约束的泛型类型调用new in时会发出Activator.CreateInstance?

时间:2008-12-15 05:56:35

标签: c# performance generics constructor

如果您拥有以下代码:

static T GenericConstruct<T>() where T : new()
{
    return new T();
}

C#编译器坚持发出对Activator.CreateInstance的调用,这比本机构造函数慢得多。

我有以下解决方法:

public static class ParameterlessConstructor<T>
    where T : new()
{
    public static T Create()
    {
        return _func();
    }

    private static Func<T> CreateFunc()
    {
        return Expression.Lambda<Func<T>>( Expression.New( typeof( T ) ) ).Compile();
    }

    private static Func<T> _func = CreateFunc();
}

// Example:
// Foo foo = ParameterlessConstructor<Foo>.Create();

但是,为什么这个解决方案应该是必要的,这对我没有意义。

5 个答案:

答案 0 :(得分:9)

怀疑这是一个JITting问题。目前,JIT为所有引用类型参数重用相同的生成代码 - 因此List<string>的vtable指向与List<Stream>相同的机器代码。如果必须在JITted代码中解析每个new T()调用,那将无效。

只是一个猜测,但它会产生某种的意义。

一个有趣的小观点:在 情况下,值类型的无参数构造函数会被调用,如果有的话(很少见)。有关详细信息,请参阅my recent blog post。我不知道是否有任何方法可以在表达式树中强制它。

答案 1 :(得分:8)

这很可能是因为不清楚T是值类型还是引用类型。在非通用场景中创建这两种类型会产生非常不同的IL。面对这种模糊性,C#被迫使用通用的类型创建方法。 Activator.CreateInstance符合要求。

快速实验似乎支持这一想法。如果键入以下代码并检查IL,它将使用initobj而不是CreateInstance,因为类型没有歧义。

static void Create<T>()
    where T : struct
{
    var x = new T();
    Console.WriteLine(x.ToString());
}

将其切换为类和new()约束但仍会强制激活Activator.CreateInstance。

答案 2 :(得分:3)

为什么这种解决方法是必要的?

因为new()泛型约束被添加到.NET 2.0中的C#2.0。

表达式来; T&GT;与此同时,朋友们加入了.NET 3.5。

因此,您的解决方法是必要的,因为在.NET 2.0中无法实现。同时,(1)使用Activator.CreateInstance()是可能的,(2)IL缺乏实现'new T()'的方法,因此使用Activator.CreateInstance()来实现该行为。

答案 3 :(得分:2)

有趣的观察:)

以下是您的解决方案的更简单的变体:

static T Create<T>() where T : new()
{
  Expression<Func<T>> e = () => new T();
  return e.Compile()();
}

显然天真(并且可能很慢):)

答案 4 :(得分:2)

这有点快,因为表达式只编译一次:

public class Foo<T> where T : new()
{
    static Expression<Func<T>> x = () => new T();
    static Func<T> f = x.Compile();

    public static T build()
    {
        return f();
    }
}

分析性能,此方法与更详细的编译表达式一样快,速度比new T()快得多(在我的测试PC上快160倍)。

为了获得更好的性能,可以消除构建方法调用,并且可以返回函数,客户端可以缓存并直接调用。

public static Func<T> BuildFn { get { return f; } }