Tại sao một phương thức ẩn danh không thể được gán cho var?


139

Tôi có đoạn mã sau:

Func<string, bool> comparer = delegate(string value) {
    return value != "0";
};

Tuy nhiên, sau đây không biên dịch:

var comparer = delegate(string value) {
    return value != "0";
};

Tại sao trình biên dịch không thể tìm ra nó là một Func<string, bool>? Nó nhận một tham số chuỗi và trả về một boolean. Thay vào đó, nó cho tôi lỗi:

Không thể gán phương thức ẩn danh cho một biến cục bộ được gõ ngầm.

Tôi có một phỏng đoán và đó là nếu phiên bản var được biên dịch , nó sẽ thiếu tính nhất quán nếu tôi có những điều sau đây:

var comparer = delegate(string arg1, string arg2, string arg3, string arg4, string arg5) {
    return false;
};

Những điều trên sẽ không có ý nghĩa vì Func <> chỉ cho phép tối đa 4 đối số (trong .NET 3.5, đó là những gì tôi đang sử dụng). Có lẽ ai đó có thể làm rõ vấn đề. Cảm ơn.


3
Lưu ý về đối số 4 đối số của bạn , trong .NET 4, Func<>chấp nhận tối đa 16 đối số.
Anthony Pegram

Cảm ơn bạn đã làm rõ. Tôi đang sử dụng .NET 3.5.
Marlon

9
Tại sao trình biên dịch nghĩ rằng đó là một Func<string, bool>? Có vẻ như Converter<string, bool>với tôi!
Ben Voigt


3
đôi khi tôi nhớ VB ..Dim comparer = Function(value$) value <> "0"
Slai

Câu trả lời:


155

Những người khác đã chỉ ra rằng có vô số loại đại biểu có thể có mà bạn có thể có nghĩa; về rất đặc biệt là những gì Funcmà nó xứng đáng được mặc định thay vì Predicatehoặc Actionhoặc bất kỳ khả năng nào khác? Và, đối với lambdas, tại sao rõ ràng ý định là chọn hình thức đại biểu, thay vì hình thức cây biểu thức?

Nhưng chúng ta có thể nói điều đó Functhật đặc biệt, và loại suy ra của phương pháp lambda hoặc ẩn danh là Func của một cái gì đó. Chúng tôi vẫn có đủ loại vấn đề. Những loại bạn muốn được suy luận cho các trường hợp sau đây?

var x1 = (ref int y)=>123;

Không có Func<T>loại mà có một ref bất cứ điều gì.

var x2 = y=>123;

Chúng tôi không biết loại tham số chính thức, mặc dù chúng tôi biết sự trở lại. (Hay chúng ta? Là trả về int? Dài? Ngắn? Byte?)

var x3 = (int y)=>null;

Chúng tôi không biết loại trả về, nhưng nó không thể bị hủy. Kiểu trả về có thể là bất kỳ loại tham chiếu hoặc bất kỳ loại giá trị nullable nào.

var x4 = (int y)=>{ throw new Exception(); }

Một lần nữa, chúng ta không biết loại trả về và lần này nó có thể bị hủy.

var x5 = (int y)=> q += y;

Đó có phải là một tuyên bố lambda trả về void hoặc một cái gì đó trả về giá trị được gán cho q? Cả hai đều hợp pháp; Nên chọn cái nào?

Bây giờ, bạn có thể nói, tốt, đừng hỗ trợ bất kỳ tính năng nào trong số đó. Chỉ cần hỗ trợ các trường hợp "bình thường" trong đó các loại có thể được giải quyết. Điều đó không có ích. Làm thế nào mà làm cho cuộc sống của tôi dễ dàng hơn? Nếu tính năng đôi khi hoạt động và đôi khi không thành công thì tôi vẫn phải viết mã để phát hiện tất cả các tình huống lỗi đó và đưa ra một thông báo lỗi có ý nghĩa cho từng tình huống . Chúng ta vẫn phải chỉ định tất cả hành vi đó, ghi lại nó, viết các bài kiểm tra cho nó, v.v. Đây là một tính năng rất đắt tiền giúp người dùng tiết kiệm được khoảng nửa tá tổ hợp phím. Chúng tôi có những cách tốt hơn để tăng giá trị cho ngôn ngữ hơn là dành nhiều thời gian để viết các trường hợp thử nghiệm cho một tính năng không hoạt động được một nửa thời gian và hầu như không cung cấp bất kỳ lợi ích nào trong trường hợp nó hoạt động.

Tình huống thực sự hữu ích là:

var xAnon = (int y)=>new { Y = y };

