C # : 반환 형식 재정의


81

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캐스팅하지 않고도 속성을 PooRadioactivePoo예를 들어,이 동안 Cat아직도의 일부가 될 수있는 Animal사용자는 반드시 자신의 방사성 똥에 대한 인식 또는 관리하지 않을 수 있습니다 목록입니다. 이해가되는 희망 ...

내가 볼 수있는 한 컴파일러는 적어도 이것을 허용하지 않습니다. 그래서 불가능하다고 생각합니다. 그러나 이것에 대한 해결책으로 무엇을 추천 하시겠습니까?


2
제네릭은 어떻습니까? 도움이되지 않습니까?
Arnis Lapsa

3
Cat.Excrement ()는 RadioActivePoo의 인스턴스를 Poo로 반환하면 안됩니까? 당신은 공통 인터페이스를 가지고 있습니다. (그리고 히스테리적인 예를 들어 주셔서 감사합니다.)
hometoast 2009-06-26

1
저도 예를 들어 감사하다고 말하고 싶습니다. 아래 @goodgai는 추상적 인 Poo를 만들 것을 제안합니다.-프로그래머가 아닌 사람에게 어떻게 설명 할 수 있을지 궁금합니다 ...
Paolo Tedesco 2009-06-26

1
@Konrad : 내가하는 경우는 약간 불확실 단지 그것이 사실은 매우 끔찍한 코드 냄새 나는 그것에 대해 알고 싶은 경우에 그 지적한다 (원인이있는 경우 나 또한 요구하는 경우는 그 화려한 의견에 웃음, 또는 : p)
Svish

3
이 예제 XD
Gurgadurgen 14

답변:


24

이미이 문제에 대한 해결책이 많이 있다는 것을 알고 있지만 기존 해결책으로 가지고 있던 문제를 해결하는 해결책을 생각 해낸 것 같습니다.

다음과 같은 이유로 기존 솔루션에 만족하지 못했습니다.

  • 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에서 반환하는 것과 같은 작업을 수행 하려면 둘 사이를 캐스트 할 수 있도록 사용해야합니다.

6
야, 이건 정말 멋질지도 몰라. 지금은 더 이상 분석 할 시간이 없지만 해주신 것 같습니다. 이걸 깨뜨렸다면 하이 파이브를하고 공유해 주셔서 감사합니다. 불행히도이 '똥'과 '배설물'사업은 큰주의를 산만하게합니다.
Nicholas Petersen 2013 년

1
메서드가 상속 된 유형을 반환해야하는 관련 문제에 대한 해결책을 찾은 것 같습니다. 즉, 'Animal'에서 상속 된 'Dog'의 메서드는 Animal이 아니라 Dog (this)를 반환합니다. 확장 방법으로 끝났으므로 여기에서 공유 할 수 있습니다.
Nicholas Petersen 2013 년

유형 안전성과 관련하여 bruce.Excement 및 bruceAsAnimal.Excrement는 RadioactivePoo 유형이 될 것입니까 ??
Anestis Kivranoglou

@AnestisKivranoglou 네 둘 다 RadioactivePoo 유형이 될 것입니다
rob

1
나는 48 시간 동안 그렇게 쟁기질을 해왔고,이 대답은 방금 깨졌습니다. 그리고 저는 ... "이봐, 이거 정말 멋질지도 몰라."
Martin Hansen Lennox

50

일반 기본 클래스는 어떻습니까?

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. "더 강력한 유형"이란 무엇을 의미합니까?


똑같은 생각을하고 있었다.
Doctor Jones

9
개와 고양이 사이의 pollymorphism에 대해 느슨하지 않습니까?
almog.ori 2009-06-26

재정의하고 싶은 다른 유형이 있다면 어떨까요? 기술적으로는 전체 유형 인수로 끝날 수 있습니다. 성가신 것 같지만 ... 네, 이것은 해결책입니다.
Svish 2009-06-26

@Svish : 일단 그런 일이 발생하면 종속성 주입 프레임 워크를 사용할 때라고 생각합니다.
Brian

StronglyTypedExcrement 메서드는 iPooProvider가 BaseAnimal인지 어떻게 알 수 있습니까? 그냥 추측합니까? 아니면 내가 보지 못하는 것입니까? 그 방법을 더 강력하게 입력하는 것이 가능할까요?
Svish

