我似乎记得读过一些关于结构通过C#在CLR中实现接口有什么不好的东西,但我似乎无法找到任何关于它的东西。这不好吗?这样做会产生意想不到的后果吗?
public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
答案 0 :(得分:157)
答案 1 :(得分:42)
这个问题有几件事情......
结构体可能实现了一个接口,但是存在关于转换,可变性和性能的问题。有关详情,请参阅此帖子:http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
通常,结构应该用于具有值类型语义的对象。通过在结构上实现接口,您可以遇到装箱问题,因为结构在结构和接口之间来回转换。作为装箱的结果,更改结构内部状态的操作可能无法正常运行。
答案 2 :(得分:8)
在某些情况下,结构体实现接口可能会有好处(如果它从来没有用过,那么.net的创建者会为它提供它是否值得怀疑)。如果结构实现了像IEquatable<T>
这样的只读接口,那么将结构存储在类型为IEquatable<T>
的存储位置(变量,参数,数组元素等)中将需要将其装箱(每个结构) type实际上定义了两种东西:一种行为为值类型的存储位置类型和一种表现为类类型的堆对象类型;第一种是可隐式转换为第二种 - “拳击” - 第二种可能通过显式转换转换为第一个 - “拆箱”)。但是,可以使用所谓的约束泛型来利用结构的接口实现而无需装箱。
例如,如果有方法CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
,则此类方法可以调用thing1.Compare(thing2)
而无需框{{1}}或thing1
。如果thing2
恰好是thing1
,则运行时将知道它何时生成Int32
的代码。因为它将知道托管方法的东西和作为参数传递的东西的确切类型,所以它不必包装任何一个。
实现接口的结构的最大问题是存储在接口类型CompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
或Object
的位置(而不是其自身类型的位置)的结构将表现为作为一个类对象。对于只读接口,这通常不是问题,但对于像ValueType
这样的变异接口,它可能会产生一些奇怪的语义。
例如,考虑以下代码:
IEnumerator<T>
标记语句#1将使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
读取第一个元素。该枚举器的状态将被复制到enumerator1
。标记语句#2将提升该副本以读取第二个元素,但不会影响enumerator2
。然后将该第二个枚举器的状态复制到enumerator1
,这将通过标记的语句#3进行处理。然后,由于enumerator3
和enumerator3
都是引用类型,因此 REFERENCE 到enumerator4
将被复制到enumerator3
,因此标记的语句将有效提前 enumerator4
和enumerator3
。
有些人试图假装值类型和引用类型都是enumerator4
,但事实并非如此。实值类型可转换为Object
,但不是它的实例。存储在该类型位置的Object
实例是值类型,其行为类型为值;将其复制到List<String>.Enumerator
类型的位置会将其转换为引用类型,将其作为引用类型。后者是一种IEnumerator<String>
,但前者不是。{/ p>
BTW,还有一些注释:(1)一般来说,可变类类应该有Object
方法测试引用相等,但是盒装结构没有合适的方法可以做到这一点; (2)尽管名称如此,Equals
是类类型,而不是值类型;从ValueType
派生的所有类型都是值类型,除了System.Enum
之外,ValueType
派生的所有类型都是值类型,但System.Enum
和ValueType
都是类类型。
答案 3 :(得分:3)
结构实现为值类型,类是引用类型。如果你有一个Foo类型的变量,并且你在其中存储了一个Fubar实例,它会将它“装箱”成一个引用类型,从而失去了首先使用结构的优势。
我看到使用结构而不是类的唯一原因是因为它将是值类型而不是引用类型,但结构不能从类继承。如果您有结构继承接口,并且传递接口,则会丢失结构的值类型性质。如果你需要接口,也可以让它成为一个类。
答案 4 :(得分:3)
(没有什么可以补充但是没有编辑能力,所以这里就是......)
完全安全。在结构上实现接口没有任何违法行为。但是你应该质疑你为什么要这样做。
然而获取对struct的接口引用将是BOX 。所以性能损失等等。
我现在能想到的唯一有效方案是illustrated in my post here。如果要修改存储在集合中的结构状态,则必须通过结构上公开的其他接口来执行此操作。
答案 5 :(得分:1)
我认为问题在于它导致装箱,因为结构是值类型所以会有轻微的性能损失。
此链接表明可能存在其他问题......
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
答案 6 :(得分:0)
实现接口的结构没有任何后果。例如,内置系统结构实现了IComparable
和IFormattable
等接口。
答案 7 :(得分:0)
值类型实现接口的理由很少。由于您不能对值类型进行子类化,因此您始终可以将其称为具体类型。
除非你有多个结构都实现相同的接口,否则它可能稍微有用,但是那时我建议使用一个类并正确地执行它。
当然,通过实现一个接口,你正在装结结构,所以它现在位于堆上,你将无法再通过它传递它......这真的强化了我的观点,你应该只是在这种情况下使用课程。
答案 8 :(得分:-6)
结构就像生活在堆栈中的类一样。我认为没有理由说他们应该“不安全”。