【问题标题】:Why is Activator.CreateInstance<T>() allowed without the new() generic type constraint?为什么在没有 new() 泛型类型约束的情况下允许 Activator.CreateInstance<T>()?
【发布时间】:2017-01-19 12:37:30
【问题描述】:

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

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>();
    }
}

这是为什么?

根据引用MSDN documentation 和this question 的https://stackoverflow.com/a/1649108/531971,泛型中的new T() 表达式实际上实现 使用Activator.CreateInstance&lt;T&gt;()。所以我不明白为什么调用new T() 需要以使用Activator.CreateInstance&lt;T&gt;() 时可以省略的方式约束泛型类型。

或者,反过来说:where T : new() 约束的意义是什么,如果通过直接使用完全相同的底层基础设施,在没有约束的通用方法中创建 T 的实例很容易?

【问题讨论】:

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

标签: c# .net generics activator type-constraints


【解决方案1】:

Activator 和 T() 在概念上有区别:

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

    • 旁注:请记住as MSDN says:

      一般来说,CreateInstance&lt;T&gt;() 泛型方法在应用程序代码中没有用处,因为在编译时必须知道类型。如果类型在编译时已知,则可以使用正常的实例化语法。

      因为通常当Type 在编译时已知时您会想要使用构造函数(CreateInstance&lt;T&gt;() uses RuntimeTypeHandle.CreateInstance 较慢[这也是Activator.CreateInstance&lt;T&gt; 本身不需要约束的原因] )。

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

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

不止于此; 在可能的情况下,您应该更喜欢编译时错误而不是 Exceptions。

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      1. 客户端代码提供了一个返回新T的函数。

      2. 客户端代码遵循一个默认函数,该函数使用其默认构造函数创建一个新的T。

      3. 客户端代码避免使用需要它创建T 的新实例的类的任何功能。

      模式#1 和#3 应该适用于任何T,而#2 应该只适用于具有无参数构造函数的类型。让Activator.CreateInstance&lt;T&gt;() 为不受约束的T 编译,并为恰好具有无参数构造函数的类型T 工作,使得代码支持所有三种使用模式变得容易。如果Activator.CreateInstance&lt;T&gt; 有new 约束,那么将它与没有的泛型类型参数一起使用会很尴尬。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-09
        • 1970-01-01
        • 2017-01-21
        • 1970-01-01
        • 1970-01-01
        • 2010-09-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多