为什么继承不能按照我认为应该的方式工作?
我遇到了一些继承问题,因为我有一组相互关联的抽象类,需要一起重写它们才能创建客户端实现。 理想情况下,我想做类似以下的事情:
abstract class Animal
{
public Leg GetLeg() {...}
}
abstract class Leg { }
class Dog : Animal
{
public override DogLeg Leg() {...}
}
class DogLeg : Leg { }
这将允许任何使用 Dog 类的人自动获取 DogLegs,并允许任何使用 Animal 类的人自动获取 Legs。 问题是重写的函数必须与基类具有相同的类型,因此无法编译。 我不明白为什么不应该这样做,因为 DogLeg 可以隐式转换为 Leg。 我知道有很多方法可以解决这个问题,但我更好奇为什么这在 C# 中不可能/实现。
编辑:我对此进行了一些修改,因为我实际上在代码中使用属性而不是函数。
编辑:我将其改回函数,因为答案仅适用于这种情况(属性设置函数的值参数的协方差不应该起作用)。 对于波动,敬请谅解! 我意识到这让很多答案显得无关紧要。
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. 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. I know there are plenty of ways around this, but I'm more curious why this isn't possible/implemented in 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). Sorry for the fluctuations! I realize it makes a lot of the answers seem irrelevant.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
您可以通过使用具有适当约束的泛型来实现您想要的目标,如下所示:
You can achieve what you want by using a generic with an appropriate constraint, like the following:
@Luke
我认为你可能误解了继承。 Dog.GetLeg() 将返回一个 DogLeg 对象。
实际调用的方法是 Dog.GetLeg(); 和 DogLeg.Kick() (我假设存在方法 Leg.kick()),声明的返回类型为 DogLeg 是不必要的,因为这就是返回的内容,即使 Dog.GetLeg() 的返回类型是腿。
@Luke
I think your perhaps misunderstanding inheritance. Dog.GetLeg() will return a DogLeg object.
the actual method called will be 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.
是的,我知道我可以直接投射,但这意味着客户必须知道 Dogs 有 DogLegs。 我想知道是否存在技术原因导致这不可能,因为存在隐式转换。
Right, I understand that I can just cast, but that means the client has to know that Dogs have DogLegs. What I'm wondering is if there are technical reasons why this isn't possible, given that an implicit conversion exists.
简短的回答是 GetLeg 的返回类型是不变的。 长答案可以在这里找到: 协变和逆变
我想补充一点,虽然继承通常是大多数开发人员从他们的工具箱中取出的第一个抽象工具,但几乎总是可以使用组合来代替。 对于 API 开发人员来说,组合的工作量稍微多一些,但使 API 对于消费者来说更有用。
The short answer is that GetLeg is invariant in its return type. 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.
C# 有显式接口实现来解决这个问题:
如果通过 Dog 类型的引用获得了 Dog,则调用 GetLeg() 将返回 DogLeg。 如果您有相同的对象,但引用的类型为 IAnimal,那么它将返回一个 Leg。
C# has explicit interface implementations to address just this issue:
If you have a Dog through a reference of type Dog, then calling GetLeg() will return a DogLeg. If you have the same object, but the reference is of type IAnimal, then it will return a Leg.
也许通过一个例子更容易看出问题:
现在,如果你是 Dog 编译的,那么应该可以编译,但我们可能不想要这样的突变体。
一个相关的问题是 Dog[] 应该是 Animal[] 或 IList吗? IList?
Perhaps it's easier to see the problem with an example:
Now that should compile if you're Dog compiled, but we probably don't want such a mutant.
A related issue is should Dog[] be an Animal[], or IList<Dog> an IList<Animal>?
http://en.wikipedia.org/wiki 描述了导致您出现问题的概念/Covariance_and_contravariance_(计算机科学)
The concept that is causing you problems is described at http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
并不是说它有多大用处,但有趣的是,Java 确实支持协变返回,因此这将完全按照您希望的方式工作。 显然 Java 没有属性;)
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. Except obviously that Java doesn't have properties ;)
Dog 应该返回 Leg 而不是 DogLeg 作为返回类型。 实际的类可能是 DogLeg,但重点是解耦,因此 Dog 的用户不必了解 DogLegs,他们只需要了解 Legs。
更改
为
:不要这样做:
它违背了对抽象类型进行编程的目的。
隐藏DogLeg的原因是因为抽象类中的GetLeg函数返回一个Abstract Leg。 如果您要重写 GetLeg,则必须返回一个 Leg。 这就是在抽象类中拥有方法的要点。 将该方法传播给它的子方法。 如果您希望 Dog 的用户了解 DogLeg,请创建一个名为 GetDogLeg 的方法并返回 DogLeg。
如果你可以按照提问者想要的那样做,那么动物的每个用户都需要了解所有动物。
Dog should return a Leg not a DogLeg as the return type. 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.
Change:
to:
Don't Do this:
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. If you are overriding the GetLeg you must return a Leg. 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.
If you COULD do as the question asker wants to, then every user of Animal would need to know about ALL animals.
让重写方法的签名具有作为被重写方法中返回类型的子类型的返回类型是完全合理的愿望(唷)。 毕竟,它们是运行时类型兼容的。
但 C# 尚不支持重写方法中的“协变返回类型”(与 C++ [1998] 和 Java [2004] 不同)。
正如 Eric Lippert 在 他的博客
[2008 年 6 月 19 日]:
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). 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]).
You'll need to work around and make do for the foreseeable future, as Eric Lippert stated in his blog
[June 19, 2008]:
这样做,然后你可以在你的客户端中利用抽象:
然后如果你需要,你可以投射它:
完全人为的,但重点是你可以这样做:
同时仍然保留拥有特殊驼峰方法的能力在狗腿上。
Do it like this, then you can leverage the abstraction in your client:
Then if you need to, you can cast it:
Totally contrived, but the point is so you can do this:
While still retaining the ability to have a special Hump method on Doglegs.
您可以使用泛型和接口在 C# 中实现它:
You can use generics and interfaces to implement that in C#:
GetLeg() 必须返回 Leg 才能覆盖。 然而,您的 Dog 类仍然可以返回 DogLeg 对象,因为它们是 Leg 的子类。 然后,客户可以将它们作为狗腿进行铸造和操作。
GetLeg() must return Leg to be an override. Your Dog class however, can still return DogLeg objects since they are a child class of Leg. clients can then cast and operate on them as doglegs.
显然,如果你是
在破损的狗腿上进行手术。
Clearly, you'll need a cast if you're
operating on a broken DogLeg.
要记住的重要一点是,您可以在使用基类型的每个地方使用派生类型(您可以将 Dog 传递给任何需要 Animal 的方法/属性/字段/变量)
让我们看一下这个函数:
一个完全有效的函数,现在让我们像这样调用函数:
如果属性 Dog.Leg 不是 Leg 类型,则 AddLeg 函数会突然包含错误并且无法编译。
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)
Let's take this function:
A perfectly valid function, now let's call the function like that:
If the property Dog.Leg isn't of type Leg the AddLeg function suddenly contains an error and cannot be compiled.
您还可以返回 Leg 和/或 DogLeg 实现的接口 ILeg。
You could also return the interface ILeg that both Leg and/or DogLeg implement.
@布莱恩·莱希
显然,如果您仅将其作为腿进行操作,则无需或没有理由施放。 但如果存在一些 DogLeg 或 Dog 的特定行为,有时就有必要进行强制转换。
@Brian Leahy
Obviously if you are only operating on it as a Leg there is no need or reason to cast. But if there is some DogLeg or Dog specific behavior, there are sometimes reasons that the cast is neccessary.