구조체가 인터페이스를 구현하는 것이 안전합니까?
구조체가 C #을 통해 CLR에서 인터페이스를 구현하는 것이 얼마나 나쁜지에 대해 읽은 것을 기억하는 것 같지만 그것에 대해 아무것도 찾을 수없는 것 같습니다. 나쁜가요? 그렇게하면 의도하지 않은 결과가 있습니까?
public interface Foo { Bar GetBar(); }
public struct Fubar : Foo { public Bar GetBar() { return new Bar(); } }
이 질문에는 몇 가지 일이 있습니다 ...
구조체가 인터페이스를 구현하는 것이 가능하지만 캐스팅, 변경 가능성 및 성능과 관련된 문제가 있습니다. 자세한 내용은이 게시물을 참조하십시오. http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
일반적으로 구조체는 값 유형 의미를 가진 객체에 사용되어야합니다. 구조체에 인터페이스를 구현하면 구조체가 구조체와 인터페이스 사이에서 앞뒤로 캐스트되므로 권투 문제가 발생할 수 있습니다. 박싱의 결과로 구조체의 내부 상태를 변경하는 작업이 제대로 작동하지 않을 수 있습니다.
다른 사람이이 답변을 명시 적으로 제공하지 않았으므로 다음을 추가합니다.
구현 구조체의 인터페이스 것은 어떠한 부정의 결과가 없습니다.
구조체를 보유하는 데 사용되는 인터페이스 유형의 모든 변수 는 해당 구조체의 boxed 값이 사용됩니다. 구조체가 변경 불가능한 경우 (좋은 점) 다음과 같은 경우가 아니면 최악의 성능 문제입니다.
- 잠금 목적으로 결과 객체 사용 (어쨌든 매우 나쁜 생각)
- 참조 같음 의미 체계를 사용하고 동일한 구조체의 두 개의 박스형 값에 대해 작동 할 것으로 예상합니다.
둘 다 가능성이 낮고 대신 다음 중 하나를 수행 할 가능성이 있습니다.
제네릭
인터페이스를 구현하는 구조체에 대한 많은 합리적인 이유는 제약 조건이 있는 일반 컨텍스트 내에서 사용할 수 있기 때문일 것 입니다. 이 방식으로 사용할 때 변수는 다음과 같습니다.
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);
}
}
- 구조체를 유형 매개 변수로 사용하도록 설정
new()
또는 같은 다른 제약 조건class
이 사용 되지 않는 한 .
- 이러한 방식으로 사용되는 구조체에서 권투를 피할 수 있습니다.
그러면 this.a는 인터페이스 참조가 아니므로 그 안에 어떤 상자가 배치되지 않습니다. 또한 C # 컴파일러가 제네릭 클래스를 컴파일하고 Type 매개 변수 T의 인스턴스에 정의 된 인스턴스 메서드의 호출을 삽입해야하는 경우 제한된 opcode를 사용할 수 있습니다 .
thisType이 값 유형이고 thisType이 메소드를 구현하는 경우 ptr은 thisType에 의한 메소드 구현을 위해 'this'포인터로 수정되지 않은 상태로 호출 메소드 명령에 전달됩니다.
이것은 boxing을 피하고 값 유형이 구현하기 때문에 인터페이스는 메소드 를 구현 해야 하므로 boxing이 발생하지 않습니다. 위의 예에서 Equals()
호출은 this.a 1에 상자없이 수행됩니다 .
저 마찰 API
대부분의 구조체는 비트 단위로 동일한 값이 2 로 간주되는 원시적 의미를 가져야 합니다. 런타임은 이러한 동작을 암시 적으로 제공 Equals()
하지만 속도가 느릴 수 있습니다. 또한이 암시 적 동등성은 의 구현으로 노출 되지 않으므로IEquatable<T>
명시 적으로 구현하지 않는 한 사전의 키로 구조체가 쉽게 사용되는 것을 방지합니다. 따라서 많은 공용 구조체 형식 이 CLR BCL 내의 많은 기존 값 형식의 동작과 일치 할뿐만 아니라보다 쉽고 더 나은 성능을 제공하기 위해 구현 IEquatable<T>
( T
자체 위치 ) 을 선언하는 것이 일반적 입니다.
BCL의 모든 기본 요소는 최소한 다음을 구현합니다.
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.
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.
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.
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 known to be of a specific struct type but in which it is required to invoke a virtual method. For example:
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:
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!
2: double and float are exceptions to this rule where NaN values are not considered equal.
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.
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.
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.
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 REFERENCE to enumerator3
will then be copied to enumerator4
, so marked statement will effectively advance both enumerator3
and 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.
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.
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.
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.
(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 BOX it. 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.
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
There are no consequences to a struct implementing an interface. For example the built-in system structs implement interfaces like IComparable
and IFormattable
.
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.
Structs are just like classes that live in the stack. I see no reason why they should be "unsafe".
참고URL : https://stackoverflow.com/questions/63671/is-it-safe-for-structs-to-implement-interfaces
'programing tip' 카테고리의 다른 글
Javascript ES6 / ES5 배열에서 찾기 및 변경 (0) | 2020.09.18 |
---|---|
CentOS 7.2에서 yum으로 gcc 5.3을 설치하는 방법은 무엇입니까? (0) | 2020.09.18 |
Linux 프로세스 상태 (0) | 2020.09.18 |
GCC 및 Clang 파서는 실제로 손으로 작성됩니까? (0) | 2020.09.18 |
require (vendor / autoload.php) : 스트림을 열지 못했습니다. (0) | 2020.09.18 |