编译派生类虚函数的时间安全性?

时间:2017-11-16 21:41:34

标签: c#

我有这段代码,问题在代码下面:

public class MainProgram
{
    public static void Main()
    {
        List<Animal> Animals = new List<Animal>();
        Animals.Add(new Dog());
        Animals.Add(new Poodle());
        Animals.Add(new Poodle());
        Animals.Add(new Beagle());
        Animals.Add(new Cat());

        Bark[] JustTheBarks = Dog.GetBarkList(Animals);
        foreach (Bark B in JustTheBarks)
        {
            Console.WriteLine(B.ToString());
        }
    }
}

abstract class Animal
{
    public abstract Noise GetNoise();
}

class Dog : Animal
{
    public override Noise GetNoise() 
    {
        return new Bark("bark");
    }

    public static Bark[] GetBarkList(List<Animal> List)
    {
        return List
            .OfType<Dog>()
            .Select(r => r.GetNoise())
            .Cast<Bark>()
            .ToArray();
    }
}

class Beagle : Dog
{
    public override Noise GetNoise()
    {
        return new Woof("woof", 7);
    }
}

class Poodle : Dog
{
    public override Noise GetNoise()
    {
        return new Purr();
    }
}

class Cat : Animal
{
    public override Noise GetNoise()
    {
        throw new NotImplementedException();
    }
}

class Noise
{
}

class Bark : Noise
{
    protected string Text;
    public Bark(string Text)
    {
        this.Text = Text;
    }

    public override string ToString()
    {
        return $"{Text}";
    }
}


class Woof : Bark
{
    protected int Pitch;

    public Woof(string Text, int Pitch) : base(Text)
    {
        this.Pitch = Pitch;
    }
    public override string ToString()
    {
        return $"{Text}->{Pitch}";
    }
}

class Purr : Noise { }

}

在纯文本中,动物和噪音,每只动物都会返回自己的噪音类型,噪音对应于动物类别,尽管有时动物可能会根据某些变量返回不同的噪音(但对于只有某种形式的树皮,狗只会返回不同的噪音) )。

这段代码当然崩溃了。 Poodle返回一个Purr(),它应该&#34;&#34;返回Bark类型的东西。我有抽象函数GetNoise(),它只返回一个Noise。我想要/需要的是写

class Dog
{
    public override Bark GetNoise();
}

但是这是不允许的,返回类型必须相同。我不想在每个Dog类等中编写第二层GetBark()函数。

我想要的是重写这个没有强制转换,并且从Dog派生的所有内容都被强制在其GetNoise()函数中返回一个Bark,以便我可以安全地写:

Bark B = Dog.GetNoise();

3 个答案:

答案 0 :(得分:3)

Scott Chamberlain的回答是完全合理的;我会添加两件事。

首先,您想要的功能被称为&#34;虚方法协方差&#34;。它受C ++支持,但不受C#支持。自C#早期以来,它一直是一个经常被要求的功能,但从未实现过,因为坦白说,它并不是一个很棒的功能:

  • 虽然在简单场景中是类型安全的,但它在更复杂的版本化场景中引入了新的脆弱基类故障
  • CLR本身不支持它,因此编译器必须生成辅助方法,这些方法效率不高
  • 您可以通过自己实施帮助方法来模拟该功能

如果你想要它,请在Roslyn github论坛上提倡它。你不会是唯一一个。

其次,除了Scott的解决方案之外,您可以考虑实施

public new virtual Bark GetNoise() => GetBark();

如您所知,您可能无法使用与签名和返回类型不完全匹配的任何方法填充现有虚拟方法槽。但是你当然可以引入一个具有不同返回类型的 new 虚拟插槽。

答案 1 :(得分:2)

这样做的方法是密封Dog级别的过载,然后制作一个孩子必须实施的新保护方法。

using System;
using System.Collections.Generic;
using System.Linq;

public class MainProgram
{
    public static void Main()
    {
        List<Animal> Animals = new List<Animal>();
        Animals.Add(new Dog());
        Animals.Add(new Poodle());
        Animals.Add(new Poodle());
        Animals.Add(new Beagle());
        Animals.Add(new Cat());

        Bark[] JustTheBarks = Dog.GetBarkList(Animals);
        foreach (Bark B in JustTheBarks)
        {
            Console.WriteLine(B.ToString());
        }
    }
}

