c#中的多态性是如此多态吗?
本文关键字:多态 多态性 | 更新日期: 2023-09-27 18:11:23
我想知道,c#是如何多态的?
如果我们根据这个词的定义:多态性 (…在面向对象编程的上下文中,多态性是指创建具有多种形式的变量、函数或对象的能力……),当然我可以提供更多的多态性的决定,但我认为平均而言,我们所有人都理解什么是计算机科学中的多态性。
那么,关于我的问题,我与c#有关。
例如,我使用非常简单明了的例子,这是一个类A
中的标准虚拟方法和另一个类B
中的重写方法:
using System;
class A
{
public virtual void CallMe(string incString)
{
Console.WriteLine(incString);
}
}
class B : A
{
public override void CallMe(string incString)
{
incString += "_new";
Console.WriteLine(incString);
}
}
class App
{
static void Main()
{
B objectB = new B();
objectB.CallMe("hello");
}
}
首先,我很沮丧:为什么c#现在允许重写返回类型和方法的参数?
例如,只需设置:
public override int CallMe(string incString)
但是你不会得到好的结果,你会捕获一个编译器异常:
B.CallMe(string)': return type must be `void' to match overridden member
对于我所描述的情况,如果我想定义一个具有类似名称但不同返回类型/参数列表的方法,我们只能通过使用关键字new
隐藏方法来解决它。我想承认,如果我们隐藏它,它根本就不是多态性,它只是一个隐藏的过程。多态性的定义需要覆盖现有的东西(类似的过程可以在其他科学中找到,如化学,数学等…)
例如,数学允许使用多态的第2,第3…任意顺序,其中存在的集合或对象总是可以重新定义的。但是计算机科学和c#究竟有什么用呢?
我读过,但没有尝试过,一些函数式编程语言(LISP, Haskell)确实允许这样的特性。
是否有可能在未来的c#中看到,因为我看到c#的每个新版本都在增加它的功能,越来越多,也许它将与lambda/委托一起成为可能?
你觉得怎么样?
面向对象编程中多态性的意义在于,您可以拥有不同的类,其方法执行不同的操作,但您以相同的方式调用它们。因此,重写是用完全相同的方法签名完成的,因为如果您要更改签名,那么您就不能以相同的方式使用这些方法。
由于方法签名保持不变,您可以使用对基类(或接口)的引用的代码,以及可以是实现该类型的任何类的实际实例。代码可以进行相同的调用,而不必关心实际类型是什么。
的例子:
A obj;
if (new Random().Next(2) == 0) {
obj = new A();
} else {
obj = new B();
}
obj.CallMe("hello");
当您创建virtual/abstract
方法时,您正在定义基类和所有可能的未来派生类之间的契约。派生类需要通过提供包含virtual/abstract
方法所暗示的确切签名的方法来"同意"这个契约。
如果你打破了这个契约,你认为多态性会如何工作?这根本行不通!
考虑interface
,您可以像IEnumerable
一样实现它来枚举一个集合。
From MSDN
它包含一个方法bool MoveNext()
,如果你可以把签名更改为任何你喜欢的东西,你会破坏整个框架,因为foreach
不能依赖你的对象来提供一个合法的MoveNext()
-方法。
再次来自MSDN
重写方法提供从基类继承的成员的新实现。被覆盖声明覆盖的方法称为覆盖基方法。重写的基方法必须具有与重写方法相同的签名。有关继承的信息,请参见继承(c#编程指南)。
我想你把重写和重载搞混了。当您希望为具有相同签名的方法提供不同的行为时,重写是适用的。
另一方面,重载是一种使方法具有相同的名称,但签名不同的方法。我想,把这两个概念混在一起会很糟糕。
我希望,不,这是不可能的。至少因为这将是一个突破性的变化。是否有可能在未来的c#中看到
乌利希期刊指南。
让我们想想,你想要的是可能的。看看这些类:
class A
{
public virtual int CallMe(string incString)
{
// some code
}
}
class B : A
{
public override void CallMe(string incString)
{
// some code
}
}
// somewhere in the code (e.g., host application, which loads a plugin)
var a = serviceLocator.GetService<A>(); // indeed returns `B` instance
var result = a.CallMe(); // hey, stop! B.CallMe is void. WTF?
如果在运行时接收到的不是A
,而是B
,那么只知道A
的代码应该如何表现?
多态原理
计算机科学中的许多主题都需要一定的严谨性。多态性也不例外。共享返回类型的要求是一个重要的要求,它允许做出许多节省时间和强大的假设。一个没有相同输出的方法不是多态的(其他语言滥用这个过程可能是为什么他们不使用lambda或c#具有的其他一些很好的推断特性)。
目前,无法根据返回类型推断调用哪个方法,因为The signature of a method does not include the return type.
坚持最佳实践
使用真正的多态原理编写可读的代码是最好的。
你可以看到下面的过程如何变得难以阅读。一个更好的方法是Separate Concerns(在compsci中的另一个大主题)。这个int,或者这些返回值到底做了什么。它们很可能代表了逻辑上的一个分支,应该用不同的签名来定义,以反映这些情况下逻辑的不同。
编译器无法推断返回类型
如果有两个具有相同签名但返回不同类型的定义,则无法区分应该使用哪个定义,编译器将抛出"成员已定义为具有相同参数类型"的错误。
下面的例子说明了为什么使用不同的返回类型可能是不好的。让我们假设这将编译
public override string CallMe(string incString)
{
incString += "_new";
return incString;
}
public override int CallMe(string incString)
{
incString += "_new";
return 1;
}
编译器如何知道在推理的情况下使用哪一个?
object o = CallMe("hello");
var v = CallMe("hello");
Type t = CallMe("hello").GetType();
泛型可以这样做
泛型是c#的一个强大工具。对于您的示例,使用泛型很容易解决这个问题。然而,这将需要切换类型,这有时会受到批评。
class A{public virtual void CallMe(string incString){Console.WriteLine(incString);}}
class B : A
{
public override void CallMe(string incString)
{
incString += "_new";
Console.WriteLine(incString);
}
public T CallMe<T>(string incString)
{
CallMe(incString);
object o;
switch( typeof(T).ToString() ){
case "System.String":
o = incString;
break;
case "System.Int32":
o = 1;
break;
default:
o = Activator.CreateInstance<T>();
break;
}
return (T)o;
}
}
可以这样使用:
static void Main()
{
B objectB = new B();
objectB.CallMe("hello");//writes "hello_new"
int i = objectB.CallMe<int>("generic");//writes "generic_new"
string s = objectB.CallMe<string>("world");//writes "world_new"
A a = objectB.CallMe<A>("!");//writes "!_new"
Console.WriteLine(i);//1
Console.WriteLine(s);//"world"
Console.WriteLine(a);//new A()
}
底线
c#是非常多态的,但重要的是要避免进入对每个人都很困难的代码,并有一个结构化的方法。具有可预测的独特签名,以便可以对所期望的内容做出更有力的假设。很明显,这段代码已经开始有太多的横切关注点,因为它有太多的责任,迫切需要重构。
在我看来,c#是最好的编程语言;这在很大程度上要归功于它在执行最佳实践方面的精心设计。
规则方面,您的案例并不是那么牵强,因为从概念上讲,返回一些东西而不是什么都不返回不会破坏用例…在概念上。
然而,有几个不同的因素在起作用。首先,c#的编程方式通常是将方法的有用输出作为返回值返回。如您所建议的那样,通过使用多态性将void
更改为string
,您将强迫基类的用户忽略它具有有意义的输出,这充其量是糟糕设计的标志。
也就是说,这种情况可能有点极端,并且涉及返回不同大小的类型(这也可能导致只能通过使整个语言变慢来解决的技术问题)。这个概念有一些更温和的实现方法可以使用。可以让重写接受更宽松的类型,如object
而不是string
,或者返回更精确的类型,如B
而不是第一个示例中的A
;这叫做协方差和逆变。这些不会引起技术问题,因为引用的大小都是一样的。我也相信这在Java中是可能的,尽管我需要检查一下。我确实知道一个事实,在Objective-C中,对于返回或接受Objective-C类型的块是可能的。
据我所知,它还没有实现c#方法覆盖,可能是因为微软认为开发、测试和维护该特性的成本超过了收益。
作为旁注,已经使它成为泛型参数。当声明泛型类型时,可以用out
注释该类型,这意味着"这个或一个子类"。例如,IEnumerable定义为IEnumerable<out T>
;这意味着任何IEnumerable<string>
也是IEnumerable<object>
。