bởi vì không có loại "có thể nói" cho điều đó. Nhưng chúng tôi luôn có vấn đề này và chúng tôi chỉ sử dụng suy luận kiểu phương pháp để suy ra kiểu:

Func<A, R> WorkItOut<A, R>(Func<A, R> f) { return f; }
...
var xAnon = WorkItOut((int y)=>new { Y = y });

và bây giờ suy luận kiểu phương thức tìm ra kiểu func là gì.


43
Khi nào bạn sẽ biên dịch câu trả lời SO của bạn thành một cuốn sách? Tôi sẽ mua nó :)
Matt Greer

13
Tôi thứ hai đề xuất cho một cuốn sách câu trả lời SO của Eric Lippert. Tiêu đề được đề xuất: "Phản ánh từ ngăn xếp"
Adam Rackis

24
@Eric: Câu trả lời hay, nhưng hơi sai lầm khi minh họa điều này là điều không thể, vì điều này thực sự hoạt động hoàn toàn tốt trong D. Chỉ là các bạn đã không chọn đưa ra loại chữ theo cách riêng của họ, và thay vào đó làm cho họ phụ thuộc trên bối cảnh của họ ... vì vậy IMHO câu trả lời phải là "bởi vì đó là cách chúng tôi tạo ra nó" hơn bất kỳ điều gì khác. :)
dùng541686

5
@ababddissonance Tôi cũng lưu ý rằng trình biên dịch là nguồn mở. Nếu bạn quan tâm đến tính năng này thì bạn có thể quyên góp thời gian và công sức cần thiết để thực hiện. Tôi khuyến khích bạn gửi một yêu cầu kéo.
Eric Lippert

7
@ Ab khu vực: Chúng tôi đã đo lường chi phí theo các nguồn lực hạn chế: nhà phát triển và thời gian. Trách nhiệm này không được thần ban cho; nó được áp đặt bởi phó chủ tịch của bộ phận phát triển. Khái niệm rằng bằng cách nào đó, nhóm C # có thể bỏ qua quy trình ngân sách là một điều kỳ lạ. Tôi đảm bảo với bạn, sự đánh đổi đã và vẫn được thực hiện bởi sự cân nhắc cẩn thận, chu đáo của các chuyên gia có cộng đồng C # bày tỏ mong muốn, sứ mệnh chiến lược cho Microsoft và ý tưởng tuyệt vời của riêng họ trong thiết kế.
Eric Lippert

29

Chỉ có Eric Lippert biết chắc chắn, nhưng tôi nghĩ đó là vì chữ ký của loại đại biểu không xác định duy nhất loại đó.

Hãy xem xét ví dụ của bạn:

var comparer = delegate(string value) { return value != "0"; };

Đây là hai suy luận có thể cho những gì varnên được:

Predicate<string> comparer  = delegate(string value) { return value != "0"; };  // okay
Func<string, bool> comparer = delegate(string value) { return value != "0"; };  // also okay

Cái nào nên trình biên dịch suy ra? Không có lý do chính đáng để chọn cái này hay cái kia. Và mặc dù a Predicate<T>tương đương về chức năng với a Func<T, bool>, chúng vẫn là các loại khác nhau ở cấp độ của hệ thống loại .NET. Do đó, trình biên dịch không thể giải quyết rõ ràng kiểu đại biểu và phải thất bại kiểu suy luận.


1
Tôi chắc chắn khá nhiều người khác tại Microsoft cũng biết chắc chắn. ;) Nhưng có, bạn ám chỉ một lý do chính, loại thời gian biên dịch không thể được xác định bởi vì không có. Mục 8.5.1 của đặc tả ngôn ngữ đặc biệt nêu bật lý do này cho phép không cho phép các hàm ẩn danh được sử dụng trong các khai báo biến được gõ ngầm.
Anthony Pegram

3
Vâng. Và thậm chí tệ hơn, đối với lambdas, chúng ta thậm chí không biết liệu nó sẽ thuộc loại đại biểu hay không; nó có thể là một cây biểu thức
Eric Lippert

Đối với bất cứ ai quan tâm, tôi đã viết lên một chút thêm về điều này và làm thế nào C # và F # phương pháp tiếp cận tương phản ở mindscapehq.com/blog/index.php/2011/02/23/...
itowlson

tại sao trình biên dịch không thể chế tạo một loại duy nhất mới như C ++ làm cho chức năng lambda của nó
Weipeng L

Làm thế nào để họ khác nhau "ở cấp độ của hệ thống loại .NET"?
arao6

6

Eric Lippert có một bài viết cũ về nó, nơi ông nói

