Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么';继承不能按我认为应该的方式工作吗?_C#_Oop_Inheritance_Covariance_Contravariance - Fatal编程技术网

C# 为什么';继承不能按我认为应该的方式工作吗?

C# 为什么';继承不能按我认为应该的方式工作吗?,c#,oop,inheritance,covariance,contravariance,C#,Oop,Inheritance,Covariance,Contravariance,我有一些继承问题,因为我有一组相互关联的抽象类,这些抽象类需要一起重写以创建客户机实现。理想情况下,我想做如下工作: abstract class Animal { public Leg GetLeg() {...} } abstract class Leg { } class Dog : Animal { public override DogLeg Leg() {...} } class DogLeg : Leg { } abstract class Animal<Le

我有一些继承问题,因为我有一组相互关联的抽象类,这些抽象类需要一起重写以创建客户机实现。理想情况下,我想做如下工作:

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }
abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

class Dog : Animal<DogLeg>
{
    public override DogLeg GetLeg()
    {
        return new DogLeg();
    }
}

class DogLeg : Leg { }
这将允许任何使用Dog类的人自动获得DogLegs,任何使用Animal类的人获得Legs。问题是被重写的函数必须与基类具有相同的类型,这样就不会编译。我不明白为什么它不应该,因为狗腿是隐式铸造到腿。我知道有很多方法可以解决这个问题,但我更好奇的是,为什么这在C#中不可能实现

编辑:我对其进行了一些修改,因为我实际上在代码中使用的是属性而不是函数

编辑:我把它改回了函数,因为答案只适用于那种情况(属性的set函数的value参数的协方差不应该起作用)。对不起,波动太大了!我意识到这会让很多答案看起来不相关。

GetLeg()必须返回Leg才能作为覆盖。但是,您的Dog类仍然可以返回DogLeg对象,因为它们是Leg的子类。然后,客户可以像狗腿一样对它们施放和操作

public class ClientObj{
    public void doStuff(){
    Animal a=getAnimal();
    if(a is Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
    }
  }
}

作为返回类型,Dog应该返回一条腿,而不是一条狗腿。实际的类可能是DogLeg,但关键是要解耦,这样Dog的用户就不必知道DogLegs,他们只需要知道Legs

更改:

class Dog : Animal
{
  public override DogLeg GetLeg() {...}
}
致:

不要这样做:

 if(a instanceof Dog){
       DogLeg dl = (DogLeg)a.GetLeg();
它违背了抽象类型编程的目的

隐藏DogLeg的原因是因为抽象类中的GetLeg函数返回一个抽象Leg。如果要覆盖GetLeg,则必须返回一个Leg。这就是在抽象类中使用方法的意义所在。将该方法传播给它的孩子。如果您想让狗的用户了解狗腿,请创建一个名为GetDogLeg的方法并返回一个DogLeg


如果你能按照提问者的要求去做,那么每一个使用动物的人都需要了解所有的动物。

是的,我知道我可以投,但这意味着客户必须知道狗有狗腿。我想知道的是,考虑到隐式转换的存在,是否有技术上的原因导致这不可能实现。

简单的回答是GetLeg的返回类型是不变的。答案很长,可以在这里找到:

我想补充一点,虽然继承通常是大多数开发人员从工具箱中取出的第一个抽象工具,但几乎总是可以使用组合来代替。对于API开发人员来说,合成的工作稍微多一些,但会使API对其用户更有用。

@Brian Leahy
显然,如果你只是作为一条腿进行手术,就没有必要也没有理由施放石膏。但是,如果有狗腿或狗狗特有的行为,有时也会有必要使用石膏的原因。

导致问题的概念在

@Luke中描述

我想你可能误解了遗产。GetLeg()将返回一个DogLeg对象

public class Dog{
    public Leg GetLeg(){
         DogLeg dl = new DogLeg(super.GetLeg());
         //set dogleg specific properties
    }
}


    Animal a = getDog();
    Leg l = a.GetLeg();
    l.kick();

实际调用的方法将是Dog.GetLeg();和DogLeg.Kick()(我假设存在一个方法Leg.Kick()),声明的返回类型为DogLeg是不必要的,因为这就是返回的,即使Dog.GetLeg()的返回类型是Leg。

您也可以返回Leg和/或DogLeg都实现的接口ILeg

abstract class Animal
{
  public virtual Leg GetLeg ()
}

abstract class Leg { }

class Dog : Animal
{
  public override Leg GetLeg () { return new DogLeg(); }
}

class DogLeg : Leg { void Hump(); }
这样做,您就可以利用客户机中的抽象:

Leg myleg = myDog.GetLeg();
然后,如果需要,您可以施放:

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }
完全是人为的,但关键是你可以这样做:

foreach (Animal a in animals)
{
   a.GetLeg().SomeMethodThatIsOnAllLegs();
}

同时仍然保留在狗腿上使用特殊驼峰方法的能力。

也许通过一个例子更容易看出问题:

Animal dog = new Dog();
dog.SetLeg(new CatLeg());
现在,如果你是狗编译的话,应该可以编译了,但我们可能不想要这样一个变种


一个相关的问题是Dog[]应该是Animal[],还是IList应该是IList?

需要记住的重要一点是,您可以在每个使用基类型的地方使用派生类型(您可以将Dog传递给任何需要Animal的方法/属性/字段/变量)

让我们来看看这个函数:

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}
一个完全有效的函数,现在让我们这样调用函数:

AddLeg(new Dog());

如果Dog.Leg属性不是Leg类型,AddLeg函数突然包含错误,无法编译。

这并不是说它有多大用处,但值得注意的是,Java确实支持协变返回,因此这正是您所希望的。很明显,Java没有属性;)

很明显,如果你是
在断了的狗腿上做手术

您可以使用泛型和接口在C#中实现这一点:

抽象类Leg{}
接口IAnimal{Leg GetLeg();}
抽象类动物:IAnimal其中TLeg:腿
{public abstract TLeg GetLeg();
Leg IAnimal.GetLeg(){返回this.GetLeg();}
}
狗类:动物
{公共类狗腿:狗腿{}
public override DogLeg GetLeg(){return new DogLeg();}
} 

让重写方法的签名具有作为重写方法(phew)中的返回类型的子类型的返回类型是完全正确的愿望。毕竟,它们是运行时类型兼容的

<>但是C++在“重写”方法中还没有支持“协变返回类型”(不像C++(1998)和java(2004))。 正如Eric Lippert在文章中所说的那样,你需要在可预见的未来努力工作 [2008年6月19日]:

这种方差称为“返回型协方差”

我们没有计划在C#中实施这种差异


通过使用具有适当约束的泛型,可以实现所需的功能,如以下所示:

abstract class Animal
{
  public Leg GetLeg() {...}
}

abstract class Leg { }

class Dog : Animal
{
  public override DogLeg Leg() {...}
}

class DogLeg : Leg { }
abstract class Animal<LegType> where LegType : Leg
{
    public abstract LegType GetLeg();
}

abstract class Leg { }

class Dog : Animal<DogLeg>
{
    public override DogLeg GetLeg()
    {
        return new DogLeg();
    }
}

class DogLeg : Leg { }
抽象类动物,其中LegType:Leg
{
公共抽象LegType GetLeg();
}
抽象类Leg{}
狗类:动物
{
公共优先权
abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

class Dog : IAnimal
{
    public override DogLeg GetLeg() { /* */ }

    Leg IAnimal.GetLeg() { return GetLeg(); }
}