32

이를 반환 유형 공분산 이라고하며 일부 사람들의 희망 에도 불구하고 일반적으로 C # 또는 .NET에서는 지원되지 않습니다 .

내가 할 일은 동일한 서명을 유지하지만 ENSURE파생 클래스에 추가 절을 추가 하여 RadioActivePoo. 간단히 말해서, 구문으로는 할 수없는 일을 계약별로 디자인을 통해 할 것입니다.

다른 사람들은 대신 가짜를 선호합니다 . 괜찮은 것 같지만 저는 "인프라"코드 라인을 절약하는 경향이 있습니다. 코드의 의미가 충분히 명확하다면 나는 기쁘다. 비록 컴파일 타임 메커니즘은 아니지만 계약에 의한 디자인을 통해이를 달성 할 수있다.

다른 답변이 제안 하는 제네릭도 마찬가지입니다 . 나는 방사성 똥을 돌려주는 것보다 더 나은 이유로 그것들을 사용할 것입니다. 그러나 그것은 나뿐입니다.


ENSURE 절은 무엇입니까? 어떻게 작동할까요? .Net의 속성입니까?
Svish 2009-06-26

1
.Net에서 .Net 4.0의 코드 계약을보기 전에 ENSURE (x) 절을 단순히 "Debug.Assert (x)"로 작성합니다. 추가 참조는 예를 들어 archive.eiffel.com/doc/manuals/technology/contract/page.html 또는 객체 지향 소프트웨어 구성, 2 판, Bertrand Meyer (1994) 11 장을 참조하십시오.
Daniel Daranas

5
"난 그냥 방사성 똥을 반환하는 것보다 더 나은 이유를 사용합니다 -하지만 그건 그냥 날이다":) 좋아하는 자신의 따옴표 내 목록에 속하는
다니엘 Daranas

9

이 옵션도 있습니다 (명시 적 인터페이스 구현).

public class Cat:Animal
{
  Poo Animal.Excrement { get { return Excrement; } }
  public RadioactivePoo Excrement { get { return new RadioactivePoo(); } }
}

기본 클래스를 사용하여 Cat을 구현할 수있는 능력을 잃게되지만 더하기 측면에서는 Cat과 Dog 사이의 다형성을 유지합니다.

그러나 추가 된 복잡성이 그만한 가치가 있는지 의심합니다.


4

'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; } }
}
}

예제는 이것을 이해하기 훨씬 더 어렵게 만들었습니다. 하지만 훌륭한 코드!
rposky 2012

2

내가 틀렸지 만 Poo에서 상속하면 RadioActivePoo를 반환 할 수있는 pollymorphism의 전체 요점이 아닌 경우 계약은 추상 클래스와 동일하지만 RadioActivePoo ()를 반환합니다.


2
당신이 완벽하게 맞지만 그는 여분의 캐스트와 좀 더 강력한 타이핑을 피하고 싶어합니다. 이것은 대부분 제네릭의 목적입니다 ...
Paolo Tedesco 2009-06-26

2

이 시도:

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(); } 
        }
    }
}

여기서 Animal 인터페이스의 요점은 무엇입니까? 아무것도 상속하지 않습니다.
rob

1

제네릭이나 확장 방법에 의존하지 않고 오히려 방법을 숨기는 방법을 찾은 것 같습니다. 그러나 다형성을 깨뜨릴 수 있으므로 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; } }
}

Aack, 신경 쓰지 마. 나는 hjb417이 이미 비슷한 솔루션을 게시했다는 것을 몰랐습니다. 적어도 내 것은 기본 클래스를 수정할 필요가 없습니다.
Cybis 2010

귀하의 "솔루션" 은 다형성을 깨뜨 리므로 실제로는 솔루션이 아닙니다. 반면에 hjb 솔루션은 실제 솔루션이며 꽤 똑똑한 IMHO입니다.
greenoldman

0

RadioactivePoo가 똥에서 파생 된 다음 제네릭을 사용하면 도움이 될 수 있습니다.


0

참고로. 이것은 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


0

나는 당신의 대답이 공분산이라고 믿습니다.

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();
    }
}

0

인터페이스 반환을 사용할 수 있습니다. 귀하의 경우, IPoo.

