【问题标题】:Is "where T : class" not enforced in any way at compile time or run time?“where T : class”在编译时或运行时没有以任何方式强制执行吗?
【发布时间】:2012-09-21 19:57:25
【问题描述】:

在下面的代码中,我将一个结构传递给一个期望类的构造函数。为什么编译和运行没有错误(并产生所需的输出)?

class Program
{
    static void Main()
    {
        var entity = new Foo { Id = 3 };
        var t = new Test<IEntity>(entity); // why doesn't this fail?
        Console.WriteLine(t.Entity.Id.ToString());
        Console.ReadKey();
    }
}

public class Test<TEntity> where TEntity : class
{
    public TEntity Entity { get; set; }

    public Test(TEntity entity)
    {
        Entity = entity;
    }

    public void ClearEntity()
    {
        Entity = null;
    }
}

public struct Foo : IEntity
{
    public int Id { get; set; }
}

public interface IEntity
{
    int Id { get; set; }
}

如果我更改我的Main() 方法,使其包含对ClearEntity() 的调用,如下所示,它仍然不会产生错误。为什么?

static void Main()
{
    var entity = new Foo { Id = 3 };
    var t = new Test<IEntity>(entity);
    Console.WriteLine(t.Entity.Id.ToString());
    t.ClearEntity(); // why doesn't this fail?
    Console.ReadKey();
}

【问题讨论】:

  • FooIEntity,它将被装箱。装箱的实例是一个类的实例。
  • Entity被定义为TEntity类型,其中TEntity : class没有实现IEntity时,他如何编译t.Entity.Id

标签: c# class generics struct


【解决方案1】:

where TEntity : class 强制 TEntity 为引用类型,但诸如 IEntity 之类的接口是引用类型。

请看这里: http://msdn.microsoft.com/en-us/library/d5x73970(v=vs.80).aspx

其中 T:类 |类型参数必须是引用类型,包括任何类、接口、委托或数组类型

关于第二个问题,您可能认为t.ClearEntity() 会失败,因为它将 null 分配给类型为值类型的变量,但事实并非如此。 Entity 的编译时类型为引用类型IEntity,运行时类型(赋值后)为空类型。所以你永远不会有Foo 类型的变量,而是null 的值。

【讨论】:

  • +1。很好的解释,谢谢。这开辟了一些我没有意识到的很好的可能性。
  • 谢谢,我添加了更多关于为什么ClearEntity 不会失败的信息。我认为值得注意的是,Entity 中存储的运行时类型是通过将 null 分配给它来更改的。
【解决方案2】:

来自 C# 文档:

其中 T : 类

类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 (请参阅下面的注释。)

因为您是通过接口传递结构,所以它仍然被视为引用类型。

【讨论】:

  • 因为它被装箱它实际上是一个引用类型,它不像是一个没有被class限制过滤的值类型。
【解决方案3】:

在 .net 运行时中,每个不可为空的值类型都有一个关联的引用类型(通常称为“装箱值类型”),它派生自 System.ValueType。说Object Foo = 5; 实际上不会将Int32 存储到Foo 中;相反,它将创建一个与Int32 关联的引用类型的新实例,并存储对该实例的引用。泛型类型上的class 约束指定所讨论的类型必须是某种引用类型,但它本身并不排除该类型可用于将引用传递给装箱值类型实例的可能性。在泛型类型约束之外的大多数上下文中,接口类型被视为类类型。

重要的是要注意,不仅盒装值类型像引用类型一样存储;它们的行为类似于引用类型。例如,List&lt;string&gt;.Enumerator 是实现IEnumerator&lt;string&gt; 的值类型。如果一个有两个List&lt;string&gt;.Enumerator 类型的变量,将一个复制到另一个将复制枚举的状态,这样就会有两个独立的枚举数指向同一个列表。将其中一个变量复制到IEnumerator&lt;string&gt; 类型的变量将创建与List&lt;string.Enumerator 关联的装箱值类型的新实例,并在后一个变量中存储对该新对象的引用(这将是第三个独立枚举器)。但是,将该变量复制到另一个类型为IEnumerator&lt;string&gt; 的变量只会存储对现有对象的引用(因为IEnumerator&lt;string&gt; 是一个引用类型)。

C# 语言试图假装值类型派生自 Object,但在 .net 运行时的内部,它们实际上并非如此。相反,它们可转换为派生自System.ValueType(又派生自Object)的类型。后一种类型将满足类型约束,即使前一种不会。顺便说一句,尽管有它的名字,System.ValueType 实际上是一个类类型。

【讨论】:

    【解决方案4】:

    同样,我假设约束关键字 class 与类型声明关键字 class 表示相同的类,但事实并非如此。

    正如其他答案中所解释的,这里的术语 class 已超载,在我看来,这对于 C# 语言设计来说是一个可怕的决定。像referencetype 这样的东西会更有帮助。

    【讨论】:

    • 一个更根本的问题是值类型存储位置保存从Object 派生的东西的虚构。盒装值类型派生自 Object,但值类型存储位置不保存这些值。从根本上说,class 上的 T 约束意味着两件事,例如T foo;:(1)foo的默认值将是一个空引用; (2) foo.bar() 将调用bar() 方法对T 进行操作,foo 拥有对它的引用,而不给该方法对foo 本身的任何访问权限。盒装值类型与任何其他类类型一样,这些事情都是如此。
    猜你喜欢
    • 2020-02-20
    • 2015-12-26
    • 1970-01-01
    • 2012-10-12
    • 1970-01-01
    • 2010-11-14
    • 2016-01-24
    • 2011-05-28
    • 2011-06-06
    相关资源
    最近更新 更多