Повече любопитни факти, отколкото наистина важни: Защо няма ограничение new() за Activator.CreateInstance‹T›()?

Мисля, че има хора, които могат да отговорят на това, това е въпрос от любопитство:

Генеричният CreateInstance метод от System.Activator, въведен в .NET v2, няма ограничения за типа на общия аргумент, но изисква конструктор по подразбиране за активирания тип, в противен случай се хвърля MissingMethodException. За мен изглежда очевидно, че този метод трябва да има ограничение на типа като

Activator.CreateInstance<T>() where T : new() {
   ...
}

Просто пропуск или някакъв анекдот се крие тук?

Актуализация

Както беше посочено, компилаторът не ви позволява да пишете

private T Create<T>() where T : struct, new()
error CS0451: The 'new()' constraint cannot be used with the 'struct' constraint

Въпреки това, вижте коментарите, структура може да се използва като аргумент за тип към общ метод, указващ ограничение new(). При това обстоятелство даденият отговор изглежда единствената валидна причина да не се ограничава методът...

Благодаря, че разгледахте това!


person flq    schedule 03.03.2011    source източник
comment
+1 Много интересен въпрос. Не мога да се сетя за причина да не е там, така че и аз съм любопитен да разбера защо!   -  person Adam Robinson    schedule 04.03.2011
comment
Що се отнася до вашата актуализация, защото е излишна; всички типове стойности имат конструктор по подразбиране, който инициализира всички стойности по подразбиране (това е, което получавате, когато извикате default(T).   -  person Adam Robinson    schedule 04.03.2011
comment
Всички структури имат ctors по подразбиране в C#.   -  person Linkgoron    schedule 04.03.2011
comment
Само за пояснение, като се има предвид, че void Foo<T>() where T:new() {}, Foo<DateTime>(); или Foo<int>(); са напълно валидни извиквания, от които компилаторът няма да се оплаче.   -  person Adam Robinson    schedule 04.03.2011
comment
Страхотен въпрос. Объркана съм. Ограничението new изглежда е въведено едновременно с генеричните продукти, така че не е като проблем с обратната съвместимост. Може би просто са забравили?   -  person StriplingWarrior    schedule 04.03.2011


Отговори (1)


Може да греша, но основното предимство, както го виждам, е, че ви позволява да направите нещо подобно:

// Simple illustration only, not claiming this is awesome code!
class Cache<T>
{
    private T _instance;

    public T Get()
    {
        if (_instance == null)
        {
            _instance = Create();
        }

        return _instance;
    }

    protected virtual T Create()
    {
        return Activator.CreateInstance<T>();
    }
}

Обърнете внимание, че ако Activator.CreateInstance<T> имаше ограничение where T : new(), тогава класът Cache<T> по-горе също би се нуждаел от това ограничение, което би било прекалено ограничаващо, тъй като Create е виртуален метод и някои производни класове може да искат да използват различни средства за инстанциране, като например извикване на вътрешен конструктор на тип или използване на статичен метод за изграждане.

person Dan Tao    schedule 03.03.2011
comment
+1. Това е отлична точка. Въпреки че поемате известен риск (възможно е T да няма конструктор по подразбиране тук, очевидно), това изглежда като напълно валиден случай. - person Adam Robinson; 04.03.2011
comment
Между другото, изглежда, че System.Runtime.CompilerServices.ConditionalWeakTable прави точно това. - person Adam Robinson; 04.03.2011
comment
Също така си струва да се отбележи, че метод CreateInstance<T>, ограничен с where T : new(), би бил напълно излишен. Тъй като T трябва да се знае по време на компилиране, тогава всяко T ограничено с where T : new() може да бъде създадено с помощта на var x = new T() синтаксис. - person LukeH; 04.03.2011
comment
@LukeH: Ха, да, някак си е смешно да мислиш, че това е въпросът, който се задава, докато ако Activator.CreateInstance<T> did имаше ограничението where T : new(), със сигурност щеше да има ТАКЪВ въпрос, Какво по дяволите е точката на Activator.CreateInstance<T>? - person Dan Tao; 04.03.2011
comment
@Dan, LukeH: Колкото и да си струва, обажданията до new T() всъщност се превръщат в обаждания до Activator.CreateInstance<T>(). - person Adam Robinson; 04.03.2011
comment
@Adam: Това е вярно за реф-типове, но доколкото ми е известно, new T() винаги ще се компилира до initobj инструкция за стойностни типове. (Не съм сигурен защо new T() не може да се компилира до newobj инструкция за реф-типове, а не до извикване на CreateInstance<T>.) - person LukeH; 04.03.2011
comment
@Adam: Ако декларирам метод T M<T>() where T : new() { return new T(); }, тогава пътят на стойностния тип е специален регистър с помощта на initobj, компилирайки се до нещо приблизително еквивалентно на if (default(T) != null) return default(T); return Activator.CreateInstance<T>(); - person LukeH; 04.03.2011