C# 结构体实现接口是否安全?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/63671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it safe for structs to implement interfaces?
提问by
I seem to remember reading something about how it is bad for structs to implement interfaces in CLR via C#, but I can't seem to find anything about it. Is it bad? Are there unintended consequences of doing so?
我似乎记得读过一些关于结构如何通过 C# 在 CLR 中实现接口的坏处,但我似乎找不到任何关于它的信息。不好吗?这样做会产生意想不到的后果吗?
public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
采纳答案by Scott Dorman
There are several things going on in this question...
在这个问题中有几件事发生了......
It is possible for a struct to implement an interface, but there are concerns that come about with casting, mutability, and performance. See this post for more details: http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
结构体可以实现接口,但在转换、可变性和性能方面存在问题。有关更多详细信息,请参阅此帖子:http: //blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
In general, structs should be used for objects that have value-type semantics. By implementing an interface on a struct you can run into boxing concerns as the struct is cast back and forth between the struct and the interface. As a result of the boxing, operations that change the internal state of the struct may not behave properly.
一般来说,结构应该用于具有值类型语义的对象。通过在结构上实现接口,您可能会遇到装箱问题,因为结构在结构和接口之间来回转换。由于装箱,更改结构内部状态的操作可能无法正常运行。
回答by Sklivvz
Structs are just like classes that live in the stack. I see no reason why they should be "unsafe".
结构就像存在于堆栈中的类。我看不出他们应该“不安全”的理由。
回答by Joseph Daigle
There are no consequences to a struct implementing an interface. For example the built-in system structs implement interfaces like IComparable
and IFormattable
.
结构体实现接口没有任何后果。例如,内置的系统结构实现了像IComparable
和这样的接口IFormattable
。
回答by FlySwat
There is very little reason for a value type to implement an interface. Since you cannot subclass a value type, you can always refer to it as its concrete type.
值类型几乎没有理由实现接口。由于您不能对值类型进行子类化,因此您始终可以将其称为具体类型。
Unless of course, you have multiple structs all implementing the same interface, it might be marginally useful then, but at that point I'd recommend using a class and doing it right.
当然,除非你有多个结构都实现了相同的接口,否则它可能有点用处,但在这一点上,我建议使用一个类并正确地做。
Of course, by implementing an interface, you are boxing the struct, so it now sits on the heap, and you won't be able to pass it by value anymore...This really reinforces my opinion that you should just use a class in this situation.
当然,通过实现一个接口,你正在装箱结构,所以它现在位于堆上,你将无法再通过值传递它......这确实加强了我的观点,你应该只使用一个类在这种情况下。
回答by Simon Keep
I think the problem is that it causes boxing because structs are value types so there is a slight performance penalty.
我认为问题在于它会导致装箱,因为结构是值类型,因此性能会略有下降。
This link suggests there might be other issues with it...
此链接表明它可能存在其他问题...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
回答by dotnetengineer
Structs are implemented as value types and classes are reference types. If you have a variable of type Foo, and you store an instance of Fubar in it, it will "Box it" up into a reference type, thus defeating the advantage of using a struct in the first place.
结构被实现为值类型,而类是引用类型。如果您有一个 Foo 类型的变量,并且您在其中存储了 Fubar 的一个实例,它将“装箱”到一个引用类型中,从而首先破坏了使用结构的优势。
The only reason I see to use a struct instead of a class is because it will be a value type and not a reference type, but the struct can't inherit from a class. If you have the struct inherit an interface, and you pass around interfaces, you lose that value type nature of the struct. Might as well just make it a class if you need interfaces.
我看到使用结构而不是类的唯一原因是因为它将是值类型而不是引用类型,但结构不能从类继承。如果你让结构继承了一个接口,并且你传递了接口,你就失去了结构的值类型特性。如果您需要接口,不妨将其设为一个类。
回答by Gishu
(Well got nothing major to add but don't have editing prowess yet so here goes..)
Perfectly Safe. Nothing illegal with implementing interfaces on structs. However you should question why you'd want to do it.
(好吧,没有什么要添加的,但还没有编辑能力,所以这里是......)非常
安全。在结构上实现接口没有任何违法行为。但是,您应该质疑为什么要这样做。
However obtaining an interface reference to a struct will BOXit. So performance penalty and so on.
但是,获取对结构的接口引用会将其装箱。所以性能惩罚等等。
The only valid scenario which I can think of right now is illustrated in my post here. When you want to modify a struct's state stored in a collection, you'd have to do it via an additional interface exposed on the struct.
我现在能想到的唯一有效场景在我的帖子中说明。当您想要修改存储在集合中的结构状态时,您必须通过在结构上公开的附加接口来完成。
回答by ShuggyCoUk
Since no one else explicitly provided this answer I will add the following:
由于没有其他人明确提供此答案,我将添加以下内容:
Implementingan interface on a struct has no negative consequences whatsoever.
在结构上实现接口没有任何负面影响。
Any variableof the interface type used to hold a struct will result in a boxed value of that struct being used. If the struct is immutable (a good thing) then this is at worst a performance issue unless you are:
用于保存结构的接口类型的任何变量都将导致使用该结构的装箱值。如果结构是不可变的(一件好事),那么这在最坏的情况下是一个性能问题,除非您是:
- using the resulting object for locking purposes (an immensely bad idea any way)
- using reference equality semantics and expecting it to work for two boxed values from the same struct.
- 将结果对象用于锁定目的(无论如何都是一个非常糟糕的主意)
- 使用引用相等语义并期望它适用于来自同一结构的两个装箱值。
Both of these would be unlikely, instead you are likely to be doing one of the following:
这两种情况都不太可能,相反,您可能会执行以下操作之一:
Generics
泛型
Perhaps many reasonable reasons for structs implementing interfaces is so that they can be used within a genericcontext with constraints. When used in this fashion the variable like so:
也许结构实现接口的许多合理原因是它们可以在具有约束的通用上下文中使用。当以这种方式使用时,变量如下:
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);
}
}
- Enable the use of the struct as a type parameter
- so long as no other constraint like
new()
orclass
is used.
- so long as no other constraint like
- Allow the avoidance of boxing on structs used in this way.
- 允许使用结构体作为类型参数
- 只要不使用其他约束,例如
new()
或class
。
- 只要不使用其他约束,例如
- 允许避免对以这种方式使用的结构进行装箱。
Then this.a is NOT an interface reference thus it does not cause a box of whatever is placed into it. Further when the c# compiler compiles the generic classes and needs to insert invocations of the instance methods defined on instances of the Type parameter T it can use the constrainedopcode:
那么 this.a 不是接口引用,因此它不会导致放入任何东西的盒子。此外,当 c# 编译器编译泛型类并需要插入在 Type 参数 T 的实例上定义的实例方法的调用时,它可以使用受约束的操作码:
If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
如果 thisType 是一个值类型并且 thisType 实现了方法,那么 ptr 将未经修改地作为“this”指针传递给调用方法指令,以实现 thisType 的方法。
This avoids the boxing and since the value type is implementing the interface is mustimplement the method, thus no boxing will occur. In the above example the Equals()
invocation is done with no box on this.a1.
这避免了装箱,并且由于值类型正在实现接口,因此必须实现该方法,因此不会发生装箱。在上面的例子中,Equals()
调用是在 this.a 1上没有框的情况下完成的。
Low friction APIs
低摩擦 API
Most structs should have primitive-like semantics where bitwise identical values are considered equal2. The runtime will supply such behaviour in the implicit Equals()
but this can be slow. Also this implicit equality is notexposed as an implementation of IEquatable<T>
and thus prevents structs being used easily as keys for Dictionaries unless they explicitly implement it themselves. It is therefore common for many public struct types to declare that they implement IEquatable<T>
(where T
is them self) to make this easier and better performing as well as consistent with the behaviour of many existing value types within the CLR BCL.
大多数结构应该具有类似原始的语义,其中按位相同的值被认为等于2。运行时将在隐式中提供此类行为,Equals()
但这可能会很慢。此外,这种隐式相等不会作为 的实现公开IEquatable<T>
,因此防止结构被轻松用作字典的键,除非它们自己明确实现它。因此,许多公共结构类型声明它们实现IEquatable<T>
(T
它们自己在哪里)是很常见的,以使其更容易和更好地执行,并与 CLR BCL 中许多现有值类型的行为保持一致。
All the primitives in the BCL implement at a minimum:
BCL 中的所有原语至少实现:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(And thusIEquatable
)
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(因此IEquatable
)
Many also implement IFormattable
, further many of the System defined value types like DateTime, TimeSpan and Guid implement many or all of these as well. If you are implementing a similarly 'widely useful' type like a complex number struct or some fixed width textual values then implementing many of these common interfaces (correctly) will make your struct more useful and usable.
许多还实现了IFormattable
,另外许多系统定义的值类型,如 DateTime、TimeSpan 和 Guid 也实现了许多或所有这些。如果您正在实现类似“广泛有用”的类型,如复数结构或一些固定宽度的文本值,那么(正确)实现许多这些通用接口将使您的结构更有用和可用。
Exclusions
排除
Obviously if the interface strongly implies mutability(such as ICollection
) then implementing it is a bad idea as it would mean tat you either made the struct mutable (leading to the sorts of errors described already where the modifications occur on the boxed value rather than the original) or you confuse users by ignoring the implications of the methods like Add()
or throwing exceptions.
显然,如果接口强烈暗示可变性(例如ICollection
),那么实现它是一个坏主意,因为这意味着您要么使结构可变(导致已经描述的各种错误,其中修改发生在装箱值而不是原始值上) 或者您通过忽略诸如Add()
或抛出异常之类的方法的含义来混淆用户。
Many interfaces do NOT imply mutability (such as IFormattable
) and serve as the idiomatic way to expose certain functionality in a consistent fashion. Often the user of the struct will not care about any boxing overhead for such behaviour.
许多接口并不意味着可变性(例如IFormattable
),而是作为以一致方式公开某些功能的惯用方式。通常,结构的用户不会关心此类行为的任何装箱开销。
Summary
概括
When done sensibly, on immutable value types, implementation of useful interfaces is a good idea
如果明智地完成,在不可变值类型上,实现有用的接口是一个好主意
Notes:
笔记:
1: Note that the compiler may use this when invoking virtual methods on variables which are knownto be of a specific struct type but in which it is required to invoke a virtual method. For example:
1:请注意,编译器可能会在对已知为特定结构类型但需要调用虚拟方法的变量调用虚拟方法时使用它。例如:
List<int> l = new List<int>();
foreach(var x in l)
;//no-op
The enumerator returned by the List is a struct, an optimization to avoid an allocation when enumerating the list (With some interesting consequences). However the semantics of foreach specify that if the enumerator implements IDisposable
then Dispose()
will be called once the iteration is completed. Obviously having this occur through a boxed call would eliminate any benefit of the enumerator being a struct (in fact it would be worse). Worse, if dispose call modifies the state of the enumerator in some way then this would happen on the boxed instance and many subtle bugs might be introduced in complex cases. Therefore the IL emitted in this sort of situation is:
List 返回的枚举器是一个结构体,这是一种在枚举列表时避免分配的优化(有一些有趣的结果)。然而,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
Thus the implementation of IDisposable does not cause any performance issues and the (regrettable) mutable aspect of the enumerator is preserved should the Dispose method actually do anything!
因此,IDisposable 的实现不会导致任何性能问题,并且如果 Dispose 方法实际执行任何操作,则保留枚举器的(令人遗憾的)可变方面!
2: double and float are exceptions to this rule where NaN values are not considered equal.
2:double 和 float 是此规则的例外,其中 NaN 值不被视为相等。
回答by supercat
In some cases it may be good for a struct to implement an interface (if it was never useful, it's doubtful the creators of .net would have provided for it). If a struct implements a read-only interface like IEquatable<T>
, storing the struct in a storage location (variable, parameter, array element, etc.) of type IEquatable<T>
will require that it be boxed (each struct type actually defines two kinds of things: a storage location type which behaves as a value type and a heap-object type which behaves as a class type; the first is implicitly convertible to the second--"boxing"--and the second may be converted to the first via explicit cast--"unboxing"). It is possible to exploit a structure's implementation of an interface without boxing, however, using what are called constrained generics.
在某些情况下,结构体实现接口可能是好的(如果它从来没有用过,那么 .net 的创建者是否会提供它是值得怀疑的)。如果一个结构体实现了一个只读接口,例如IEquatable<T>
,将结构体存储在类型的存储位置(变量、参数、数组元素等)IEquatable<T>
中将需要对其进行装箱(每个结构体类型实际上定义了两种东西:一个存储位置类型表现为值类型,堆对象类型表现为类类型;第一个可以隐式转换为第二个--“装箱”--第二个可以通过显式转换转换为第一个-- “开箱”)。可以在不装箱的情况下利用接口的结构实现,但是,使用所谓的约束泛型。
For example, if one had a method CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
, such a method could call thing1.Compare(thing2)
without having to box thing1
or thing2
. If thing1
happens to be, e.g., an Int32
, the run-time will know that when it generates the code for CompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
. Since it will know the exact type of both the thing hosting the method and the thing that's being passed as a parameter, it won't have to box either of them.
例如,如果有一个方法CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
,这样的方法可以调用thing1.Compare(thing2)
而无需框thing1
或thing2
。如果thing1
碰巧是,例如,Int32
,运行时将知道它何时生成 的代码CompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
。由于它将知道托管方法的事物和作为参数传递的事物的确切类型,因此不必将它们中的任何一个装箱。
The biggest problem with structs that implement interfaces is that a struct which gets stored in a location of interface type, Object
, or ValueType
(as opposed to a location of its own type) will behave as a class object. For read-only interfaces this is not generally a problem, but for a mutating interface like IEnumerator<T>
it can yield some strange semantics.
实现接口的结构的最大问题是,一个结构存储在接口类型, Object
, or ValueType
(而不是它自己类型的位置)的位置将表现为类对象。对于只读接口,这通常不是问题,但对于像IEnumerator<T>
它这样的可变接口可能会产生一些奇怪的语义。
Consider, for example, the following code:
例如,考虑以下代码:
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
Marked statement #1 will prime enumerator1
to read the first element. The state of that enumerator will be copied to enumerator2
. Marked statement #2 will advance that copy to read the second element, but will not affect enumerator1
. The state of that second enumerator will then be copied to enumerator3
, which will be advanced by marked statement #3. Then, because enumerator3
and enumerator4
are both reference types, a REFERENCEto enumerator3
will then be copied to enumerator4
, so marked statement will effectively advance bothenumerator3
and enumerator4
.
标记语句 #1 将enumerator1
准备读取第一个元素。该枚举器的状态将被复制到enumerator2
. 标记的语句 #2 将推进该副本以读取第二个元素,但不会影响enumerator1
. 然后,第二个枚举器的状态将被复制到enumerator3
,这将由标记的语句 #3 推进。然后,因为enumerator3
和enumerator4
都是引用类型,一个参考,以enumerator3
将被复制到enumerator4
,如此显着的语句将有效地促进双方enumerator3
和enumerator4
。
Some people try to pretend that value types and reference types are both kinds of Object
, but that's not really true. Real value types are convertible to Object
, but are not instances of it. An instance of List<String>.Enumerator
which is stored in a location of that type is a value-type and behaves as a value type; copying it to a location of type IEnumerator<String>
will convert it to a reference type, and it will behave as a reference type. The latter is a kind of Object
, but the former is not.
有些人试图假装值类型和引用类型都是Object
,但事实并非如此。实值类型可转换为Object
,但不是它的实例。List<String>.Enumerator
存储在该类型位置的实例是值类型并且表现为值类型;将其复制到 type 的位置IEnumerator<String>
会将其转换为引用类型,并且它将表现为引用类型。后者是一种Object
,而前者不是。
BTW, a couple more notes: (1) In general, mutable class types should have their Equals
methods test reference equality, but there is no decent way for a boxed struct to do so; (2) despite its name, ValueType
is a class type, not a value type; all types derived from System.Enum
are value types, as are all types which derive from ValueType
with the exception of System.Enum
, but both ValueType
and System.Enum
are class types.
顺便说一句,还有一些注意事项:(1)通常,可变类类型应该让它们的Equals
方法测试引用相等性,但是对于装箱结构来说,没有合适的方法来这样做;(2) 尽管它的名字,ValueType
是一个类类型,而不是一个值类型;衍生自所有类型System.Enum
是值类型,因为是从派生的所有类型ValueType
的除外System.Enum
,但两者ValueType
和System.Enum
是类的类型。