abstract class Animal
{
    public abstract Noise GetNoise();
}

class Dog : Animal
{
    public sealed override Noise GetNoise() 
    {
        return GetBark();
    }

    protected virtual Bark GetBark()
    {
        return new Bark("bark");
    }

    public static Bark[] GetBarkList(List<Animal> List)
    {
        return List
            .OfType<Dog>()
            .Select(r => r.GetBark()) //Now calls GetBark() instead of GetNoise()
            .ToArray();
    }
}

class Beagle : Dog
{
    protected override Bark GetBark()
    {
        return new Woof("woof", 7);
    }
}

class Poodle : Dog
{
    protected override Bark GetBark()
    {
        return new Purr(); //This is now a compiler error.
    }
}

class Cat : Animal
{
    public override Noise GetNoise()
    {
        throw new NotImplementedException();
    }
}

class Noise
{
}

class Bark : Noise
{
    protected string Text;
    public Bark(string Text)
    {
        this.Text = Text;
    }

    public override string ToString()
    {
        return $"{Text}";
    }
}


class Woof : Bark
{
    protected int Pitch;

    public Woof(string Text, int Pitch) : base(Text)
    {
        this.Pitch = Pitch;
    }
    public override string ToString()
    {
        return $"{Text}->{Pitch}";
    }
}

class Purr : Noise { }

答案 2 :(得分:-1)

这是OO的经典误用。

Beagle和Poodle不是&#34; Dog&#34;的子类。相反,它们是具有相似特征的不同类型的狗。它们都具有相同的属性,但这些属性的值标识类似实例的组而不是子类。

这类似于&#34; Square&#34;和&#34;矩形&#34;作为形状的子类,实际上它们都是多边形的实例。它们共有的属性(4个顶点,4个相等的90度内角)不构成不同的形状类别,而是这些形状的属性的不同值。

在您的示例中,该问题更深入地将噪声的不同特征区分为子类。 Purr和Bark都是仅通过波形和/或生产方法区分的噪声的实例。

缺少的是噪音的性质(声音,次声或其他 - 例如昆虫摩擦腿/翼壳等)或噪音的目的(警告,快乐,愤怒,焦虑,疼痛,吸引力)伙伴等)

将动物的噪音降低到单一方法可以解决这个问题。

假设猫和狗是(更多)合法的动物亚类:猫有能力(一种方法)到Purr()但是狗没有。狗有能力吠(),但猫没有。但是,Cat()具有Meow()的能力,在某些情况下,Meow()可能被认为等同于Bark(),但在其他情况下,Hiss()可能更接近等效。再一次,当Dog Growls()类似于Cat Hiss()ing或Purr()ing(生理上是后者,在语境上是前者)?

然后有Yelp()ing。哈尔()ING。等

所有这些都是噪音,并且与任何特定的动物类型无关。没有合理的基础可以说如果某些东西能够产生任何噪音,那么它只能制作特定的噪声子类。

简而言之:此对象模型已损坏。:)

而不是试图扭曲类以适应破碎的模型,从长远来看,它将更好地为您提供修复模型本身。

如何修复它的种类几乎是无限的。

您可能有针对特定噪音的特定方法。您可以将这些方法组成为接口的成员。方法或接口可能围绕噪声的性质进行组织(IBark,IYelp,IPurr等)

IBark.Bark()
IYelp.Yelp()

或者可能有针对行为集合的方法(IExpressPain,IExpressHappiness):

List<EmotionalExpression> IExpressPain.ExpressionsOfPain()
List<EmotionalExpression> IExpressPain.ExpressionsOfHappiness()

或者这些方法可能会将表达式添加到某些指定和提供的集合中:

IExpressPain.ExpressIrritation(List<EmotionalExpression> expressions)

情感表达可能是(例如)Wag,Twitch,Purr,Growl等的一些表示。其中Wag和Twitch是TailExpression的子类,而Purr和Growl是SubVocalExpression的子类(反过来是AudibleExpression的子类)等因此,对于任何特定的情绪驱动行为,动物可以表达不同类别(物理,听觉等)的任何数量的不同情绪表达。

这完全取决于应用程序的需求和可接受的复杂性。

但我认为可以说GetNoise()是 over - 简化,因此你很难。