2012-01-06 2 views
0
public interface IVector<TScalar> { 
    void Add(ref IVector<TScalar> addend); 
} 

public struct Vector3f : IVector<float> { 
    public void Add(ref Vector3f addend); 
} 

컴파일러 답 :구조/클래스 대 인터페이스 대체 계약이 끊어 졌습니까?

+2

계약은 주어진 구현뿐만 아니라 *'IVector '을 받아 들여야한다는 계약입니다. C#은 메서드를 재정의하거나 인터페이스를 구현하는 방법으로 인수 분산을 지원하지 않습니다. –

+0

네, 방금 나 자신을 깨달았 어. 어쨌든. –

답변

2

"Vector3f는 인터페이스 멤버 IVector<float>.Add(ref IVector<float>)를 구현하지 않습니다"그러나이 수행 할 수 있습니다 그러나

public interface IVector<T, TScalar> 
    where T : IVector<T, TScalar> 
{ 
    void Add(ref T addend); 
} 

public struct Vector3f : IVector<Vector3f, float> 
{ 
    public void Add(ref Vector3f addend) 
    { 
    } 
} 

, 이것은 당신이 변경할 수 구조체를 가지고 의미 , 당신은해서는 안됩니다. 불변 사람을 사용하려면,이 인터페이스를 재정의해야 할 것 :

public interface IVector<T, TScalar> 
    where T : IVector<T, TScalar> 
{ 
    T Add(T addend); 
} 

public struct Vector3f : IVector<Vector3f, float> 
{ 
    public Vector3f Add(Vector3f addend) 
    { 
    } 
} 

편집 :

앤서니 Pegram가 지적 하듯이,이 패턴에 구멍이 있습니다. 그럼에도 불구하고 널리 사용됩니다. 예를 들어 : 자세한 내용은

struct Int32 : IComparable<Int32> ... 

, 여기에이 패턴에 대한 에릭 Lippert의 기사 Curiouser and curiouser에 대한 링크입니다.

+0

그러나 주어진 경우, 당신은 또한'EvilVector3f : IVector '를 정의 할 수 있습니다. 인터페이스 나 추상 메소드가 단지 작동하는 방식으로 구현된다는 아이디어를 깔끔하게 표현할 방법이 아직 없기 때문입니다 클래스를 구현하는 클래스의 유형으로 (* 내가 알고있는 것은 내가 바보라는 것을 인정한다.) –

+0

분명히 이것은 잘못된 접근법이며 구현과 인터페이스를 분리하지 않기 때문에 내가 원하는 것이 아니다. –

+0

@AnthonyPegram 좋은 지적. 이것은 C++에서 "기이하게 반복되는 템플릿 패턴"이라고 불리며 C#에서는 "흥미롭게 반복되는 제네릭 패턴"이라고 불리워 야하지만 Google은 해당 구문에 대해 1390 개의 히트만을 제공합니다. 주제에 대한 에릭 리 퍼트 (Eric Lippert)의 기사 링크를 추가했습니다. – phoog

1

다른 사람들은 자신의 클래스의 다른 항목과 상호 작용할 수있는 클래스를 명확하게 식별 할 수있는 방법이 없다는 점을 지적했습니다. 이러한 어려움은 Liskov Substitution Principle을 위반하는 사실로부터 어떤 측정에서 비롯된다. 클래스가 baseQ 유형의 두 객체를 받아들이고 하나가 서로 작동한다고 예상하면 LSP는 baseQ 객체 중 하나를 derivedQ로 바꿀 수 있어야한다고 지시합니다. 이는 baseQ가 derivedQ에서 작동해야 함을 의미하며 derivedQ는 baseQ에서 작동해야 함을 의미합니다. 보다 일반적으로 baseQ의 파생물은 baseQ의 다른 파생물에서 작동해야합니다. 인터페이스는 따라서 공분산 적이 지 않으며, 반항 적이 지 않으며, 불변 적이 지 않지만 비 범용 적입니다.

제네릭을 사용하고자하는 이유는 인터페이스가 boxing없이 structs에 적용되도록하는 것이기 때문에 phoog의 대답에 제공된 패턴이 좋습니다. 인터페이스의 목적은 변수 나 매개 변수 유형보다는 제약 조건으로 사용되어야하고 제약 조건을 사용하여 루틴이 부과 할 수있는 필수 조건이므로 형식 매개 변수에 반사적 제약을 가하는 것에 대해 일반적으로 걱정할 필요가 없습니다 (예 : VectorList<T,U> where T:IVector<T,U>).

덧붙여서 제약 조건으로 사용되는 인터페이스 유형의 동작은 인터페이스 유형의 변수 및 매개 변수의 동작과 매우 다릅니다. 모든 구조체 유형에는 ValueType에서 파생 된 다른 유형이 있습니다. 이 후자의 유형은 가치 의미보다는 참조 의미를 나타낼 것이다. 값 유형의 변수 또는 매개 변수가 루틴에 전달되거나 클래스 유형이 필요한 변수에 저장되면 시스템은 내용을 ValueType에서 파생 된 새 클래스 객체로 복사합니다. 문제의 구조체가 불변 인 경우, 그러한 모든 사본은 항상 원본과 서로 동일한 내용을 보유하게되므로 원본과 일반적으로 의미 상 동등한 것으로 간주 될 수 있습니다. 그러나 문제의 구조체가 변경 가능하면 그러한 복사 작업은 예상되는 것과 매우 다른 의미를 산출 할 수 있습니다. 인터페이스 메소드가 구조체를 돌연변이시키는 것이 유용 할 수있는 경우가 있지만 이러한 인터페이스는 극도로주의해서 사용해야합니다.

예를 들어, List<T>.Enumerator의 동작을 고려하면 IEnumerator<T>을 구현합니다. List<T>.Enumerator 유형의 변수 하나를 같은 유형의 다른 변수에 복사하면 목록 위치의 "스냅 샷"이 생성됩니다. 한 변수에서 MoveNext를 호출해도 다른 변수에는 영향을주지 않습니다.이러한 변수를 Object, IEnumerator<T> 또는 IEnumerator<T>에서 파생 된 인터페이스 중 하나에 복사하면 모양이 변경되고 원래 변수 나 새 변수에서 MoveNext를 호출해도 다른 변수는 영향을받지 않습니다. 반면에 Object 유형, IEnumerator<T> 유형 또는 IEnumerator<T>에서 파생 된 인터페이스 중 하나의 변수를 동일한 유형 또는 다른 유형 중 하나 인 다른 유형으로 복사하는 경우 스냅 샷을 찍지 않고 이전 버전의 참조를 복사하면됩니다. 생성 된 스냅 샷.

변수의 모든 복사본을 의미 상 동등하게 사용하는 것이 유용한 경우가 있습니다. 의미 론적으로 떼어 놓는 것이 유용 할 수있는 다른 경우가 있습니다. 불행히도, 조심하지 않으면, "의미 론적으로 혼란 스럽다"라고만 기술 할 수있는 이상한 의미의 의미로 끝날 수 있습니다.

+0

+1입니다. – phoog

관련 문제