C # : 반환 형식 재정의
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
사용자는 반드시 자신의 방사성 똥에 대한 인식 또는 관리하지 않을 수 있습니다 목록입니다. 이해가되는 희망 ...
내가 볼 수있는 한 컴파일러는 적어도 이것을 허용하지 않습니다. 그래서 불가능하다고 생각합니다. 그러나 이것에 대한 해결책으로 무엇을 추천 하시겠습니까?
일반 기본 클래스는 어떻습니까?
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
. 간단히 말해서, 구문으로는 할 수없는 일을 계약별로 디자인을 통해 할 것입니다.
다른 사람들은 대신 가짜를 선호합니다 . 괜찮은 것 같지만 저는 "인프라"코드 라인을 절약하는 경향이 있습니다. 코드의 의미가 충분히 명확하다면 만족스럽고 계약에 의한 설계를 통해 컴파일 시간 메커니즘이 아니지만이를 달성 할 수 있습니다.
다른 답변이 제안 하는 제네릭도 마찬가지입니다 . 나는 방사성 똥을 돌려주는 것보다 더 나은 이유에서 그것들을 사용할 것입니다. 그러나 그것은 나뿐입니다.
이미이 문제에 대한 해결책이 많이 있다는 것을 알고 있지만 기존 해결책으로 가지고 있던 문제를 해결하는 해결책을 생각 해낸 것 같습니다.
다음과 같은 이유로 기존 솔루션에 만족하지 못했습니다.
- 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 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(); }
}
}
}
제네릭이나 확장 방법에 의존하지 않고 오히려 방법을 숨기는 방법을 찾았다 고 생각합니다. 그러나 다형성을 깨뜨릴 수 있으므로 Cat에서 더 상속하는 경우 특히주의하십시오.
이 게시물이 8 개월 늦었음에도 불구하고 여전히 누군가를 도울 수 있기를 바랍니다.
public interface Animal
{
Poo Excrement { get; }
}
public class Poo
{
}
public class RadioActivePoo : Poo
{
}
public class AnimalBase : Animal
{
public virtual Poo Excrement { get { return new Poo(); } }
}
public class Dog : AnimalBase
{
// No override, just return normal poo like normal animal
}
public class CatBase : AnimalBase
{
public override Poo Excrement { get { return new RadioActivePoo(); } }
}
public class Cat : CatBase
{
public new RadioActivePoo Excrement { get { return (RadioActivePoo) base.Excrement; } }
}
RadioactivePoo가 똥에서 파생 된 다음 제네릭을 사용하면 도움이 될 수 있습니다.
참고로. 이것은 Scala에서 아주 쉽게 구현됩니다.
trait Path
trait Resource
{
def copyTo(p: Path): Resource
}
class File extends Resource
{
override def copyTo(p: Path): File = new File
override def toString = "File"
}
class Directory extends Resource
{
override def copyTo(p: Path): Directory = new Directory
override def toString = "Directory"
}
val test: Resource = new Directory()
test.copyTo(null)
다음은 플레이 할 수있는 라이브 예제입니다. http://www.scalakata.com/50d0d6e7e4b0a825d655e832
나는 당신의 대답이 공분산이라고 믿습니다.
class Program
{
public class Poo
{
public virtual string Name { get{ return "Poo"; } }
}
public class RadioactivePoo : Poo
{
public override string Name { get { return "RadioactivePoo"; } }
public string DecayPeriod { get { return "Long time"; } }
}
public interface IAnimal<out T> where T : Poo
{
T Excrement { get; }
}
public class Animal<T>:IAnimal<T> where T : Poo
{
public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } }
private T _excrement;
}
public class Dog : Animal<Poo>{}
public class Cat : Animal<RadioactivePoo>{}
static void Main(string[] args)
{
var dog = new Dog();
var cat = new Cat();
IAnimal<Poo> animal1 = dog;
IAnimal<Poo> animal2 = cat;
Poo dogPoo = dog.Excrement;
//RadioactivePoo dogPoo2 = dog.Excrement; // Error, dog poo is not RadioactivePoo.
Poo catPoo = cat.Excrement;
RadioactivePoo catPoo2 = cat.Excrement;
Poo animal1Poo = animal1.Excrement;
Poo animal2Poo = animal2.Excrement;
//RadioactivePoo animal2RadioactivePoo = animal2.Excrement; // Error, IAnimal<Poo> reference do not know better.
Console.WriteLine("Dog poo name: {0}",dogPoo.Name);
Console.WriteLine("Cat poo name: {0}, decay period: {1}" ,catPoo.Name, catPoo2.DecayPeriod);
Console.WriteLine("Press any key");
var key = Console.ReadKey();
}
}
인터페이스 반환을 사용할 수 있습니다. 귀하의 경우에는 IPoo.
이것은 주석 기반 클래스를 사용하고 있기 때문에 귀하의 경우 제네릭 유형을 사용하는 것보다 낫습니다.
다음과 같은 덕분에 상속 된 반환 유형 (정적 메서드의 경우에도)과 다른 구체적인 유형을 실제로 반환 할 수 있습니다 dynamic
.
public abstract class DynamicBaseClass
{
public static dynamic Get (int id) { throw new NotImplementedException(); }
}
public abstract class BaseClass : DynamicBaseClass
{
public static new BaseClass Get (int id) { return new BaseClass(id); }
}
public abstract class DefinitiveClass : BaseClass
{
public static new DefinitiveClass Get (int id) { return new DefinitiveClass(id);
}
public class Test
{
public static void Main()
{
var testBase = BaseClass.Get(5);
// No cast required, IntelliSense will even tell you
// that var is of type DefinitiveClass
var testDefinitive = DefinitiveClass.Get(10);
}
}
회사를 위해 작성한 API 래퍼에 이것을 구현했습니다. API를 개발하려는 경우 일부 사용 사례에서 유용성과 개발 환경을 개선 할 수 있습니다. 그럼에도 불구하고의 사용은 dynamic
성능에 영향을 미치므로 피하십시오.
참고URL : https://stackoverflow.com/questions/1048884/c-overriding-return-types
'Program Tip' 카테고리의 다른 글
노드의 중괄호 (중괄호) require 문 (0) | 2020.10.24 |
---|---|
ASP.NET MVC에서 권한이없는 컨트롤러 리디렉션 (0) | 2020.10.24 |
C #에서 하위 목록을 가져 오는 방법 (0) | 2020.10.24 |
Node.js 용 유효성 검사 라이브러리 (0) | 2020.10.24 |
Android : API 수준 VS. (0) | 2020.10.24 |