문제

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 여전히 An의 일부가 될 수 있습니다 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();
    }
}

이런 식으로 개와 고양이는 모두 동물로부터 물려받습니다 (의견에서 언급 한 바와 같이, 첫 번째 해결책은 상속을 보존하지 않았습니다).
마커 인터페이스로 클래스를 명시 적으로 표시해야하지만 고통 스럽지만 아마도 몇 가지 아이디어를 줄 수 있습니다 ...

두 번째 편집 @Svish : 확장 방법이 어떤 식 으로든 시행되지 않는다는 것을 설명하기 위해 코드를 수정했습니다. iPooProvider 상속 BaseAnimal. "더 강력한"란 무엇을 의미합니까?

다른 팁

이것은 ... 불리운다 반환 유형 공분산 일부 사람들의 경우에도 C# 또는 .NET에서 일반적으로 지원되지 않습니다. 소원.

내가 할 일은 동일한 서명을 유지하지만 추가를 추가하는 것입니다. ENSURE 파생 클래스에 대한 조항은 RadioActivePoo. 요컨대, 계약을 통해 구문을 통해 할 수없는 일을 계약으로 설계를 통해 할 것입니다.

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

제네릭에 대해서도 동일합니다 다른 답변 제안하다. 나는 단지 방사성 똥을 돌려주는 것보다 더 나은 이유로 그것들을 사용할 것입니다. 그러나 그것은 단지 나입니다.

이 문제에 대한 많은 솔루션이 이미 있다는 것을 알고 있지만 기존 솔루션과 관련된 문제를 해결하는 문제를 생각해 냈다고 생각합니다.

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

  • Paolo Tedesco의 첫 번째 솔루션 : 고양이와 개는 공통 기본 클래스가 없습니다.
  • 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> { }

이 솔루션을 사용하면 개나 고양이에서 아무것도 무시할 필요가 없습니다! 다음은 샘플 사용량입니다.

Cat bruce = new Cat();
IAnimal bruceAsAnimal = bruce as IAnimal;
Console.WriteLine(bruce.Excrement.ToString());
Console.WriteLine(bruceAsAnimal.Excrement.ToString());

이것은 "방사성 푸"가 두 번 출력 될 것입니다.

추가 독서

  • 명시 적 인터페이스 구현
  • 새로운 수정 자. 이 단순화 된 솔루션에서는 사용하지 않았지만 더 복잡한 솔루션에서 필요할 수 있습니다. 예를 들어, BAILIANIMAL에 대한 인터페이스를 만들려면 "Pootyp Decrement"의 감소에서이를 사용해야합니다.
  • 일반 수정 자 (공분산). 다시이 솔루션에서는 사용하지 않았지만 Return과 같은 일을하고 싶다면 MyType<Poo> Ianimal에서 돌아 왔습니다 MyType<PooType> Baseanimal에서 두 사이를 캐스트 할 수 있도록 사용해야합니다.

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

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

당신은 기본 등급을 사용하여 고양이를 구현할 수있는 능력을 상실하지만, 플러스쪽에는 고양이와 개 사이의 다형성을 유지합니다.

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

'배설물'을 생성하는 보호 된 가상 메소드를 정의하고 '배설물'을 반환하는 공공 재산을 유지하십시오. 파생 클래스는 기본 클래스의 리턴 유형을 무시할 수 있습니다.

다음 예에서, 나는 '배설물'을 비판적이지만, 파생 된 클래스가 적절한 '똥'을 제공 할 수 있도록 속성 ExcrementImpl을 제공합니다. 그런 다음 파생 유형은 기본 클래스 구현을 숨겨 '배출'의 반환 유형을 무시할 수 있습니다.

전:

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

내가 틀렸다면 나를 교정하지만 똥에서 상속되면 방사성 푸를 반환 할 수있는 폴리 모르 스피즘의 요점이 아니다

이 시도:

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

방사성 푸가 똥에서 파생 된 다음 제네릭을 사용하는 경우 도움이 될 수 있습니다.

참고로. 이것은 스칼라에서 아주 쉽게 구현됩니다.

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를 개발하려는 경우 일부 사용 사례에서 유용성 및 DEV 경험을 향상시킬 수 있습니다. 그럼에도 불구하고, 사용 dynamic 성능에 영향을 미치므로 피하십시오.

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