C #에서 반환 형식을 재정의하는 방법이 있습니까? 그렇다면 어떻게, 그렇지 않은 경우 권장되는 방법은 무엇입니까?
제 경우에는 추상 기본 클래스와 그 자손이있는 인터페이스가 있습니다. 나는 이것을하고 싶다 (실제로는 아니지만 예를 들어!) :
public interface Animal
{
Poo Excrement { get; }
}
public class AnimalBase
{
public virtual Poo Excrement { get { return new Poo(); } }
}
public class Dog
{
// No override, just return normal poo like normal animal
}
public class Cat
{
public override RadioactivePoo Excrement { get { return new RadioActivePoo(); } }
}
RadioactivePoo
물론 Poo
.
이 원하는에 대한 나의 이유는 사용하는 사람들이 너무 Cat
객체가 사용할 수 Excrement
캐스팅하지 않고도 속성을 Poo
로 RadioactivePoo
예를 들어,이 동안 Cat
아직도의 일부가 될 수있는 Animal
사용자는 반드시 자신의 방사성 똥에 대한 인식 또는 관리하지 않을 수 있습니다 목록입니다. 이해가되는 희망 …
내가 볼 수있는 한 컴파일러는 적어도 이것을 허용하지 않습니다. 그래서 불가능하다고 생각합니다. 그러나 이것에 대한 해결책으로 무엇을 추천 하시겠습니까?
답변
이미이 문제에 대한 해결책이 많이 있다는 것을 알고 있지만 기존 해결책으로 가지고 있던 문제를 해결하는 해결책을 생각 해낸 것 같습니다.
다음과 같은 이유로 기존 솔루션에 만족하지 못했습니다.
- Paolo Tedesco의 첫 번째 솔루션 : Cat과 Dog에는 공통 기본 클래스가 없습니다.
- Paolo Tedesco의 두 번째 솔루션 : 약간 복잡하고 읽기가 어렵습니다.
- Daniel Daranas의 솔루션 : 이것은 작동하지만 불필요한 캐스팅 및 Debug.Assert () 문이 많이 사용되어 코드가 복잡해집니다.
- hjb417의 솔루션 : 이 솔루션을 사용하면 로직을 기본 클래스에 유지할 수 없습니다. 이 예제 (생성자 호출)에서는 논리가 매우 사소하지만 실제 예제에서는 그렇지 않습니다.
내 솔루션
이 솔루션은 제네릭과 메서드 숨김을 모두 사용하여 위에서 언급 한 모든 문제를 극복해야합니다.
public class Poo { }
public class RadioactivePoo : Poo { }
interface IAnimal
{
Poo Excrement { get; }
}
public class BaseAnimal<PooType> : IAnimal
where PooType : Poo, new()
{
Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } }
public PooType Excrement
{
get { return new PooType(); }
}
}
public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }
이 솔루션을 사용하면 Dog 또는 Cat!에서 아무것도 재정의 할 필요가 없습니다! 다음은 몇 가지 샘플 사용입니다.
Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());
그러면 다형성이 깨지지 않았 음을 보여주는 “RadioactivePoo”가 두 번 출력됩니다.
추가 읽기
- 명시 적 인터페이스 구현
- 새로운 수정 자 . 이 단순화 된 솔루션에서는 사용하지 않았지만 더 복잡한 솔루션에서 필요할 수 있습니다. 예를 들어 BaseAnimal에 대한 인터페이스를 생성하려면 “PooType Excrement”를 정의 할 때이를 사용해야합니다.
- out Generic Modifier (공분산) . 다시이 솔루션에서는 사용하지 않았지만
MyType<Poo>
IAnimal에서 반환MyType<PooType>
하고 BaseAnimal에서 반환하는 것과 같은 작업을 수행 하려면 둘 사이를 캐스트 할 수 있도록 사용해야합니다.
답변
일반 기본 클래스는 어떻습니까?
public class Poo { }
public class RadioactivePoo : Poo { }
public class BaseAnimal<PooType>
where PooType : Poo, new() {
PooType Excrement {
get { return new PooType(); }
}
}
public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }
편집 : 확장 방법과 마커 인터페이스를 사용하는 새로운 솔루션 …
public class Poo { }
public class RadioactivePoo : Poo { }
// just a marker interface, to get the poo type
public interface IPooProvider<PooType> { }
// Extension method to get the correct type of excrement
public static class IPooProviderExtension {
public static PooType StronglyTypedExcrement<PooType>(
this IPooProvider<PooType> iPooProvider)
where PooType : Poo {
BaseAnimal animal = iPooProvider as BaseAnimal;
if (null == animal) {
throw new InvalidArgumentException("iPooProvider must be a BaseAnimal.");
}
return (PooType)animal.Excrement;
}
}
public class BaseAnimal {
public virtual Poo Excrement {
get { return new Poo(); }
}
}
public class Dog : BaseAnimal, IPooProvider<Poo> { }
public class Cat : BaseAnimal, IPooProvider<RadioactivePoo> {
public override Poo Excrement {
get { return new RadioactivePoo(); }
}
}
class Program {
static void Main(string[] args) {
Dog dog = new Dog();
Poo dogPoo = dog.Excrement;
Cat cat = new Cat();
RadioactivePoo catPoo = cat.StronglyTypedExcrement();
}
}
이런 식으로 Dog와 Cat은 모두 Animal에서 상속받습니다 (댓글에서 언급했듯이 첫 번째 솔루션은 상속을 보존하지 않았습니다).
마커 인터페이스로 클래스를 명시 적으로 표시하는 것이 필요합니다. 이것은 고통 스럽지만 아마도 이것은 당신에게 몇 가지 아이디어를 줄 수 있습니다.
SECOND EDIT @Svish : 확장 메서드가 .NET Framework에서 iPooProvider
상속 된 사실을 어떤 식 으로든 시행하지 않는다는 것을 명시 적으로 보여주기 위해 코드를 수정했습니다 BaseAnimal
. “더 강력한 유형”이란 무엇을 의미합니까?
답변
이를 반환 유형 공분산 이라고하며 일부 사람들의 희망 에도 불구하고 일반적으로 C # 또는 .NET에서는 지원되지 않습니다 .
내가 할 일은 동일한 서명을 유지하지만 ENSURE
파생 클래스에 추가 절을 추가 하여 RadioActivePoo
. 간단히 말해서, 구문으로는 할 수없는 일을 계약별로 디자인을 통해 할 것입니다.
다른 사람들은 대신 가짜를 선호합니다 . 괜찮은 것 같지만 저는 “인프라”코드 라인을 절약하는 경향이 있습니다. 코드의 의미가 충분히 명확하다면 나는 기쁘다. 비록 컴파일 타임 메커니즘은 아니지만 계약에 의한 디자인을 통해이를 달성 할 수있다.
다른 답변이 제안 하는 제네릭도 마찬가지입니다 . 나는 방사성 똥을 돌려주는 것보다 더 나은 이유로 그것들을 사용할 것입니다. 그러나 그것은 나뿐입니다.
답변
이 옵션도 있습니다 (명시 적 인터페이스 구현).
public class Cat:Animal
{
Poo Animal.Excrement { get { return Excrement; } }
public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}
기본 클래스를 사용하여 Cat을 구현할 수있는 능력을 잃게되지만 더하기 측면에서는 Cat과 Dog 사이의 다형성을 유지합니다.
그러나 추가 된 복잡성이 그만한 가치가 있는지 의심합니다.
답변
‘Excrement’를 생성하고 ‘Excrement’를 반환하는 공용 속성을 가상이 아닌 상태로 유지하는 보호 된 가상 메서드를 정의하지 않는 이유는 무엇입니까? 그러면 파생 클래스가 기본 클래스의 반환 형식을 재정의 할 수 있습니다.
다음 예제에서는 ‘Excrement’를 가상이 아닌 것으로 만들지 만 파생 클래스가 적절한 ‘Poo’를 제공 할 수 있도록 ExcrementImpl 속성을 제공합니다. 파생 된 형식은 기본 클래스 구현을 숨겨서 ‘Excrement’반환 형식을 재정의 할 수 있습니다.
전의:
namepace ConsoleApplication8
{
public class Poo { }
public class RadioactivePoo : Poo { }
public interface Animal
{
Poo Excrement { get; }
}
public class AnimalBase
{
public Poo Excrement { get { return ExcrementImpl; } }
protected virtual Poo ExcrementImpl
{
get { return new Poo(); }
}
}
public class Dog : AnimalBase
{
// No override, just return normal poo like normal animal
}
public class Cat : AnimalBase
{
protected override Poo ExcrementImpl
{
get { return new RadioactivePoo(); }
}
public new RadioactivePoo Excrement { get { return (RadioactivePoo)ExcrementImpl; } }
}
}
답변
내가 틀렸지 만 Poo에서 상속하면 RadioActivePoo를 반환 할 수있는 pollymorphism의 전체 요점이 아닌 경우 계약은 추상 클래스와 동일하지만 RadioActivePoo ()를 반환합니다.
답변
이 시도:
namespace ClassLibrary1
{
public interface Animal
{
Poo Excrement { get; }
}
public class Poo
{
}
public class RadioactivePoo
{
}
public class AnimalBase<T>
{
public virtual T Excrement
{
get { return default(T); }
}
}
public class Dog : AnimalBase<Poo>
{
// No override, just return normal poo like normal animal
}
public class Cat : AnimalBase<RadioactivePoo>
{
public override RadioactivePoo Excrement
{
get { return new RadioactivePoo(); }
}
}
}