Почему компилятор С# испускает Activator.CreateInstance при вызове new in с общим типом с новым() ограничением?
Если у вас есть код вроде следующего:
static T GenericConstruct<T>() where T : new()
{
return new T();
}
Компилятор С# настаивает на том, чтобы вызывать вызов 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();
Но мне не имеет смысла, почему это обходное решение необходимо.
Ответы
Ответ 1
Я подозреваю, что это проблема JITting. В настоящее время JIT использует один и тот же сгенерированный код для всех аргументов ссылочного типа, поэтому a List<string>
vtable указывает на тот же машинный код, что и на List<Stream>
. Это не сработает, если каждый вызов new T()
должен быть разрешен в JIT-коде.
Просто догадайтесь, но это имеет определенный смысл.
Одна интересная маленькая точка: ни в одном случае не вызывается конструктор без параметров без значения, если он есть (что исчезает редко). Подробнее см. мое последнее сообщение в блоге. Я не знаю, есть ли способ заставить его в деревьях выражений.
Ответ 2
Это, вероятно, потому, что неясно, является ли T типом значения или ссылочным типом. Создание этих двух типов в неосновном сценарии создает очень разные ИЛ. Перед лицом этой двусмысленности С# вынужден использовать универсальный метод создания типа. Activator.CreateInstance подходит для счета.
Быстрое экспериментирование, похоже, поддерживает эту идею. Если вы введете следующий код и изучите IL, он будет использовать initobj вместо CreateInstance, потому что не существует двусмысленности в типе.
static void Create<T>()
where T : struct
{
var x = new T();
Console.WriteLine(x.ToString());
}
Переключение на ограничение класса и new(), хотя все равно вынуждает Activator.CreateInstance.
Ответ 3
Почему это обходное решение необходимо?
Поскольку новое() общее ограничение было добавлено в С# 2.0 в .NET 2.0.
Выражение < Т > и друзья, тем временем, были добавлены в .NET 3.5.
Итак, ваше обходное решение необходимо, потому что это невозможно в .NET 2.0. Между тем (1) использование Activator.CreateInstance() было возможным, и (2) IL не имеет способа реализовать "новый T()", поэтому Activator.CreateInstance() использовался для реализации этого поведения.
Ответ 4
Интересное наблюдение:)
Вот более простое решение для вашего решения:
static T Create<T>() where T : new()
{
Expression<Func<T>> e = () => new T();
return e.Compile()();
}
Очевидно, наивный (и возможно медленный):)
Ответ 5
Это немного быстрее, поскольку выражение компилируется только один раз:
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; } }