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/委托一起成为可能?

你觉得怎么样?

c#中的多态性是如此多态吗?

面向对象编程中多态性的意义在于,您可以拥有不同的类,其方法执行不同的操作,但您以相同的方式调用它们。因此,重写是用完全相同的方法签名完成的,因为如果您要更改签名,那么您就不能以相同的方式使用这些方法。

由于方法签名保持不变,您可以使用对基类(或接口)的引用的代码,以及可以是实现该类型的任何类的实际实例。代码可以进行相同的调用,而不必关心实际类型是什么。

的例子:

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>