在C#中使用接口时的值与引用类型

时间:2013-05-30 16:59:19

标签: c# class interface struct boxing

我想在C#中创建一个类似语义的类型。它是不可变的,内存占用少。但是,它主要通过它实现的接口访问。在这种情况下,必须将值类型装箱,这意味着必须将实际值从堆栈复制到堆中。因此,我想知道使用值类型(struct)而不是引用类型(类)是否有任何优势?

为了说明我的情况,我提供了以下带有实现IReferenceTypeImplementation的接口ValueTypeImplementation的示例:

interface I
{
    int GetSomeInt();
}

class ReferenceTypeImplementation : I
{
    public readonly int i;

    public ReferenceTypeImplementation (int i)
    {
        this.i = i;
    }

    public int GetSomeInt()
    {
        return i*2;
    }
}

struct ValueTypeImplementation : I
{
    public readonly int i;

    public ValueTypeImplementation (int i)
    {
        this.i = i;
    }

    public int GetSomeInt()
    {
        return i*2;
    }
}

我几乎只使用接口I之类的

来使用这些类型
I i1 = new ReferenceTypeImplementation(1);
I i2 = new ValueTypeImplementation(1);

使用ValueTypeImplementation优于ReferenceTypeImplementation是否有任何好处?

3 个答案:

答案 0 :(得分:10)

  

在ReferenceTypeImplementation上使用ValueTypeImplementation有什么优势吗?

如果你打算通过界面使用它,那就不行了。正如您所提到的,这将打包值类型,从而抵消任何潜在的性能提升。

此外,您的预期主要用途将通过界面这一事实表明您期望引用语义,这再次表明class会更多适用于这种情况。

答案 1 :(得分:2)

将结构转换为接口会导致装箱。调用隐式实现 结构上的成员不会导致装箱:

interface I { void Foo(); }
struct S : I { public void Foo() {} }

S s = new S();
s.Foo(); // No boxing.

I i = s; // Box occurs when casting to interface.
i.Foo();

在你的情况下,如果你可以使用隐式实现/调用,那么你最好使用struct,因为你正在避免;

(a)拳击

(b)没有额外的内存开销(适用于任何引用类型分配)。

答案 2 :(得分:0)

如果Foo类型实现了IBarIBar的实例可以通过两种方式使用Foo的成员:

  • 对实例的引用(对于类类型)或对包含实例副本的堆对象的引用(对于值类型)可以存储在IBar

  • 实例(对于值类型)或对它的引用(对于类类型)可以存储在泛型类型的变量(或字段,数组元素或其他存储位置)中,该类型受限于IBar

类类型或接口类型的存储位置将包含对堆对象的引用,或null。原始值类型的存储位置将保存表示其值的位集合。非原始值类型的存储位置将保存其所有公共和私有实例字段的连接内容。如果T是类类型或接口类型,则泛型类型T的存储位置将保存堆引用,如果T是原始值类型,则表示原始值的位集合,如果T是结构类型,则为T个字段的连接值;此确定基于T的实际类型,而不是基于其任何约束。

返回类型FooIBar,将Foo存储在IBar中将导致系统创建一个新的堆对象,该对象将保存所有连接的内容Foo的公共和私有字段,并在IBar中存储对该堆对象的引用。然后,该堆对象将像任何其他堆对象一样运行,而不是像值类型存储位置那样。有时候表达类似于值类型的东西,有时候像类类型一样,可能会令人困惑;在可能的情况下,最好避免使用这种混合语义。

在使用将遵循上述第二种模式的情况下,实现接口的结构的主要时间是值得的。例如,可能有一个方法:

// Return 1, 2, or 3 depending upon how many matching or different things there are
int CountUniqueThings<T>(T first, T second, T third) where T:IEquatable<T>

在这种情况下,如果要调用CountUniqueThings(4, 6, 6),系统可以直接在类型为IEquatable<System.Int32>.Equals(System.Int32)的传入参数上调用System.Int32方法。如果已经声明了该方法:

int CountUniqueThings(IEquatable<System.Int32> first, 
                      IEquatable<System.Int32> second,
                      IEquatable<System.Int32> third)

然后调用者必须创建一个新的堆对象来保存Int32值4,第二个保存值6,第三个也保持值6,然后必须传递引用到那些CountUniqueThings方法的对象。恶心。

堆对象的创建有一定的成本。如果创建一个值类型将允许一个人避免创建堆对象来保存绝大多数实例,这可能是一个巨大的胜利。但是,如果创建的每个实例都需要创建一个堆对象来保存副本(例如,为了分配给接口类型变量),则值类型优势将完全消失。如果实例平均需要创建多个堆对象来保存副本(每次将类型从堆类型转换为值类型并返回时,将需要一个新实例),使用值类型可能会少得多比简单地构造一个代码可以传递给引用的类类型实例更有效。