구조체가 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
일반적으로 구조체는 값 형식 의미가있는 개체에 사용해야합니다. 구조체에서 인터페이스를 구현하면 구조체와 인터페이스 사이에서 구조체가 앞뒤로 캐스팅되므로 권투 문제가 발생할 수 있습니다. 복싱으로 인해 구조체의 내부 상태를 변경하는 작업이 제대로 작동하지 않을 수 있습니다.
아무도이 답변을 명시 적으로 제공하지 않았으므로 다음을 추가합니다.
구조의 인터페이스를 구현하는 부정적인 결과는 없습니다.
구조체를 보유하는 데 사용되는 인터페이스 유형의 variable을 사용하면 해당 구조체의 상자 값이 사용됩니다. 구조체가 불변 (좋은 것)이라면 다음과 같은 경우가 아니면 최악의 성능 문제입니다.
이 두 가지가 모두 가능하지는 않지만 대신 다음 중 하나를 수행 할 가능성이 있습니다.
아마도 인터페이스를 구현하는 구조체에 대한 많은 합리적인 이유는 constraints 와 함께 generic 컨텍스트 내에서 사용될 수 있기 때문입니다. . 이 방식으로 변수를 사용하면 다음과 같이 변수가됩니다.
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의 인스턴스에 정의 된 인스턴스 메소드의 호출을 삽입해야하는 경우 constrained opcode를 사용할 수 있습니다.
ThisType이 값 유형이고 thisType이 메소드를 구현하는 경우 ptr은 thisType에 의한 메소드 구현을 위해 호출 메소드 명령에 대한 'this'포인터로 수정되지 않은 채 전달됩니다.
이것은 권투를 피하고 값 유형이 인터페이스를 구현하기 때문에 must 메소드를 구현하므로 권투가 발생하지 않습니다. 위의 예에서 Equals()
호출은 this에 상자없이 수행됩니다.1.
대부분의 구조체에는 비트 단위의 동일한 값이 동일한 것으로 간주되는 프리미티브와 같은 의미론이 있어야합니다.2. 런타임은 암시 적 Equals()
에서 이러한 동작을 제공하지만 느려질 수 있습니다. 또한이 암시 적 평등은 notIEquatable<T>
의 구현으로 노출되어 있으므로 명시 적으로 구현하지 않는 한 구조체를 사전의 키로 쉽게 사용할 수 없습니다. 따라서 많은 공공 구조체 유형이 IEquatable<T>
를 구현한다고 선언하는 것이 일반적입니다 (여기서 T
는 자체입니다). CLR BCL 내의 유형.
BCL의 모든 기본 요소는 최소한 다음을 구현합니다.
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(따라서 IEquatable
)또한 많은 사람들이 IFormattable
를 구현하고 있으며 DateTime, TimeSpan 및 Guid와 같은 많은 시스템 정의 값 유형은 이들 중 다수 또는 전부를 구현합니다. 복잡한 숫자 구조체 또는 고정 너비 텍스트 값과 같이 유사하게 광범위하게 유용한 유형을 구현하는 경우 이러한 공통 인터페이스를 올바르게 구현하면 구조체가 더 유용하고 사용 가능해집니다.
인터페이스가 mutability (예 : ICollection
)를 강력하게 암시하는 경우, 구조체를 변경 가능하게 만들었 음을 의미하는 나쁜 아이디어입니다. 원래 값이 아닌 상자 값에서 수정이 발생하는 위치에 이미 설명 된 오류) 또는 Add()
과 같은 메서드의 의미를 무시하거나 예외를 throw하여 사용자를 혼동합니다.
많은 인터페이스는 변경 가능성 (예 : IFormattable
)을 의미하지 않으며 특정 기능을 일관된 방식으로 노출하는 관용적 방법으로 사용됩니다. 종종 구조체의 사용자는 그러한 행동에 대한 권투 오버 헤드에 신경 쓰지 않을 것입니다.
불변의 값 유형에서 현명하게 수행되면 유용한 인터페이스를 구현하는 것이 좋습니다
1 : 컴파일러가 known 변수에 대해 가상 메소드를 호출 할 때 특정 구조체 유형이지만 가상 메소드를 호출해야하는 경우이를 사용할 수 있습니다. 예를 들면 다음과 같습니다.
List<int> l = new List<int>();
foreach(var x in l)
;//no-op
List에 의해 리턴 된 열거자는리스트를 열거 할 때 할당을 피하기위한 최적화 된 구조체입니다 (몇 가지 흥미로운 consequences ). 그러나 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 : 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 : leave.s IL_0035 IL_0026 : ldloca .s 02 IL_0028 : 제한됨. System.Collections.Generic.List.Enumerator IL_002E : callvirt System.IDisposable.Dispose IL_0033 : nop IL_0034 : 최종적으로
따라서 IDisposable을 구현해도 성능 문제가 발생하지 않으며 Dispose 메서드가 실제로 수행하는 경우 열거 자의 (유감스러운) 변경 가능한 측면이 보존됩니다!
2 : NaN 값이 같은 것으로 간주되지 않는 double 및 float은이 규칙에서 예외입니다.
어떤 경우에는 struct가 인터페이스를 구현하는 것이 좋을 수도 있습니다 (유용하지 않은 경우 .net의 작성자가 제공했을 가능성이 의심됩니다). 구조체가 IEquatable<T>
와 같은 읽기 전용 인터페이스를 구현하는 경우, 구조체를 IEquatable<T>
유형의 저장 위치 (변수, 매개 변수, 배열 요소 등)에 저장하려면 상자를 채워야합니다 ( 각 구조체 유형은 실제로 값 유형으로 작동하는 저장 위치 유형과 클래스 유형으로 작동하는 힙 객체 유형의 두 가지 종류를 정의합니다. 두 번째는 명시 적 캐스트 ( "unboxing")를 통해 첫 번째로 변환 될 수 있습니다. 그러나 제약이없는 제네릭이라고하는 것을 사용하여 복싱없이 인터페이스의 구조 구현을 활용할 수 있습니다.
예를 들어, CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>
메소드가있는 경우 이러한 메소드는 thing1
또는 thing2
상자없이 thing1.Compare(thing2)
을 호출 할 수 있습니다. thing1
가 예를 들어 Int32
인 경우 런타임은 CompareTwoThings<Int32>(Int32 thing1, Int32 thing2)
에 대한 코드를 생성 할 때이를 알게됩니다. 메서드를 호스팅하는 것과 매개 변수로 전달되는 것의 정확한 유형을 알 수 있으므로 둘 중 하나를 상자에 넣을 필요는 없습니다.
인터페이스를 구현하는 구조체의 가장 큰 문제는 인터페이스 유형, Object
또는 ValueType
(자체 유형의 위치와 반대)에 저장되는 구조체는 다음과 같이 작동한다는 것입니다. 클래스 객체. 읽기 전용 인터페이스의 경우 이것은 일반적으로 문제가되지 않지만 IEnumerator<T>
와 같은 변경 인터페이스의 경우 이상한 의미가있을 수 있습니다.
예를 들어 다음 코드를 고려하십시오.
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
표시된 명령문 # 1은 enumerator1
를 시작하여 첫 번째 요소를 읽습니다. 해당 열거 자의 상태가 enumerator2
에 복사됩니다. 표시된 명령문 # 2는 해당 사본을 진행하여 두 번째 요소를 읽지 만 enumerator1
에는 영향을 미치지 않습니다. 그런 다음 두 번째 열거 자의 상태가 enumerator3
로 복사되고 표시된 명령문 # 3으로 진행됩니다. 그런 다음 enumerator3
및 enumerator4
는 모두 참조 유형이므로 REFERENCE ~ enumerator3
enumerator4
에 복사되므로 표시된 명령문은 효과적으로 bothenumerator3
및 enumerator4
로 진행됩니다.
어떤 사람들은 값 유형과 참조 유형이 모두 Object
의 종류 인 것처럼 가장하려고하지만 실제로는 그렇지 않습니다. 실제 값 유형은 Object
로 변환 할 수 있지만 해당 인스턴스는 아닙니다. 해당 유형의 위치에 저장된 List<String>.Enumerator
인스턴스는 값 유형이며 값 유형으로 작동합니다. IEnumerator<String>
유형의 위치에 복사하면 참조 유형으로 변환되고 참조 유형으로 동작. 후자는 일종의 Object
이지만 전자는 그렇지 않습니다.
BTW, 몇 가지 참고 사항 : (1) 일반적으로 변경 가능한 클래스 유형은 Equals
메소드의 참조 동등성을 테스트해야하지만, 박스 구조에서 그렇게하는 적절한 방법은 없습니다. (2) 이름에도 불구하고 ValueType
는 값 유형이 아닌 클래스 유형입니다. System.Enum
에서 파생 된 모든 유형은 System.Enum
를 제외하고 ValueType
에서 파생 된 모든 유형과 마찬가지로 ValueType
및 System.Enum
는 수업 유형입니다.
(글쎄 추가 할 중요한 것은 없지만 아직 편집 능력은 없습니다.)
완전히 안전합니다. 구조체에 인터페이스를 구현하는 것은 불법이 아닙니다. 그러나 왜 그렇게하고 싶은지 질문해야합니다.
그러나 구조체에 대한 인터페이스 참조를 얻으면 BOX 됩니다. 성능 저하 등.
내가 지금 생각할 수있는 유일한 유효한 시나리오는 내 게시물에 설명되어 있음 입니다. 컬렉션에 저장된 구조체의 상태를 수정하려면 구조체에 노출 된 추가 인터페이스를 통해 수정해야합니다.
구조체는 값 형식으로 구현되며 클래스는 참조 형식입니다. Foo 유형의 변수가 있고 해당 인스턴스에 Fubar 인스턴스를 저장하면 참조 유형으로 "Boxed"되어 구조를 처음 사용할 때의 이점이 사라집니다.
클래스 대신 구조체를 사용하는 유일한 이유는 그것이 값 형식이고 참조 형식이 아니기 때문에 구조체가 클래스에서 상속 할 수 없기 때문입니다. 구조체가 인터페이스를 상속하고 인터페이스를 전달하면 구조체의 해당 값 유형 특성이 손실됩니다. 인터페이스가 필요한 경우 클래스로 만들 수도 있습니다.
문제는 구조체가 값 유형이므로 약간의 성능 저하가 있기 때문에 권투가 발생한다는 것입니다.
이 링크는 다른 문제가있을 수 있음을 제안합니다 ...
http://blogs.msdn.com/abhinaba/archive/2005/10/05/477238.aspx
인터페이스를 구현하는 구조체에는 아무런 영향이 없습니다. 예를 들어 내장 시스템 구조체는 IComparable
및 IFormattable
와 같은 인터페이스를 구현합니다.
값 유형이 인터페이스를 구현할 이유가 거의 없습니다. 값 유형을 서브 클래스화할 수 없으므로 항상 구체적인 유형으로 참조 할 수 있습니다.
물론 동일한 인터페이스를 구현하는 여러 구조체가 없다면 그다지 유용하지는 않지만 클래스를 사용하고 올바르게 수행하는 것이 좋습니다.
물론 인터페이스를 구현하면 구조체를 복싱하므로 이제는 힙에 앉아 더 이상 값으로 전달할 수 없습니다. 이러한 상황에서.