Why is Activator.CreateInstance<T>() allowed without the new() generic type constraint?
Asked Answered
B

3

10

In the sample code shown below, the "CompileError" method won't compile, because it requires the where T : new() constraint as shown in the CreateWithNew() method. However, the CreateWithActivator<T>() method compiles just fine without a constraint.

public class GenericTests
{
    public T CompileError<T>() // compile error CS0304
    {
        return new T();
    }

    public T CreateWithNew<T>() where T : new() // builds ok
    {
        return new T();
    }

    public T CreateWithActivator<T>() // builds ok
    {
        return Activator.CreateInstance<T>();
    }
}

Why is this?

According to https://mcmap.net/q/402609/-activator-createinstance-lt-t-gt-vs-new, which references MSDN documentation, and this question, the new T() expression in generics is actually implemented using Activator.CreateInstance<T>(). So I don't understand why calling new T() requires that the generic type be constrained in a way that can be omitted when using Activator.CreateInstance<T>().

Or, to put the question the other way around: what's the point of the where T : new() constraint, if it's easy to create instances of T in a generic method without the constraint, by directly using the exact same underlying infrastructure?

Becca answered 12/9, 2016 at 5:58 Comment(4)
Because CreateInstance() just uses plain old reflection and isn't subject to any constraints. If the type has a default constructor, it will create it.Avens
This is self-control thing. You can do pretty much everything through reflection (even destroy all OOP principles), but many will not appreciate it, cause its dirty.Noyade
There are plenty of ways of writing awkward code that lets you eliminate a compile time error and generate a runtime one instead. This is just one instance. E.g. dynamic lets you write calls to functions that don't exist, so instead of a compiler error you get a runtime exception. Similarly, here, CreateInstance lets you postpone finding out that no parameterless constructor exists until runtime.Hand
The question "what's the point of the where T : new() constraint" can be distilled to "what's the point of type constraints?" They both have the same answer.Vetiver
L
13

There is a conceptual difference between Activator and T():

  • Activator.CreateInstance<T> — I want to create a new instance of T using its default constructor — And throw an Exception if it doesn't have one (Since something very wrong has happened and I want to handle it/throw it myself).

    • Side note: keep in mind that as MSDN says:

      In general, there is no use for the CreateInstance<T>() generic method in application code, because the type must be known at compile time. If the type is known at compile time, normal instantiation syntax can be used.

      since generally you would want to use a constructor when the Type is known at compile time (CreateInstance<T>() uses RuntimeTypeHandle.CreateInstance which is slower [Also that's why Activator.CreateInstance<T> itself doesn't need the constraint]).

  • T() — I Want to call the empty constructor of T, supposedly like a standard constructor call.

You don't want a "standard" constructor call to fail because "No such constructor was found", therefore, the compiler wants you to constraint that there is one.

More than that; You should prefer Compile time errors over Exceptions where possible.

The fact that T() is implemented internally using reflection irrelevant for the average case of "I just want a default instance of T" (of course that the internal implementation is important if you care about performance/etc...).

Loudhailer answered 12/9, 2016 at 6:10 Comment(0)
N
2

This is just sugar. Self-control sugar if you can say so. For example, you can call through reflection almost any method in any type (in some cases even without instance!), but this is just not right, don't you agree? Your code will just become unmaintainable at some point, and many errors will pop-up in execution time, this is very bad. So, if you can control yourself before execution - just do it.

Constraint will help you to understand it in compile-time.

Noyade answered 12/9, 2016 at 6:12 Comment(0)
M
1

The Activator.CreateInstance<T>() method is exposed to user code to allow for the possibility that a generic class might be usable in different ways, some of which would require that the type parameter satisfy certain constraints and some of which don't. For example, a class Foo<T> might support any of the following usage patterns:

  1. Client code supplies a function that returns a new T.

  2. Client code defers to a default function which creates a new T using its default constructor.

  3. Client code avoids using any features of the class that would require it to create new instances of T.

Patterns #1 and #3 should be usable with any T, while #2 should only work with types having parameterless constructors. Having Activator.CreateInstance<T>() compile for an unconstrained T, and work for types T that happen to have parameterless constructors, makes it easy to have code support all three usage patterns. If Activator.CreateInstance<T> had a new constraint, it would be very awkward to use it with generic type parameters that don't have one.

Mycobacterium answered 12/9, 2016 at 18:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.