문제

클라이언트 구현을 생성하기 위해 모두 함께 재정의해야 하는 상호 관련된 추상 클래스 그룹이 있기 때문에 상속 문제가 있습니다.이상적으로는 다음과 같은 작업을 수행하고 싶습니다.

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }

이렇게 하면 Dog 클래스를 사용하는 사람은 누구나 자동으로 DogLegs를 얻을 수 있고 Animal 클래스를 사용하는 사람은 누구나 Legs를 얻을 수 있습니다.문제는 재정의된 함수가 기본 클래스와 동일한 유형을 가져야 하므로 컴파일되지 않는다는 것입니다.DogLeg가 암시적으로 Leg로 캐스팅 가능하기 때문에 왜 그렇게 하면 안되는지 모르겠습니다.이 문제를 해결할 수 있는 방법이 많이 있다는 것을 알고 있지만 이것이 C#에서 가능/구현되지 않는 이유가 더 궁금합니다.

편집하다:실제로 코드에서 함수 대신 속성을 사용하고 있기 때문에 이를 다소 수정했습니다.

편집하다:대답은 해당 상황에만 적용되기 때문에 다시 함수로 변경했습니다(속성 설정 함수의 값 매개 변수에 대한 공분산). 해서는 안 된다 일하다).변동이 있어서 죄송합니다!나는 그것이 많은 답변이 관련성이 없어 보인다는 것을 알고 있습니다.

도움이 되었습니까?

해결책

짧은 대답은 GetLeg의 반환 유형이 변하지 않는다는 것입니다.긴 답변은 여기에서 찾을 수 있습니다. 공분산과 반공분산

상속은 일반적으로 대부분의 개발자가 도구 상자에서 꺼내는 첫 번째 추상화 도구이지만 대신 구성을 사용하는 것이 거의 항상 가능하다는 점을 덧붙이고 싶습니다.구성은 API 개발자에게는 약간 더 많은 작업이지만 소비자에게는 API를 더 유용하게 만듭니다.

다른 팁

분명히, 부러진 Dogleg에서 작동하는 경우 캐스트가 필요합니다.

Dog는 반환 유형으로 DogLeg가 아닌 Leg를 반환해야 합니다.실제 클래스는 DogLeg일 수 있지만 요점은 Dog 사용자가 DogLeg에 대해 알 필요 없이 Legs에 대해서만 알 필요가 있도록 분리하는 것입니다.

변화:

class Dog : Animal
{
  public override DogLeg GetLeg() {...}
}

에게:

class Dog : Animal
{
  public override Leg GetLeg() {...}
}

하지 마십시오:

 if(a instanceof Dog){
       DogLeg dl = (DogLeg)a.GetLeg();

추상 유형으로 프로그래밍하는 목적을 상실합니다.

DogLeg를 숨기는 이유는 추상 클래스의 GetLeg 함수가 Abstract Leg를 반환하기 때문입니다.GetLeg를 재정의하는 경우 Leg를 반환해야 합니다.이것이 추상 클래스에 메소드를 갖는 요점입니다.해당 방법을 자식에게 전파합니다.Dog 사용자에게 DogLeg에 대해 알리려면 GetDogLeg라는 메서드를 만들고 DogLeg를 반환합니다.

질문자가 원하는 대로 할 수 있다면 Animal의 모든 사용자는 모든 동물에 대해 알아야 합니다.

재정의된 메서드의 시그니처가 재정의된 메서드의 반환 유형의 하위 유형인 반환 유형을 갖도록 하는 것은 완전히 유효한 요구입니다().결국, 런타임 유형과 호환됩니다.

그러나 C#은 재정의된 메서드에서 아직 "공변 반환 유형"을 지원하지 않습니다(C++ [1998] 및 Java [2004]와 달리).

Eric Lippert가 언급했듯이 가까운 미래를 위해 노력하고 성과를 내야 합니다. 그의 블로그[2008년 6월 19일]:

이러한 종류의 분산을 "반환 유형 공분산"이라고 합니다.

우리는 C#에서 그런 종류의 변형을 구현할 계획이 없습니다.

abstract class Animal
{
  public virtual Leg GetLeg ()
}

abstract class Leg { }

class Dog : Animal
{
  public override Leg GetLeg () { return new DogLeg(); }
}

class DogLeg : Leg { void Hump(); }

이렇게 하면 클라이언트에서 추상화를 활용할 수 있습니다.

Leg myleg = myDog.GetLeg();

그런 다음 필요한 경우 캐스팅할 수 있습니다.

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }

완전히 인위적이지만 요점은 다음과 같이 할 수 있다는 것입니다.

foreach (Animal a in animals)
{
   a.GetLeg().SomeMethodThatIsOnAllLegs();
}