Và trên thực tế, đặc tả C # 2.0 gọi điều này. Biểu thức nhóm phương thức và biểu thức phương thức ẩn danh là biểu thức không chữ trong C # 2.0 và biểu thức lambda kết hợp chúng trong C # 3.0. Do đó, việc họ tỏ ra "trần trụi" ở phía bên phải của một tuyên bố ngầm là bất hợp pháp.


Và điều này được nhấn mạnh bởi mục 8.5.1 của đặc tả ngôn ngữ. "Biểu thức khởi tạo phải có loại thời gian biên dịch" để được sử dụng cho biến cục bộ được gõ ngầm.
Anthony Pegram

5

Các đại biểu khác nhau được coi là các loại khác nhau. ví dụ, Actionkhác với MethodInvokervà một Actionthể hiện không thể được gán cho một biến loại MethodInvoker.

Vì vậy, được đưa ra một đại biểu ẩn danh (hoặc lambda) như thế () => {}, nó là một Actionhoặc một MethodInvoker? Trình biên dịch không thể nói.

Tương tự, nếu tôi khai báo một loại đại biểu lấy một stringđối số và trả về một bool, làm thế nào trình biên dịch sẽ biết bạn thực sự muốn Func<string, bool>thay vì loại đại biểu của tôi? Nó không thể suy ra loại đại biểu.


2

Các điểm sau đây là từ MSDN liên quan đến các biến cục bộ được gõ ngầm định:

  1. var chỉ có thể được sử dụng khi một biến cục bộ được khai báo và khởi tạo trong cùng một câu lệnh; biến không thể được khởi tạo thành null hoặc nhóm phương thức hoặc hàm ẩn danh.
  2. Từ khóa var hướng dẫn trình biên dịch suy ra loại biến từ biểu thức ở phía bên phải của câu lệnh khởi tạo.
  3. Điều quan trọng là phải hiểu rằng từ khóa var không có nghĩa là "biến thể" và không chỉ ra rằng biến được gõ lỏng lẻo hoặc bị ràng buộc muộn. Nó chỉ có nghĩa là trình biên dịch xác định và gán loại thích hợp nhất.

Tham chiếu MSDN: Nhập ngầm định các biến cục bộ

Xem xét những điều sau đây về Phương pháp ẩn danh:

  1. Các phương thức ẩn danh cho phép bạn bỏ qua danh sách tham số.

Tham khảo MSDN: Phương thức ẩn danh

Tôi nghi ngờ rằng vì phương thức ẩn danh thực sự có thể có các chữ ký phương thức khác nhau, trình biên dịch không thể suy ra chính xác loại phù hợp nhất để gán sẽ là gì.


1

Bài viết của tôi không trả lời câu hỏi thực tế, nhưng nó trả lời câu hỏi cơ bản về:

"Làm thế nào để tôi tránh phải gõ một số loại fugly như thế Func<string, string, int, CustomInputType, bool, ReturnType>nào?" [1]

Là một lập trình viên lười biếng / hacky, tôi đã thử nghiệm sử dụng Func<dynamic, object>- lấy một tham số đầu vào duy nhất và trả về một đối tượng.

Đối với nhiều đối số, bạn có thể sử dụng nó như vậy:

dynamic myParams = new ExpandoObject();
myParams.arg0 = "whatever";
myParams.arg1 = 3;
Func<dynamic, object> y = (dynObj) =>
{
    return dynObj.arg0.ToUpper() + (dynObj.arg1 * 45); //screw type casting, amirite?
};
Console.WriteLine(y(myParams));

Mẹo: Bạn có thể sử dụng Action<dynamic>nếu bạn không cần trả lại đối tượng.

Vâng tôi biết nó có thể đi ngược lại các nguyên tắc lập trình của bạn, nhưng điều này có ý nghĩa với tôi và có lẽ một số lập trình viên Python.

Tôi là người mới làm quen tại các đại biểu ... chỉ muốn chia sẻ những gì tôi học được.


[1] Điều này giả định rằng bạn không gọi một phương thức yêu cầu xác định trước Funclàm tham số, trong trường hợp đó, bạn sẽ phải nhập chuỗi đầy đủ đó: /


0

Làm thế nào là về điều đó?

var item = new
    {
        toolisn = 100,
        LangId = "ENG",
        toolPath = (Func<int, string, string>) delegate(int toolisn, string LangId)
        {
              var path = "/Content/Tool_" + toolisn + "_" + LangId + "/story.html";
              return File.Exists(Server.MapPath(path)) ? "<a style=\"vertical-align:super\" href=\"" + path + "\" target=\"_blank\">execute example</a> " : "";
        }
};

string result = item.toolPath(item.toolisn, item.LangId);
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.