3

我想知道,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对于我所描述的情况,如果我想定义一个名称相似但返回类型/参数列表不同的方法,我们只能在 C# 中通过使用关键字隐藏方法来解决它。我想承认,如果我们隐藏它,它根本就不是多态,它只是一个隐藏过程。定义中的多态性需要覆盖现有的东西(类似的过程可以在其他科学科学中找到,如化学、数学等......)

例如,数学允许使用第二、第三... N-arny 顺序的多态性,其中存在的集合或对象总是可以重新定义。但是计算机科学和 C# 到底是什么?

我读过,但没有尝试过,一些函数式编程语言(LISP、Haskell)确实允许这样的特性。

将来是否有可能在 C# 中看到,因为正如我所见,每个新版本的 C# 都在增加其功能性内容,越来越多,也许 lambdas/delegates 可能会实现?

你怎么看待这件事?

4

5 回答 5

2

我认为,您将覆盖重载混淆了。当您想为具有相同签名的方法提供不同的行为时,覆盖是适用的。

另一方面,重载是一种使方法具有相同名称但签名不同的方法。我认为,混合这两个概念将是一个地狱。

将来是否有可能在 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应该如何表现,而不是A在运行时接收B

于 2013-04-30T08:25:48.257 回答
2

当你创建一个virtual/abstract方法时,你定义了一个基类和所有未来可能的派生类之间的契约。virtual/abstract派生类需要通过提供包含作为隐含方法的确切签名的方法来“同意”此协定。
如果你打破了那个契约,你认为多态性将如何工作?它根本行不通!
考虑interfaces ,您可以IEnumerable通过集合进行枚举。

来自MSDN

它包含一个方法bool MoveNext(),如果您可以将签名更改为您喜欢的任何内容,那么您将破坏整个框架,因为foreach无法依赖您的对象来提供合法的MoveNext()方法。

再次来自MSDN

覆盖方法提供了从基类继承的成员的新实现。被覆盖声明覆盖的方法称为被覆盖的基方法。被覆盖的基方法必须与覆盖方法具有相同的签名。有关继承的信息,请参阅继承(C# 编程指南)

于 2013-04-30T08:26:08.293 回答
2

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

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

例子:

A obj;
if (new Random().Next(2) == 0) {
  obj = new A();
} else {
  obj = new B();
}
obj.CallMe("hello");
于 2013-04-30T08:33:03.850 回答
1

从规则上讲,您的案例并不是那么牵强,因为从概念上讲,返回一些东西而不是什么都不会破坏用例......从概念上讲。

然而,有几个不同的因素在起作用。首先,C# 的编程方式通常是将方法的有用输出作为返回值返回。正如您所建议的那样,通过使用多态性将 a 更改void为 a string,您将强迫基类的用户忽略它具有有意义的输出,这充其量是糟糕设计的标志。

也就是说,这种情况可能有点极端,并且涉及返回不同大小的类型(这也可能导致只能通过使整个语言变慢才能解决的技术问题)。可以使用该概念的较温和的实现。可以让覆盖接受更宽松的类型,例如object代替string,或者返回更精确的类型,例如B代替A您的第一个示例;这就是所谓的协变和逆变。这些不会引起技术问题,因为参考都是相同的大小。我也相信这在 Java 中是可能的,尽管我需要检查一下。我确实知道一个事实,即在 Objective-C 中返回或接受 Objective-C 类型的块是可能的。

据我所知,它还没有实现 C# 方法覆盖,可能是因为微软认为开发、测试和维护该功能的成本超过了收益。

附带说明一下,它已经成为通用论点。当你声明一个泛型类型时,可以用 注释该类型out,这意味着“这个或一个子类”。例如, IEnumerable 定义为IEnumerable<out T>; 这意味着 anyIEnumerable<string>也是一个IEnumerable<object>.

于 2013-04-30T18:22:14.260 回答
1

多态原则

计算机科学中的许多主题都需要一定的严谨性。多态性不是例外之一。共享回报类型的要求是一项重要的要求,它可以节省许多时间和做出强有力的假设。不具有相同输出的方法不是多态的(其他语言滥用此过程可能是他们无法使用 lambda 或 C# 具有的其他一些不错的推理功能的原因)。

就目前而言,无法根据返回类型推断要调用哪个方法,因为The signature of a method does not include the return type.


坚持最佳实践

编写采用真正多态原则的可读代码是最好的。

您可以看到以下过程将如何变得难以阅读。更好的方法是分离关注点(Comp Sci 中的另一个大主题)。用这个 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# 是最好的编码语言;这在很大程度上要归功于它在执行最佳实践方面的精心设计。

于 2013-04-30T20:22:24.417 回答