Почему компилятор c # генерирует Activator.CreateInstance при вызове new с универсальным типом с ограничением new ()? - PullRequest
16 голосов
/ 15 декабря 2008

Когда у вас есть код, подобный следующему:

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 ]

9 голосов
/ 15 декабря 2008

I подозреваю, это проблема JITting. В настоящее время JIT повторно использует один и тот же сгенерированный код для всех аргументов ссылочного типа - поэтому таблица v List<string> указывает на тот же машинный код, что и List<Stream>. Это не сработает, если каждый вызов new T() должен быть разрешен в коде JITted.

Просто предположение, но оно имеет определенное количество смысла.

Один интересный маленький момент: в ни в одном случае не вызывается конструктор без параметров типа значения, если он есть (что крайне редко). См. мой недавний пост в блоге для подробностей. Я не знаю, есть ли способ заставить это в деревьях выражений.

8 голосов
/ 15 декабря 2008

Это вероятно потому, что неясно, является ли 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.

3 голосов
/ 10 июня 2009

Зачем нужен этот обходной путь?

Поскольку общее ограничение new () было добавлено в C # 2.0 в .NET 2.0.

Выражение и друзья тем временем были добавлены в .NET 3.5.

Так что ваш обходной путь необходим, потому что это было невозможно в .NET 2.0. Между тем, (1) использование Activator.CreateInstance () было возможно, и (2) у IL нет способа реализовать 'new T ()', поэтому Activator.CreateInstance () был использован для реализации этого поведения.

2 голосов
/ 15 августа 2009

Это немного быстрее, поскольку выражение компилируется только один раз:

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() (в 160 раз быстрее на моем тестовом ПК).

Для чуть более высокой производительности можно исключить вызов метода сборки и вместо него можно вернуть функтор, который клиент может кэшировать и вызывать напрямую.

public static Func<T> BuildFn { get { return f; } }
2 голосов
/ 15 декабря 2008

Интересное наблюдение:)

Вот более простой вариант вашего решения:

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

Очевидно наивный (и, возможно, медленный):)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...