C# 结构实现接口安全吗?

C# 结构实现接口安全吗?,c#,interface,struct,C#,Interface,Struct,我似乎记得读过一些关于结构如何通过C#在CLR中实现接口的文章,但我似乎找不到任何相关内容。坏吗?这样做是否会产生意料之外的后果 public interface Foo { Bar GetBar(); } public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } } 结构就像存在于堆栈中的类。我看不出为什么它们应该是“不安全的”。对于实现接口的结构没有任何后果。例如,内置的系统结构实现了接口,如ICompara

我似乎记得读过一些关于结构如何通过C#在CLR中实现接口的文章,但我似乎找不到任何相关内容。坏吗?这样做是否会产生意料之外的后果

public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }

结构就像存在于堆栈中的类。我看不出为什么它们应该是“不安全的”。

对于实现接口的结构没有任何后果。例如,内置的系统结构实现了接口,如
IComparable
IFormattable

没有什么理由让值类型实现接口。由于不能对值类型进行子类化,因此始终可以将其称为其具体类型

当然,除非您有多个结构都实现了同一个接口,否则它可能会稍微有用,但在这一点上,我建议您使用一个类并正确地执行它


当然,通过实现接口,可以装箱结构,因此它现在位于堆上,您将无法再通过值传递它了……这确实加强了我的观点,即在这种情况下,您应该只使用类。

我认为问题在于,它会导致装箱,因为结构是值类型,所以会有轻微的性能损失

这个链接表明它可能还有其他问题


结构实现为值类型,类实现为引用类型。如果您有一个Foo类型的变量,并且您在其中存储了Fubar的实例,那么它将把它“装箱”到引用类型中,从而破坏了首先使用struct的优势


我看到使用结构而不是类的唯一原因是,它将是值类型而不是引用类型,但结构不能从类继承。如果让结构继承接口,并传递接口,则会丢失结构的值类型特性。如果您需要接口,不妨将其设置为一个类。

在这个问题中,有几件事正在进行

结构实现接口是可能的,但是在转换、易变性和性能方面存在一些问题。有关更多详细信息,请参阅本文:


通常,结构应该用于具有值类型语义的对象。通过在结构上实现接口,当结构在结构和接口之间来回转换时,您可能会遇到装箱问题。作为装箱的结果,更改结构内部状态的操作可能无法正常运行。

(我们没有要添加的主要内容,但还没有编辑能力,所以这里是…。
非常安全。在结构上实现接口并不违法。但是,您应该质疑为什么要这样做。

但是,获取对结构的接口引用会将其装箱。所以表现惩罚等等


我现在能想到的唯一有效的情况是。当您想要修改存储在集合中的结构的状态时,您必须通过结构上公开的附加接口进行修改。

由于没有其他人明确提供此答案,我将添加以下内容:

在结构上实现接口不会产生任何负面影响

用于保存结构的接口类型的任何变量都将导致使用该结构的装箱值。如果结构是不可变的(一件好事),那么这在最坏的情况下是一个性能问题,除非您:

  • 将结果对象用于锁定目的(无论如何都是一个非常糟糕的主意)
  • 使用引用相等语义,并期望它对来自同一结构的两个装箱值起作用
这两种情况都不太可能发生,相反,您可能正在执行以下操作之一:

仿制药 也许结构实现接口的许多合理原因是,它们可以在具有的通用上下文中使用。以这种方式使用时,变量如下所示:

class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
    private readonly T a;

    public bool Equals(Foo<T> other)
    {
         return this.a.Equals(other.a);
    }
}
列表返回的枚举数是一个结构,这是一种优化,用于在枚举列表时避免分配(有点有趣)。但是,foreach的语义指定,如果枚举器实现了
IDisposable
,则迭代完成后将调用
Dispose()
。显然,通过装箱调用实现这一点将消除枚举数作为结构的任何好处(事实上这会更糟)。更糟糕的是,如果dispose调用以某种方式修改枚举器的状态,那么这将发生在已装箱的实例上,并且在复杂的情况下可能会引入许多细微的错误。因此,在这种情况下发出的IL为:

IL_0001: newobj System.Collections.Generic.List..ctor IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: callvirt System.Collections.Generic.List.GetEnumerator IL_000E: stloc.2 IL_000F: br.s IL_0019 IL_0011: ldloca.s 02 IL_0013: call System.Collections.Generic.List.get_Current IL_0018: stloc.1 IL_0019: ldloca.s 02 IL_001B: call System.Collections.Generic.List.MoveNext IL_0020: stloc.3 IL_0021: ldloc.3 IL_0022: brtrue.s IL_0011 IL_0024: leave.s IL_0035 IL_0026: ldloca.s 02 IL_0028: constrained. System.Collections.Generic.List.Enumerator IL_002E: callvirt System.IDisposable.Dispose IL_0033: nop IL_0034: endfinally IL_0001:newobj系统.Collections.Generic.List..ctor IL_0006:stloc.0 IL_0007:没有 IL_0008:ldloc.0 IL_0009:callvirt System.Collections.Generic.List.GetEnumerator IL_000E:stloc.2 IL_000F:br.s IL_0019 IL_0011:ldloca.s 02 IL_0013:调用System.Collections.Generic.List.get_Current IL_0018:stloc.1 IL_0019:ldloca.s 02 IL_001B:调用System.Collections.Generic.List.MoveNext IL_0020:stloc.3 IL_0021:ldloc.3 IL_0022:brtrue.s IL_0011 IL_0024:离开。s IL_0035 IL_0026:ldloca.s 02 IL_0028:受约束。System.Collections.Generic.List.Enumerator IL_002E:callvirt System.IDisposable.Dispose IL_0033:没有 IL_0034:最终结束 因此,IDisposable的实现不会导致任何性能问题,如果Dispose方法实际执行任何操作,枚举器的(令人遗憾的)可变方面将被保留


2:double和float是此规则的例外,其中NaN值不相等。

在某些情况下,结构实现接口可能是好的(如果它从来没有用过,则.net的创建者可能会提供它)。如果一个结构实现了一个只读接口 IL_0001: newobj System.Collections.Generic.List..ctor IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: callvirt System.Collections.Generic.List.GetEnumerator IL_000E: stloc.2 IL_000F: br.s IL_0019 IL_0011: ldloca.s 02 IL_0013: call System.Collections.Generic.List.get_Current IL_0018: stloc.1 IL_0019: ldloca.s 02 IL_001B: call System.Collections.Generic.List.MoveNext IL_0020: stloc.3 IL_0021: ldloc.3 IL_0022: brtrue.s IL_0011 IL_0024: leave.s IL_0035 IL_0026: ldloca.s 02 IL_0028: constrained. System.Collections.Generic.List.Enumerator IL_002E: callvirt System.IDisposable.Dispose IL_0033: nop IL_0034: endfinally
List<String> myList = [list containing a bunch of strings]
var enumerator1 = myList.GetEnumerator();  // Struct of type List<String>.IEnumerator
enumerator1.MoveNext(); // 1
var enumerator2 = enumerator1;
enumerator2.MoveNext(); // 2
IEnumerator<string> enumerator3 = enumerator2;
enumerator3.MoveNext(); // 3
IEnumerator<string> enumerator4 = enumerator3;
enumerator4.MoveNext(); // 4