이것은 주석 기반 클래스를 사용하고 있기 때문에 귀하의 경우 제네릭 유형을 사용하는 것보다 낫습니다.


0

뿐만 아니라 기술로서 다음 콤바인 몇 가지 다른 답변의 가장 좋은 측면 중 일부의 핵심 요소 수 있도록 Cat성기를 갖는 Excrement요구의 자산 RadioactivePoo유형을하지만 단지로 그것을 반환 할 수있는 Poo우리가 가지고 한 우리 만 알고있는 경우가 AnimalBase아닌 특히 Cat.

호출자는 제네릭이 구현에 존재하더라도 제네릭을 사용할 필요가 없으며 특별한 .NET을 얻기 위해 다른 이름의 함수를 호출 할 필요가 없습니다 Poo.

중간 클래스 AnimalWithSpecialisationsExcrement속성 을 봉인하는 역할 만하며 public이 아닌 속성을 통해 파생 반환 형식 의 속성 이있는 SpecialPoo파생 클래스에 연결합니다 .AnimalWithSpecialPoo<TPoo>Excrement

경우 Cat그의 유일한 동물이다 Poo어떤 방법으로 특별이, 또는 우리의 유형이 원하지 않는 Excrementa의 주요 정의 기능으로 Cat, 중간 제네릭 클래스는 계층 구조에서 제외 될 수 그래서 Cat직접 도출에서 AnimalWithSpecialisations,하지만 경우가 누구의 주요 특징들이 있다는 것입니다 여러 가지 동물 Poo의 중간 클래스에 "상용구"을 분리, 어떤 방법으로 특별하다 유지하는 데 도움이 Cat별도의 가상 함수 호출의 몇 가지의 비용이기는하지만, 상당히 깨끗한 클래스 자체는.

예제 코드는 대부분의 예상 작업이 "예상대로"작동 함을 보여줍니다.

public interface IExcretePoo<out TPoo>
  where TPoo : Poo
{
  TPoo Excrement { get; }
}

public class Poo
{ }

public class RadioactivePoo : Poo
{ }

public class AnimalBase : IExcretePoo<Poo>
{
  public virtual Poo Excrement { get { return new Poo(); } }
}

public class Dog : AnimalBase
{
  // No override, just return normal poo like normal animal
}

public abstract class AnimalWithSpecialisations : AnimalBase
{
  // this class connects AnimalBase to AnimalWithSpecialPoo<TPoo>
  public sealed override Poo Excrement { get { return SpecialPoo; } }

  // if not overridden, our "special" poo turns out just to be normal animal poo...
  protected virtual Poo SpecialPoo { get { return base.Excrement; } }
}

public abstract class AnimalWithSpecialPoo<TPoo> : AnimalWithSpecialisations, IExcretePoo<TPoo>
  where TPoo : Poo
{
  sealed protected override Poo SpecialPoo { get { return Excrement; } }
  public new abstract TPoo Excrement { get; }
}

public class Cat : AnimalWithSpecialPoo<RadioactivePoo>
{
  public override RadioactivePoo 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.Excrement;

    AnimalBase animal = cat;

    Poo animalPoo = catPoo;
    animalPoo = animal.Excrement;

    AnimalWithSpecialPoo<RadioactivePoo> radioactivePooingAnimal = cat;
    RadioactivePoo radioactivePoo = radioactivePooingAnimal.Excrement;

    IExcretePoo<Poo> pooExcreter = cat; // through this interface we don't know the Poo was radioactive.
    IExcretePoo<RadioactivePoo> radioactivePooExcreter = cat; // through this interface we do.

    // we can replace these with the dog equivalents:
    animal = dog;
    animalPoo = dogPoo;
    pooExcreter = dog;

    // but we can't do:
    // radioactivePooExcreter = dog;
    // radioactivePooingAnimal = dog;
    // radioactivePoo = dogPoo;
  }

0

C # 9는 공변 재정의 반환 형식을 제공합니다. 기본적으로 : 원하는 것은 작동합니다 .


-1

다음과 같은 덕분에 상속 된 반환 유형 (정적 메서드의 경우에도)과 다른 구체적인 유형을 실제로 반환 할 수 있습니다 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성능에 영향을 미치므로 피하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.