Doglegs에 특별한 Hump 방법을 사용할 수 있는 능력은 여전히 ​​유지됩니다.

제네릭과 인터페이스를 사용하여 C#에서 구현할 수 있습니다.

abstract class Leg { }

interface IAnimal { Leg GetLeg(); }

abstract class Animal<TLeg> : IAnimal where TLeg : Leg
 { public abstract TLeg GetLeg();
   Leg IAnimal.GetLeg() { return this.GetLeg(); }
 }

class Dog : Animal<Dog.DogLeg>
 { public class DogLeg : Leg { }
   public override DogLeg GetLeg() { return new DogLeg();}
 } 

GetLeg()는 Leg를 재정의하려면 반환해야 합니다.그러나 Dog 클래스는 Leg의 하위 클래스이므로 여전히 DogLeg 객체를 반환할 수 있습니다.그런 다음 클라이언트는 doglegs로 캐스팅하고 작동할 수 있습니다.

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}

문제를 일으키는 개념은 다음에 설명되어 있습니다. http://en.wikipedia.org/wiki/Covariance_and_contravariance_(컴퓨터_과학)

많이 사용되는 것은 아니지만 Java가 공변량 반환을 지원하므로 이것이 정확히 원하는 대로 작동한다는 점은 흥미로울 수 있습니다.Java에는 속성이 없다는 점을 제외하면;)

아마도 예제를 통해 문제를 확인하는 것이 더 쉬울 것입니다.

Animal dog = new Dog();
dog.SetLeg(new CatLeg());

이제 Dog를 컴파일했다면 컴파일해야 하지만 아마도 그런 돌연변이는 원하지 않을 것입니다.

관련된 문제는 Dog[]가 Animal[]이어야 하는지 아니면 IList<Dog>가 IList<Animal>이어야 하는지 여부입니다.

C#에는 이 문제를 해결하기 위한 명시적인 인터페이스 구현이 있습니다.

abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

class Dog : IAnimal
{
    public override DogLeg GetLeg() { /* */ }

    Leg IAnimal.GetLeg() { return GetLeg(); }
}

Dog 유형의 참조를 통해 Dog가 있는 경우 GetLeg()를 호출하면 DogLeg가 반환됩니다.동일한 개체가 있지만 참조가 IAnimal 유형인 경우 Leg를 반환합니다.

맞습니다. 그냥 캐스팅할 수 있다는 것은 이해합니다. 하지만 이는 클라이언트가 Dogs에 DogLegs가 있다는 것을 알아야 함을 의미합니다.내가 궁금한 것은 암시적 변환이 존재하는 경우 이것이 가능하지 않은 기술적인 이유가 있는지입니다.

@Brian Leahy는 분명히 다리로 만 작동하는 경우 캐스트 할 필요 나 이유가 없습니다.그러나 DogLeg 또는 Dog 특정 동작이 있는 경우 캐스트가 필요한 이유가 있는 경우가 있습니다.

Leg 및/또는 DogLeg가 모두 구현하는 ILeg 인터페이스를 반환할 수도 있습니다.

기억해야 할 중요한 점은 기본 유형을 사용하는 모든 곳에서 파생 유형을 사용할 수 있다는 것입니다(동물이 필요한 모든 메소드/속성/필드/변수에 Dog를 전달할 수 있음).

이 기능을 살펴보겠습니다.

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}

완벽하게 유효한 함수입니다. 이제 다음과 같이 함수를 호출해 보겠습니다.

AddLeg(new Dog());

Dog.Leg 속성이 Leg 유형이 아닌 경우 AddLeg 함수에 갑자기 오류가 포함되어 컴파일할 수 없습니다.

@루크

나는 아마도 당신이 상속을 오해하고 있다고 생각합니다.Dog.GetLeg()는 DogLeg 객체를 반환합니다.

public class Dog{
    public Leg GetLeg(){
         DogLeg dl = new DogLeg(super.GetLeg());
         //set dogleg specific properties
    }
}


    Animal a = getDog();
    Leg l = a.GetLeg();
    l.kick();

호출되는 실제 메서드는 Dog.GetLeg()입니다.그리고 DogLeg.Kick() (Leg.kick() 메서드가 존재한다고 가정합니다) 거기에 대해 선언된 반환 유형은 DogLeg가 필요하지 않습니다. 왜냐하면 Dog.GetLeg()에 대한 반환 유형이 다음과 같더라도 이것이 반환된 것이기 때문입니다. 다리.

다음과 같이 적절한 제약 조건이 있는 제네릭을 사용하면 원하는 것을 얻을 수 있습니다.

abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

class Dog : Animal<DogLeg>
{
    public override DogLeg GetLeg()
    {
        return new DogLeg();
    }
}

class DogLeg : Leg { }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top