为什么从扩展类中调用扩展方法需要'this'关键字


79

我为ASP.NET MVC ViewPage创建了扩展方法,例如:

public static class ViewExtensions
{
    public static string Method<T>(this ViewPage<T> page) where T : class
    {
        return "something";
    }
}

从视图(从派生ViewPage)中调用此方法时,除非得到使用关键字进行调用的错误,否则会出现错误“ CS0103:名称“方法”在当前上下文中不存在this

<%: Method() %> <!-- gives error CS0103 -->
<%: this.Method() %> <!-- works -->

为什么this需要关键字?还是没有它就可以工作,但是我缺少了什么?

(我认为此问题一定是重复的,但找不到)

更新

正如Ben Robinson所说,调用扩展方法的语法只是编译器糖。那么为什么编译器在不需要this关键字的情况下不能自动检查当前类型的基本类型的扩展方法呢?


@ M4N-这似乎是您追求的可能的重复:为什么我不能在扩展List的类中调用OrderBy?
djdd87

@GenericTypeTea:是的,这是一个类似的问题。但是我不是在问如何调用扩展方法(我知道我必须添加this关键字),而是在问为什么this需要关键字。我更新了我的问题。
M4N

@ M4N-好点,他们说这是必须做的,但是答案并不能真正说明原因。
djdd87

在实例方法上,“ this”被隐式传递给方法。通过调用Method()而不是this.Method()或Method(this),您并没有告诉编译器将什么传递给该方法。
亚历克斯·汉弗莱

@Alex。从技术上讲,我认为编译器可以在调用Method()的上下文中推断“ this”,但是我认为这将是一个错误的设计选择,并使代码的可读性降低。
本·罗宾逊

Answers:


54

几点:

首先,不需要提议的功能(在扩展方法调用上隐式的“ this。”)。扩展方法对于LINQ查询理解以我们想要的方式工作是必不可少的。接收者总是在查询中声明,因此不必支持隐式的使LINQ工作。

其次,此功能更通用的扩展方法设计背道而驰:也就是说,扩展方法允许您扩展无法扩展自己的类型,要么是因为它是接口并且您不知道实现,要么是因为知道实现但没有源代码。

如果您在针对该类型中的类型使用扩展方法的情况,那么您确实有权访问源代码。那么,为什么首先使用扩展方法?如果您可以访问扩展类型的源代码,则 可以自己编写一个实例方法,然后完全不必使用扩展方法!然后,您的实现可以利用对对象的私有状态的访问权限,而扩展方法则不能。

在您可以访问的类型中更轻松地使用扩展方法,鼓励使用扩展方法而不是实例方法。扩展方法很棒,但是如果有实例方法,通常最好使用实例方法。

鉴于这两点,语言设计者不再需要负担解释该功能为何存在的负担。现在由您来解释为什么应该这样做。功能具有巨大的成本。此功能不是必需的,它违背了扩展方法的既定设计目标。我们为什么要承担实施成本?说明此功能启用了哪些引人注目的重要方案,我们将在将来考虑实施。我看不到任何令人信服的重要场景可以证明这一点,但是也许我错过了一个。


11
感谢您的回答!有两点:1:我并不是要求实现该功能,只是出于解释原因,为什么不存在/为什么this需要该功能。2:为什么要从扩展类型调用扩展方法?在给出的示例中,我向基类(ViewPage)添加了一些便捷方法,并从派生类调用它。这消除了为所有视图创建通用基类的需要。顺便说一句:我同意在扩展类型中使用扩展方法很尴尬,但是再次看到带有MVC ViewPage的示例。
M4N

3
@ M4N:这也正是我的情况:我想扩展UserControl方法,并使该扩展名可用于所有UserControl,而无需显式的“ this”。我知道编译器开发不是一个民主国家,但我认为这是对隐式“ this”的投票:-)
Duncan Bayne

8
您好,埃里克(Eric),这不是主要情况。但是,一般模式似乎是1。您没有对基类的源代码编辑权限。2.您想向基类型添加一些(受保护的)方法,您要在派生类的主体中调用这些方法。还有其他机制可以实现这一目标吗?this.MethodName并不是很难键入,但是过了一会儿就会变得烦人……
Gishu 2011年

5
我认为向基类(您没有源代码)添加扩展方法的场景是有效的。在那种情况下,能够不带“ this”调用该方法会很好。顺便说一句,这可以在下一个带有“静态导入”的c#版本中解决。
麦角酸

4
在要扩展的类中使用扩展方法的一个有效原因是当您具有实现接口的多个类型时。例如,您有两个实现的类ICrossProductable,并且在该接口上定义了一个扩展方法,称为GetProduct。非常简单的示例,但是我经常发现这很有用。但是,这并不是所有情况下的最佳方法。
伊恩·纽森

