简体   繁体   English

为什么继承不以我认为它应该工作的方式工作?

[英]Why doesn't inheritance work the way I think it should work?

I'm having some inheritance issues as I've got a group of inter-related abstract classes that need to all be overridden together to create a client implementation. 我有一些继承问题,因为我有一组相互关联的抽象类,需要全部重写以创建客户端实现。 Ideally I would like to do something like the following: 理想情况下,我想做类似以下的事情:

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

abstract class Leg { }

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

class DogLeg : Leg { }

This would allow anyone using the Dog class to automatically get DogLegs and anyone using the Animal class to get Legs. 这将允许任何使用Dog类的人自动获取DogLegs以及任何使用Animal类获取Legs的人。 The problem is that the overridden function has to have the same type as the base class so this will not compile. 问题是被覆盖的函数必须与基类具有相同的类型,因此不会编译。 I don't see why it shouldn't though, since DogLeg is implicitly castable to Leg. 我不明白为什么它不应该,因为DogLeg可以隐式地施放到Leg。 I know there are plenty of ways around this, but I'm more curious why this isn't possible/implemented in C#. 我知道有很多方法可以解决这个问题,但我更好奇为什么在C#中无法实现/实现这一点。

EDIT : I modified this somewhat, since I'm actually using properties instead of functions in my code. 编辑 :我有点修改,因为我实际上在我的代码中使用属性而不是函数。

EDIT : I changed it back to functions, because the answer only applies to that situation (covariance on the value parameter of a property's set function shouldn't work). 编辑 :我将其更改回函数,因为答案仅适用于那种情况(属性的set函数的value参数的协方差不应该起作用)。 Sorry for the fluctuations! 对不起波动! I realize it makes a lot of the answers seem irrelevant. 我意识到这使得许多答案看起来无关紧要。

The short answer is that GetLeg is invariant in its return type. 简短的回答是GetLeg的返回类型是不变的。 The long answer can be found here: Covariance and contravariance 可以在这里找到答案很长的答案: 协方差和逆变

I'd like to add that while inheritance is usually the first abstraction tool that most developers pull out of their toolbox, it is almost always possible to use composition instead. 我想补充一点,虽然继承通常是大多数开发人员从他们的工具箱中取出的第一个抽象工具,但几乎总是可以使用组合。 Composition is slightly more work for the API developer, but makes the API more useful for its consumers. 对于API开发人员来说,组合工作稍微多一些,但是使API对其消费者更有用。

很明显,如果你在破损的DogLeg上操作,你需要一个演员表。

Dog should return a Leg not a DogLeg as the return type. 狗应该返回Leg而不是DogLeg作为返回类型。 The actual class may be a DogLeg, but the point is to decouple so the user of Dog doesn't have to know about DogLegs, they only need to know about Legs. 实际的课程可能是DogLeg,但关键是要解耦,所以Dog的用户不必了解DogLegs,他们只需要了解Legs。

Change: 更改:

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

to: 至:

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

Don't Do this: 不要这样做:

 if(a instanceof Dog){
       DogLeg dl = (DogLeg)a.GetLeg();

it defeats the purpose of programing to the abstract type. 它违背了编程为抽象类型的目的。

The reason to hide DogLeg is because the GetLeg function in the abstract class returns an Abstract Leg. 隐藏DogLeg的原因是因为抽象类中的GetLeg函数返回一个抽象的Leg。 If you are overriding the GetLeg you must return a Leg. 如果你要覆盖GetLeg,你必须返回一条腿。 Thats the point of having a method in an abstract class. 这就是在抽象类中使用方法的重点。 To propagate that method to it's childern. 将该方法传播给它的孩子们。 If you want the users of the Dog to know about DogLegs make a method called GetDogLeg and return a DogLeg. 如果您希望Dog的用户了解DogLegs,请创建一个名为GetDogLeg的方法并返回DogLeg。

If you COULD do as the question asker wants to, then every user of Animal would need to know about ALL animals. 如果您可以像提问者那样做,那么Animal的每个用户都需要了解所有动物。

It is a perfectly valid desire to have the signature an overriding method have a return type that is a subtype of the return type in the overridden method ( phew ). 完全有效的愿望是让签名覆盖方法具有返回类型,该类型是重写方法( phew )中返回类型的子类型。 After all, they are run-time type compatible. 毕竟,它们与运行时类型兼容。

But C# does not yet support "covariant return types" in overridden methods (unlike C++ [1998] & Java [2004]). 但是C#在重写方法中还不支持“协变返回类型”(与C ++ [1998]和Java [2004]不同)。

You'll need to work around and make do for the foreseeable future, as Eric Lippert stated in his blog [June 19, 2008]: 正如Eric Lippert在他的博客 [2008年6月19日]中所述,你需要在可预见的未来努力工作并做好准备:

That kind of variance is called "return type covariance". 这种方差称为“返回类型协方差”。

we have no plans to implement that kind of variance in C#. 我们没有计划在C#中实现这种差异。

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

Do it like this, then you can leverage the abstraction in your client: 这样做,然后你可以利用客户端的抽象:

Leg myleg = myDog.GetLeg();

Then if you need to, you can cast it: 然后,如果你需要,你可以施展它:

if (myleg is DogLeg) { ((DogLeg)myLeg).Hump()); }

