接口变量具有值类型或引用类型语义吗?

本文关键字:引用类型 语义 类型 变量 接口 | 更新日期: 2023-09-27 18:18:01

接口变量是否具有值类型或引用类型语义?

接口由类型实现,这些类型要么是值类型,要么是引用类型。显然,intstring都实现了IComparable, int是值类型,string是引用类型。但是这个呢:

IComparable x = 42;
IComparable y = "Hello, World!";

(我试图回答的问题可能被删除了,因为它问接口是存储在堆栈还是堆上,而且,正如我们都应该意识到的那样,从语义而不是实现的角度来考虑值类型和引用类型之间的差异更有建设性。有关讨论,请参阅Eric Lippert的stack是一个实现细节。

接口变量具有值类型或引用类型语义吗?

通常,根据现有的答案,它是一个引用类型,需要装箱;但也有例外(总是有例外吗?)在具有where约束的泛型方法中,可以是:

void Foo<T>(T obj) where T : ISomeInterface {
    obj.SomeMethod();
}

这是一个受约束的操作,即使是值类型,也不会对进行框化。这是通过constrained实现的。相反,JIT对引用类型执行虚拟调用,对值类型执行静态调用。没有拳击。

这是关于理解类型的装箱和拆箱。在你的例子中,int在赋值时被装箱,并且对该"盒子"或对象的引用被赋给x。值类型int被定义为实现IComparable的结构体。但是,一旦使用该接口引用引用值类型int时,它将被装箱并放在堆上。这就是它在实践中的运作方式。使用接口引用在定义上导致装箱这一事实使得该引用类型具有语义。

MSDN:装箱和拆箱

类型为IComparable的变量或字段是引用类型的变量或字段,与分配给该字段的值的类型无关。这意味着示例代码中的x被装箱。

一个简单的测试将证明这一点。测试是基于这样一个事实:您只能将一个值类型拆箱为其原始类型(以及该类型的可空版本):

    [TestMethod, ExpectedException(typeof(InvalidCastException))]
    public void BoxingTest()
    {
        IComparable i = 42;
        byte b = (byte)i;      //exception: not allowed to unbox an int to any type other than int
        Assert.AreEqual(42, b);
        Assert.Fail();
    }

编辑

在此之上,c#规范明确定义了引用类型,包括类类型、接口类型、数组类型和委托类型。

编辑2

Marc Gravell在他的回答中指出,带有接口约束的泛型类型是另一种情况。这个不会引起框

接口类型的变量将始终具有不可变语义、可变引用语义或"古怪"语义(与普通引用或值语义不同的东西)。如果variable1variable2都被声明为相同的接口类型,一个执行variable2 = variable1,另一个不再写入任何一个变量,那么variable1引用的实例将始终与variable2引用的实例无法区分(因为它们将是相同的实例)。

具有接口约束的泛型类型可能具有不可变语义、可变引用语义或"古怪"语义,但也可能具有可变值语义。如果没有将接口记录为具有可变值语义,这可能是危险的。不幸的是,没有办法将接口约束为具有不可变语义或可变值语义(这意味着在variable2 = variable1之后,不应该通过编写variable2来更改variable1,反之亦然)。可以在接口约束的同时添加一个"结构"约束,但是这会排除具有不可变语义的类,而不排除具有引用语义的结构。