Tại sao từ khóa 'this' được yêu cầu để gọi một phương thức mở rộng từ bên trong lớp mở rộng


79

Tôi đã tạo một phương thức mở rộng cho một ASP.NET MVC ViewPage, ví dụ:

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

Khi gọi phương thức này từ Chế độ xem (dẫn xuất từ ViewPage), tôi gặp lỗi " CS0103: Tên 'Phương thức' không tồn tại trong ngữ cảnh hiện tại " trừ khi tôi sử dụng thistừ khóa để gọi nó:

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

Tại sao thistừ khóa được yêu cầu? Hoặc nó hoạt động mà không có nó, nhưng tôi thiếu một cái gì đó?

(Tôi nghĩ rằng phải có một bản sao của câu hỏi này, nhưng tôi không thể tìm thấy một câu hỏi)

Cập nhật :

Như Ben Robinson nói , cú pháp để gọi các phương thức mở rộng chỉ là đường biên dịch. Sau đó, tại sao trình biên dịch không thể tự động kiểm tra các phương thức mở rộng cho các loại cơ sở của loại hiện tại mà không yêu cầu từ khóa this?


@ M4N - Đây có vẻ như là bản sao có thể xảy ra mà bạn đang theo dõi: Tại sao tôi không thể gọi OrderBy trong một lớp mở rộng Danh sách?
djdd87

@GenericTypeTea: vâng, đó là một câu hỏi tương tự. Nhưng tôi không hỏi làm thế nào để gọi phương thức mở rộng (tôi biết tôi phải thêm thistừ khóa), nhưng tôi đang hỏi tại sao thistừ khóa được yêu cầu. Tôi đã cập nhật câu hỏi của mình.
M4N

@ M4N - Điểm tốt, họ nói rằng điều đó phải được thực hiện, nhưng câu trả lời không thực sự đưa ra lời giải thích đủ tốt về lý do tại sao.
djdd87

Trên các phương thức instance, 'this' được truyền ngầm cho phương thức. Bằng cách gọi Method () chứ không phải this.Method () hoặc Method (this), bạn không nói với trình biên dịch những gì cần truyền cho phương thức.
Alex Humphrey

@Alex. Về mặt kỹ thuật, tôi tưởng tượng rằng trình biên dịch có thể suy ra "điều này" trong ngữ cảnh gọi Method (), tuy nhiên tôi nghĩ rằng đây sẽ là một lựa chọn thiết kế tồi và làm cho mã khó đọc hơn.
Ben Robinson

Câu trả lời:


55

Một vài điểm:

Trước hết, tính năng được đề xuất (ngầm định là "this." Trên một cuộc gọi phương thức mở rộng) là không cần thiết . Các phương pháp mở rộng là cần thiết để hiểu truy vấn LINQ hoạt động theo cách chúng tôi muốn; bộ thu luôn được nêu trong truy vấn vì vậy không cần thiết phải hỗ trợ điều này ngầm định để làm cho LINQ hoạt động.

Thứ hai, tính năng này hoạt động chống lại thiết kế chung hơn của các phương thức mở rộng: cụ thể là, các phương thức mở rộng cho phép bạn mở rộng một loại mà bạn không thể tự mở rộng , vì đó là một giao diện và bạn không biết cách triển khai hoặc vì bạn làm biết thực hiện nhưng không có mã nguồn.

Nếu bạn đang ở trong kịch bản mà bạn đang sử dụng một phương pháp mở rộng cho một loại trong kiểu đó thì bạn làm có quyền truy cập vào mã nguồn. Tại sao bạn lại sử dụng phương pháp mở rộng ngay từ đầu? Bạn có thể tự viết một phương thức phiên bản nếu bạn có quyền truy cập vào mã nguồn của loại mở rộng và sau đó bạn không phải sử dụng một phương thức mở rộng nào cả! Sau đó, việc triển khai của bạn có thể tận dụng lợi thế của việc có quyền truy cập vào trạng thái riêng tư của đối tượng, điều mà các phương thức mở rộng không thể.

Việc làm cho việc sử dụng các phương thức tiện ích mở rộng từ bên trong một loại mà bạn có quyền truy cập trở nên dễ dàng hơn đang khuyến khích việc sử dụng các phương thức tiện ích mở rộng thay vì các phương thức phiên bản. Các phương thức mở rộng là rất tốt, nhưng thường tốt hơn nếu bạn có một phương thức thể hiện.

Với hai điểm đó, nhà thiết kế ngôn ngữ không còn gánh nặng giải thích tại sao tính năng không tồn tại. Bây giờ bạn phải giải thích tại sao nó phải như vậy . Các tính năng có chi phí rất lớn liên quan đến chúng. Tính năng này không cần thiết và hoạt động chống lại các mục tiêu thiết kế đã nêu của các phương pháp mở rộng; tại sao chúng ta phải chịu chi phí thực hiện nó? Giải thích tình huống hấp dẫn, quan trọng nào được kích hoạt bởi tính năng này và chúng tôi sẽ xem xét triển khai nó trong tương lai. Tôi không thấy bất kỳ kịch bản hấp dẫn, quan trọng nào biện minh cho điều đó, nhưng có lẽ có một kịch bản mà tôi đã bỏ qua.