Totally contrived, but the point is so you can do this: 完全做作,但关键是你可以做到这一点:

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

While still retaining the ability to have a special Hump method on Doglegs. 同时仍保留在Doglegs上使用特殊驼峰方法的能力。

You can use generics and interfaces to implement that in C#: 您可以使用泛型和接口在C#中实现它:

abstract class Leg { }

interface IAnimal { Leg GetLeg(); }

abstract class Animal<TLeg> : IAnimal where TLeg : Leg
 { public abstract TLeg GetLeg();
   Leg IAnimal.GetLeg() { return this.GetLeg(); }
 }

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

GetLeg() must return Leg to be an override. GetLeg()必须返回Leg作为覆盖。 Your Dog class however, can still return DogLeg objects since they are a child class of Leg. 但是,您的Dog类仍然可以返回DogLeg对象,因为它们是Leg的子类。 clients can then cast and operate on them as doglegs. 然后客户可以像doglegs一样投射和操作它们。

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

Not that it is much use, but it is maybe interesting to note the Java does support covariant returns, and so this would work exactly how you hoped. 并不是说它有多大用处,但注意Java确实支持协变返回可能很有意思,因此这将完全符合您的预期。 Except obviously that Java doesn't have properties ;) 显然Java没有属性;)

Perhaps it's easier to see the problem with an example: 也许通过示例更容易看到问题:

Animal dog = new Dog();
dog.SetLeg(new CatLeg());

Now that should compile if you're Dog compiled, but we probably don't want such a mutant. 现在应该编译如果你编译了Dog,但我们可能不想要这样的突变体。

A related issue is should Dog[] be an Animal[], or IList<Dog> an IList<Animal>? 一个相关的问题是Dog []应该是Animal [],还是IList <Dog> IList <Animal>?

C# has explicit interface implementations to address just this issue: C#有明确的接口实现来解决这个问题:

abstract class Leg { }
class DogLeg : Leg { }

interface IAnimal
{
    Leg GetLeg();
}

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

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

If you have a Dog through a reference of type Dog, then calling GetLeg() will return a DogLeg. 如果您通过Dog类型的引用拥有Dog,则调用GetLeg()将返回DogLeg。 If you have the same object, but the reference is of type IAnimal, then it will return a Leg. 如果您具有相同的对象,但引用的类型为IAnimal,则它将返回Leg。

Right, I understand that I can just cast, but that means the client has to know that Dogs have DogLegs. 是的,我知道我可以投,但这意味着客户必须知道Dogs有DogLegs。 What I'm wondering is if there are technical reasons why this isn't possible, given that an implicit conversion exists. 我想知道的是,如果存在技术原因,为什么这是不可能的,因为存在隐式转换。

@Brian Leahy Obviously if you are only operating on it as a Leg there is no need or reason to cast. @Brian Leahy显然如果你只是作为一个腿进行操作,就没有必要或没有理由去施展。 But if there is some DogLeg or Dog specific behavior, there are sometimes reasons that the cast is neccessary. 但是如果有一些DogLeg或Dog特定的行为,有时候有必要进行演员表演。

@Luke @Luke

I think your perhaps misunderstanding inheritance. 我想你或许会误解遗产。 Dog.GetLeg() will return a DogLeg object. Dog.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();

the actual method called will be Dog.GetLeg(); 调用的实际方法是Dog.GetLeg(); and DogLeg.Kick() (I'm assuming a method Leg.kick() exists) there for, the declared return type being DogLeg is unneccessary, because that is what returned, even if the return type for Dog.GetLeg() is Leg. 和DogLeg.Kick()(我假设一个方法Leg.kick()存在)那里,声明的返回类型是DogLeg是不必要的,因为这是返回的,即使Dog.GetLeg()的返回类型是腿。

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

The important thing to remember is that you can use a derived type every place you use the base type (you can pass Dog to any method/property/field/variable that expects Animal) 要记住的重要一点是,您可以在每个使用基类型的地方使用派生类型(您可以将Dog传递给任何期望Animal的方法/属性/字段/变量)

Let's take this function: 我们来看看这个功能:

public void AddLeg(Animal a)
{
   a.Leg = new Leg();
}

A perfectly valid function, now let's call the function like that: 一个完全有效的函数,现在让我们调用这样的函数:

AddLeg(new Dog());

If the property Dog.Leg isn't of type Leg the AddLeg function suddenly contains an error and cannot be compiled. 如果属性Dog.Leg不是Leg类型,则AddLeg函数突然包含错误,无法编译。

You can achieve what you want by using a generic with an appropriate constraint, like the following: 您可以通过使用具有适当约束的泛型来实现您想要的效果,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM