C# 为什么Activator.CreateInstance<;T>;()在没有new()泛型类型约束的情况下是否允许?

C# 为什么Activator.CreateInstance<;T>;()在没有new()泛型类型约束的情况下是否允许?,c#,.net,generics,activator,type-constraints,C#,.net,Generics,Activator,Type Constraints,在下面显示的示例代码中,“CompileError”方法不会编译,因为它需要where t:new()约束,如CreateWithNew()方法中所示。但是,CreateWithActivator()方法在没有约束的情况下编译得很好 public class GenericTests { public T CompileError<T>() // compile error CS0304 { return new T(); } pub

在下面显示的示例代码中,“CompileError”方法不会编译,因为它需要
where t:new()
约束,如
CreateWithNew()
方法中所示。但是,
CreateWithActivator()
方法在没有约束的情况下编译得很好

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>();
    }
}
公共类泛型测试
{
public T CompileError()//编译错误CS0304
{
返回新的T();
}
public T CreateWithNew(),其中T:new()//构建确定
{
返回新的T();
}
public T CreateWithActivator()//生成正常
{
返回Activator.CreateInstance();
}
}
为什么会这样

根据引用的和,泛型中的
newt()
表达式实际上是使用
Activator.CreateInstance()
实现的。所以我不明白为什么调用
newt()
需要以一种在使用
Activator.CreateInstance()时可以忽略的方式约束泛型类型


或者,用另一种方式来回答这个问题:
where T:new()
约束的意义是什么,如果在没有约束的通用方法中很容易创建
T
的实例,通过直接使用完全相同的基础结构?

Activator
T()
之间存在概念上的差异:

  • Activator.CreateInstance
    -我想使用它的默认构造函数创建一个
    T
    的新实例-,如果它没有异常则抛出一个
    异常(因为发生了一些非常错误的事情,我想自己处理/抛出)

    • 旁注:请记住:

      通常,应用程序代码中的
      CreateInstance()
      泛型方法没有任何用处,因为类型必须在编译时已知。如果类型在编译时已知,则可以使用正常的实例化语法

      因为通常情况下,当编译时已知
      类型时(
      CreateInstance()
      速度较慢[这也是
      Activator.CreateInstance
      本身不需要约束的原因]),您会希望使用构造函数

  • T()
    -我想调用
    T
    的空构造函数,应该像标准构造函数调用一样

您不希望“标准”构造函数调用失败,因为“没有找到这样的构造函数”,因此,编译器希望您约束存在一个构造函数

不仅如此在可能的情况下,您应该更喜欢编译时错误而不是
异常


T()
是使用反射在内部实现的,这一事实与“我只想要一个
T
的默认实例”的一般情况无关(当然,如果您关心性能/等等,内部实现是重要的)。

这只是糖分。如果可以的话,自我控制糖。例如,您几乎可以通过反射调用任何类型的任何方法(在某些情况下甚至没有实例!),但这是不对的,您不同意吗?您的代码在某个时候将变得不可维护,并且在执行时会弹出许多错误,这是非常糟糕的。所以,如果你能在行刑前控制自己,那就去做吧


约束将帮助您在编译时理解它。

Activator.CreateInstance()
方法公开给用户代码,以允许泛型类以不同的方式可用,其中一些方式要求类型参数满足某些约束,而另一些则不满足。例如,类
Foo
可能支持以下任何一种使用模式:

  • 客户机代码提供了一个返回新
    T
    的函数

  • 客户机代码遵从默认函数,该函数使用默认构造函数创建新的
    T

  • 客户机代码避免使用类的任何特性来创建
    T
    的新实例


  • 模式#1和#3应可用于任何
    T
    ,而#2应仅用于具有无参数构造函数的类型。让
    Activator.CreateInstance()
    为不受约束的
    T
    编译,并为碰巧有无参数构造函数的类型
    T
    工作,使代码能够轻松支持所有三种使用模式。如果
    Activator.CreateInstance
    有一个
    new
    约束,将其与没有约束的泛型类型参数一起使用将非常尴尬。

    因为
    CreateInstance()
    只使用普通的旧反射,不受任何约束。如果类型有一个默认构造函数,它将创建它。这是一种自我控制。你可以通过反射做几乎所有的事情(甚至破坏所有的OOP原则),但很多人不会欣赏它,因为它很脏。有很多方法可以编写笨拙的代码,让你消除编译时错误,并生成运行时错误。这只是一个例子。例如,
    dynamic
    允许您编写对不存在的函数的调用,因此不会出现编译器错误,而是会出现运行时异常。类似地,这里,
    CreateInstance
    允许您推迟到运行时才发现不存在无参数构造函数。问题“where T:new()约束的要点是什么”可以提炼为“类型约束的要点是什么?”它们都有相同的答案。