11
Cảm ơn câu trả lời đó! Hai điểm: 1: Tôi không yêu cầu tính năng đó được triển khai, chỉ để giải thích, tại sao nó không có / tại sao thiscần phải có. 2: Tại sao tôi gọi một phương thức mở rộng từ kiểu mở rộng? Trong ví dụ đã cho, tôi đang thêm một số phương thức tiện lợi vào lớp cơ sở (ViewPage) và tôi đang gọi nó từ các lớp dẫn xuất. Điều này loại bỏ nhu cầu tạo một lớp cơ sở chung cho tất cả các chế độ xem của tôi. BTW: Tôi đồng ý rằng việc sử dụng một phương thức mở rộng từ bên trong kiểu mở rộng là một điều bất tiện, nhưng hãy xem lại ví dụ với MVC ViewPage.
M4N

3
@ M4N: đó chính xác là tình huống của tôi: Tôi muốn mở rộng phương thức UserControl và có tiện ích mở rộng đó cho tất cả các UserControls của tôi, không có 'this' rõ ràng. Tôi biết rằng sự phát triển trình biên dịch không phải là một nền dân chủ nhưng hãy cân nhắc bỏ phiếu của tôi cho một tiềm ẩn 'này' :-)
Duncan Bayne

8
Xin chào Eric, đây không phải là tình huống chính. Tuy nhiên, mô hình chung dường như là 1. Bạn không có quyền truy cập sửa mã nguồn đối với một lớp cơ sở. 2. bạn muốn thêm một số phương thức (được bảo vệ) vào kiểu cơ sở, mà bạn muốn gọi trong phần thân của các lớp dẫn xuất. Có một số cơ chế khác để đạt được điều này? this.MethodName không phải là khó để gõ nhưng nó được gây phiền nhiễu sau một thời gian ...
Gishu

5
Tôi nghĩ rằng kịch bản thêm một phương thức mở rộng vào một lớp cơ sở (mà bạn không có nguồn) là một kịch bản hợp lệ. Trong trường hợp đó, thật tuyệt nếu có thể gọi phương thức mà không có "this". BTW điều này có thể được giải quyết trong phiên bản c # tiếp theo với "nhập tĩnh".
axit lysergic-

4
Một lý do hợp lệ để sử dụng các phương thức mở rộng từ bên trong lớp bạn đang mở rộng là khi bạn có nhiều loại triển khai một giao diện. Ví dụ: bạn có hai lớp triển khai ICrossProductablevà bạn xác định một phương thức mở rộng trên giao diện đó được gọi GetProduct. Ví dụ rất đơn giản, nhưng tôi thường thấy điều này hữu ích. Tuy nhiên, đây không phải là cách tốt nhất trong mọi trường hợp.
Ian Newson

9

Không có nó, trình biên dịch chỉ xem nó như một phương thức tĩnh trong một lớp tĩnh lấy trang làm tham số đầu tiên. I E

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

vs.

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

1
Tôi nghĩ đó phải là ViewExtensions.Method (trang).
Dave Van den Eynde

6

Trên các phương thức instance, 'this' được truyền ngầm cho từng phương thức một cách minh bạch, vì vậy bạn có thể truy cập tất cả các thành viên mà nó cung cấp.

Các phương thức mở rộng là tĩnh. Bằng cách gọi Method()thay vì this.Method()hoặc Method(this), bạn không nói với trình biên dịch những gì cần truyền cho phương thức.

Bạn có thể nói 'tại sao nó không nhận ra đối tượng đang gọi là gì và chuyển nó làm tham số?'

Câu trả lời là các phương thức mở rộng là tĩnh và có thể được gọi từ ngữ cảnh tĩnh, nơi không có 'cái này'.

Tôi đoán họ có thể kiểm tra điều đó trong quá trình biên dịch, nhưng thành thật mà nói, có lẽ rất nhiều việc phải làm với số tiền cực kỳ ít. Và thành thật mà nói, tôi thấy ít lợi ích trong việc loại bỏ một số tính rõ ràng của các cuộc gọi phương thức mở rộng. Thực tế là chúng có thể bị nhầm lẫn với các phương thức cá thể có nghĩa là chúng có thể khá khó trực quan (ví dụ như NullReferenceExceptions không được ném ra). Đôi khi tôi nghĩ rằng họ nên giới thiệu một toán tử kiểu 'chuyển tiếp ống dẫn' mới cho các phương thức mở rộng.


Vì vậy, có vẻ như họ đã đưa ra một lựa chọn thiết kế thực sự tồi khi để nó có thể được gọi từ một ngữ cảnh tĩnh, vì tôi không hiểu tại sao mọi người lại làm như vậy.
AustinWBryan

3

