Program Tip

C # : 반환 형식 재정의

programtip 2020. 10. 24. 11:39
반응형

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캐스팅하지 않고도 속성을 PooRadioactivePoo예를 들어,이 동안 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

반응형