8

没有它,编译器只是将其视为静态类中的静态方法,该静态类将page作为其第一个参数。即

// without 'this'
string s = ViewExtensions.Method(page);

// with 'this'
string s = page.Method();

1
我认为应该是ViewExtensions.Method(page)。
Dave Van den Eynde

6

在实例方法上,“ this”透明地隐式传递给每个方法,因此您可以访问它提供的所有成员。

扩展方法是静态的。通过调用Method()而不是this.Method()or Method(this),您并没有告诉编译器将什么传递给该方法。

您可能会说“为什么不仅仅意识到调用对象是什么并将其作为参数传递?”

答案是扩展方法是静态的,可以从没有“ this”的静态上下文中调用。

我猜他们可以在编译过程中进行检查,但是老实说,这可能需要付出很多工作,而回报却很少。老实说,取消扩展方法调用的某些显式性几乎没有好处。它们可能会误认为实例方法,这意味着它们有时可能非常不直观(例如,不会抛出NullReferenceExceptions)。我有时认为他们应该为扩展方法引入一个新的“管道前移”样式运算符。


因此,似乎他们做出了一个非常糟糕的设计选择,那就是可以从静态上下文中调用它,因为我不知道为什么有人会这么做。
AustinWBryan '18

3

重要的是要注意扩展方法和常规方法之间存在差异。我认为您只是遇到了其中之一。

我将为您提供另一个区别的示例:在null对象引用上调用扩展方法相当容易。幸运的是,使用常规方法很难做到这一点。(但是可以做到的。IIRC,Jon Skeet演示了如何通过处理CIL代码来做到这一点。)

static void ExtensionMethod(this object obj) { ... }

object nullObj = null;
nullObj.ExtensionMethod();  // will succeed without a NullReferenceException!

话虽如此,我同意this调用扩展方法似乎有点不合逻辑。毕竟,一种扩展方法在理想情况下应该“感觉”到并且表现得像普通方法一样。

但是实际上,扩展方法更像是在现有语言之上添加的语法糖,而不是早期的核心功能,它在各个方面都非常适合该语言。


在Boo语言中,您可以直接在null(即null.Do())上调用Extension方法或属性。
谢尔盖·米尔沃达

1
@Sergey:听起来很危险... :)让我很好奇Boo如何知道(暗示的)类型null?几乎可以是任何引用类型,它如何知道可用的方法null
stakx-

1
可能有充分的理由能够使用空引用调用扩展方法。
Dave Van den Eynde

@DaveVandenEynde:恕我直言,.net不为非虚拟方法定义属性是错误的,该属性要求编译器使用非虚拟调用来调用它们。一些不可变的类类型在String逻辑上表现为值,并且当为null时可能具有合理的默认值(例如,.net可以始终将静态类型的null引用string视为空字符串,而不是偶发地这样做)。必须为诸如此类使用静态方法简直if (!String.IsNullOrEmpty(stringVar))是荒谬的。
超级猫

1
@supercat是的,可怕。您也可以执行string.IsNullOrEmpty()。更漂亮。
Dave Van den Eynde 2012年

1

因为ViewPage类不存在扩展方法。您需要告诉编译器正在调用扩展方法的内容。请记住this.Method(),只是的编译器糖ViewExtensions.Method(this)。这与您不能仅通过方法名称在任何类中间调用扩展方法的方式相同。


1
这是有道理的(以某种方式)。但是,如果它是编译器的糖,那为什么编译器在没有this关键字的情况下不能检查扩展方法?
M4N

对我来说似乎是一个疏忽。如您所说-编译器可以看到该方法不存在,然后检查扩展名的可用性。
TomTom

是的,可以说这可行。我怀疑这是使代码更具可读性的特定设计决策。如果您可以简单地在一个类中调用ExtensionMethod(),那么如果该类不包含该方法但使用this.ExtensionMethod(),则可能会使读代码的人有些困惑,至少与使用MyInstance.ExtentionMethod()一致。
本·罗宾逊

我认为this.Method()更像ViewExtensions.Method(this)的语法糖。
亚历克斯·汉弗莱

1

我正在使用流畅的API,但遇到了同样的问题。即使我可以访问要扩展的类,我仍然希望每个流利方法的逻辑都在它们自己的文件中。“ this”关键字非常不直观,用户一直认为缺少该方法。我所做的是使我的类成为实现我需要的方法的部分类,而不是使用扩展方法。我没有在答案中提及偏s。如果您有这个问题,偏s可能是一个更好的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.