Điều quan trọng cần lưu ý là có sự khác biệt giữa các phương pháp mở rộng và các phương pháp thông thường. Tôi nghĩ bạn vừa bắt gặp một trong số chúng.

Tôi sẽ cung cấp cho bạn một ví dụ về sự khác biệt khác: Khá dễ dàng để gọi một phương thức mở rộng trên một nulltham chiếu đối tượng. May mắn thay, điều này khó thực hiện hơn nhiều với các phương pháp thông thường. (Nhưng nó có thể được thực hiện. IIRC, Jon Skeet đã trình bày cách thực hiện điều này bằng cách thao tác mã CIL.)

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

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

Điều đó đang được nói, tôi đồng ý rằng có vẻ hơi phi lý khi thisđược yêu cầu để gọi phương thức mở rộng. Rốt cuộc, một phương thức mở rộng lý tưởng nên "cảm thấy" và hoạt động giống như một phương thức bình thường.

Nhưng trên thực tế, các phương thức mở rộng giống như một đường cú pháp được thêm vào bên trên ngôn ngữ hiện có hơn là một tính năng cốt lõi ban đầu phù hợp tuyệt vời với ngôn ngữ ở mọi khía cạnh.


trong ngôn ngữ Boo, bạn có thể gọi phương thức hoặc thuộc tính Extension trực tiếp trên null tức là null.Do ().
Sergey Mirvoda

1
@Sergey: Nghe có vẻ nguy hiểm ... :) Khiến tôi tò mò làm thế nào Boo biết loại (ngụ ý) null? Có thể chỉ là về bất kỳ loại tham chiếu nào, làm thế nào nó biết được những phương pháp nào có sẵn trên null?
stakx - không còn đóng góp

1
Có thể có một lý do chính đáng để có thể gọi một phương thức mở rộng với một tham chiếu rỗng.
Dave Van den Eynde

@DaveVandenEynde: IMHO, thật là sai lầm khi .net không xác định một thuộc tính cho các phương thức không phải ảo. Điều này sẽ yêu cầu trình biên dịch gọi chúng bằng các lệnh gọi không ảo. Một số kiểu lớp không thay đổi như Stringhoạt động một cách hợp lý như các giá trị và có thể có giá trị mặc định hợp lý khi null (ví dụ: .net có thể luôn coi tham chiếu null kiểu tĩnh stringlà một chuỗi rỗng, thay vì chỉ làm như vậy một cách thường xuyên). Việc phải sử dụng các phương thức tĩnh cho những thứ như if (!String.IsNullOrEmpty(stringVar))đơn giản là gớm ghiếc.
supercat

1
@supercat Vâng, gớm ghiếc. Bạn cũng có thể thực hiện string.IsNullOrEmpty (). Đẹp hơn nhiều.
Dave Van den Eynde

1

Bởi vì phương thức mở rộng không tồn tại với lớp ViewPage. Bạn cần cho trình biên dịch biết bạn đang gọi phương thức mở rộng. Hãy nhớ rằng this.Method()chỉ là đường biên dịch cho ViewExtensions.Method(this). Nó cũng giống như cách bạn không thể gọi một phương thức mở rộng ở giữa bất kỳ lớp nào bằng tên phương thức.


1
Điều này có ý nghĩa (bằng cách nào đó). Nhưng nếu đó là đường của trình biên dịch, thì tại sao trình biên dịch không thể kiểm tra các phương thức mở rộng mà không có thistừ khóa?
M4N

Đối với tôi có vẻ như một sự giám sát. Như bạn đã nói - trình biên dịch có thể thấy phương thức không tồn tại, sau đó kiểm tra các tiện ích mở rộng để biết tính khả dụng.
TomTom

Vâng, được cho là điều này có thể hoạt động. Tôi nghi ngờ đó là một quyết định thiết kế cụ thể để làm cho mã dễ đọc hơn. Nếu bạn chỉ có thể gọi ExtensionMethod () trong một lớp, nó có thể hơi khó hiểu đối với ai đó đang đọc mã nếu lớp đó không chứa phương thức đó nhưng với phương thức này.
Ben Robinson

Tôi nghĩ rằng this.Method () giống như đường cú pháp cho ViewExtensions.Method (this).
Alex Humphrey

1

Tôi đang làm việc trên một API thông thạo và gặp phải vấn đề tương tự. Mặc dù tôi có quyền truy cập vào lớp mà tôi đang mở rộng, tôi vẫn muốn logic của từng phương thức thông thạo nằm trong tệp riêng của chúng. Từ khóa "this" rất không trực quan, người dùng cứ nghĩ phương pháp này bị thiếu. Những gì tôi đã làm là làm cho lớp của tôi trở thành một lớp một phần triển khai các phương thức tôi cần thay vì sử dụng các phương thức mở rộng. Tôi không thấy đề cập đến tiệc tùng trong các câu trả lời. Nếu bạn có câu hỏi này, tiệc tùng có thể là một lựa chọn tốt hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.