Câu trả lời ngắn:
Nhà điều hành quote là một nhà điều hành mà gây ra ngữ nghĩa đóng cửa vào toán hạng của nó . Hằng số chỉ là giá trị.
Dấu ngoặc kép và hằng số có ý nghĩa khác nhau và do đó có các biểu diễn khác nhau trong một cây biểu thức . Có cùng một đại diện cho hai thứ rất khác nhau là cực kỳ khó hiểu và dễ xảy ra lỗi.
Câu trả lời dài:
Hãy xem xét những điều sau:
(int s)=>(int t)=>s+t
Lambda bên ngoài là một nhà máy cho các bộ cộng được liên kết với tham số của lambda bên ngoài.
Bây giờ, giả sử chúng ta muốn biểu diễn điều này như một cây biểu thức mà sau này sẽ được biên dịch và thực thi. Phần thân của cây biểu hiện phải là gì? Nó phụ thuộc vào việc bạn muốn trạng thái đã biên dịch trả về một đại biểu hay một cây biểu thức.
Hãy bắt đầu bằng cách loại bỏ trường hợp không thú vị. Nếu chúng ta muốn nó trả về một đại biểu thì câu hỏi về việc sử dụng Trích dẫn hay Hằng số là một điểm tranh luận:
var ps = Expression.Parameter(typeof(int), "s");
var pt = Expression.Parameter(typeof(int), "t");
var ex1 = Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt),
ps);
var f1a = (Func<int, Func<int, int>>) ex1.Compile();
var f1b = f1a(100);
Console.WriteLine(f1b(123));
Lambda có một lambda lồng nhau; trình biên dịch tạo lambda bên trong như một đại biểu cho một hàm được đóng trên trạng thái của hàm được tạo cho lambda bên ngoài. Chúng ta không cần xem xét trường hợp này nữa.
Giả sử chúng ta muốn trạng thái đã biên dịch trả về một cây biểu thức của bên trong. Có hai cách để làm điều đó: cách dễ và cách khó.
Cách khó là nói điều đó thay vì
(int s)=>(int t)=>s+t
ý chúng tôi thực sự là
(int s)=>Expression.Lambda(Expression.Add(...
Và sau đó tạo cây biểu thức cho điều đó , tạo ra mớ hỗn độn này :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
blah blah blah, hàng chục dòng mã phản chiếu để tạo lambda. Mục đích của toán tử trích dẫn là nói với trình biên dịch cây biểu thức rằng chúng ta muốn lambda đã cho được coi như một cây biểu thức, không phải như một hàm, mà không cần phải tạo mã tạo cây biểu thức một cách rõ ràng .
Cách dễ dàng là:
var ex2 = Expression.Lambda(
Expression.Quote(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile();
var f2b = f2a(200).Compile();
Console.WriteLine(f2b(123));
Và thực sự, nếu bạn biên dịch và chạy mã này, bạn sẽ có câu trả lời đúng.
Lưu ý rằng toán tử trích dẫn là toán tử tạo ra ngữ nghĩa đóng trên lambda bên trong sử dụng một biến bên ngoài, một tham số chính thức của lambda bên ngoài.
Câu hỏi là: tại sao không loại bỏ Quote và làm cho điều này làm điều tương tự?
var ex3 = Expression.Lambda(
Expression.Constant(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile();
var f3b = f3a(300).Compile();
Console.WriteLine(f3b(123));
Hằng số không tạo ra ngữ nghĩa đóng. Tại sao nên làm thế? Bạn đã nói rằng đây là một hằng số . Nó chỉ là một giá trị. Nó phải hoàn hảo khi được giao cho trình biên dịch; trình biên dịch sẽ có thể chỉ tạo một kết xuất giá trị đó vào ngăn xếp nơi nó cần thiết.
Vì không có bao đóng nào được tạo ra, nếu bạn làm điều này, bạn sẽ nhận được ngoại lệ "biến" thuộc loại 'System.Int32' không được xác định "trên lời gọi.
(Bên cạnh: Tôi vừa xem xét trình tạo mã để tạo đại biểu từ cây biểu thức được trích dẫn và rất tiếc là một nhận xét mà tôi đã đưa vào mã hồi năm 2006 vẫn còn đó. FYI, tham số bên ngoài được lưu trữ được chụp nhanh thành một hằng số khi được trích dẫn Cây biểu thức được trình biên dịch thời gian chạy sửa đổi thành một đại biểu. Có một lý do chính đáng tại sao tôi viết mã theo cách đó mà tôi không nhớ chính xác vào thời điểm này, nhưng nó có tác dụng phụ khó chịu là giới thiệu đóng trên các giá trị của tham số bên ngoài thay vì đóng trên các biến. Rõ ràng nhóm kế thừa mã đó đã quyết định không sửa lỗi đó, vì vậy nếu bạn đang dựa vào sự đột biến của thông số bên ngoài đóng ngoài được quan sát trong lambda nội thất được trích dẫn đã biên dịch, bạn sẽ thất vọng. Tuy nhiên, vì việc (1) biến đổi một tham số chính thức và (2) dựa vào đột biến của một biến bên ngoài là một cách lập trình khá tệ, tôi khuyên bạn nên thay đổi chương trình của mình để không sử dụng hai phương pháp lập trình xấu này, thay vì đang chờ một bản sửa lỗi không có vẻ như sắp xảy ra. Xin lỗi vì lỗi.)
Vì vậy, để lặp lại câu hỏi:
Trình biên dịch C # có thể được thực hiện để biên dịch các biểu thức lambda lồng nhau thành một cây biểu thức liên quan đến Expression.Constant () thay vì Expression.Quote () và bất kỳ trình cung cấp truy vấn LINQ nào muốn xử lý cây biểu thức thành một số ngôn ngữ truy vấn khác (chẳng hạn như SQL ) có thể tìm một ConstantExpression với kiểu Expression thay vì UnaryExpression với kiểu nút Quote đặc biệt và mọi thứ khác sẽ giống nhau.
Bạn nói đúng. Chúng tôi có thể mã hóa thông tin ngữ nghĩa có nghĩa là "tạo ra ngữ nghĩa đóng trên giá trị này" bằng cách sử dụng kiểu của biểu thức hằng làm cờ .
"Hằng số" khi đó sẽ có ý nghĩa "sử dụng giá trị không đổi này, trừ khi kiểu xảy ra là một kiểu cây biểu thức và giá trị là một cây biểu thức hợp lệ, trong trường hợp đó, thay vào đó, hãy sử dụng giá trị là cây biểu thức do viết lại bên trong cây biểu thức đã cho để tạo ra ngữ nghĩa đóng trong ngữ cảnh của bất kỳ lambdas bên ngoài nào mà chúng ta có thể đang ở ngay bây giờ.
Nhưng tại sao sẽ chúng tôi làm điều đó điều điên? Toán tử trích dẫn là một toán tử cực kỳ phức tạp và nó nên được sử dụng một cách rõ ràng nếu bạn định sử dụng nó. Bạn đang đề xuất rằng để phân tích cú pháp về việc không thêm một phương thức gốc và kiểu nút bổ sung trong số hàng chục đã có, chúng tôi thêm một trường hợp góc kỳ lạ vào các hằng số, để các hằng số đôi khi là hằng số logic và đôi khi chúng được viết lại lambdas với ngữ nghĩa đóng.
Nó cũng sẽ có một hiệu ứng hơi kỳ lạ rằng hằng số không có nghĩa là "sử dụng giá trị này". Giả sử vì một lý do kỳ lạ nào đó bạn muốn trường hợp thứ ba ở trên biên dịch một cây biểu thức thành một đại biểu đưa ra một cây biểu thức có tham chiếu không được viết lại đến một biến bên ngoài? Tại sao? Có lẽ bởi vì bạn đang thử nghiệm trình biên dịch của mình và muốn chỉ chuyển hằng số qua để bạn có thể thực hiện một số phân tích khác về nó sau này. Đề xuất của bạn sẽ biến điều đó thành không thể; bất kỳ hằng số nào xảy ra thuộc loại cây biểu thức sẽ được viết lại bất kể. Người ta có một kỳ vọng hợp lý rằng "không đổi" có nghĩa là "sử dụng giá trị này". "Constant" là một nút "làm những gì tôi nói". Bộ xử lý không đổi ' để nói dựa trên loại.
Và tất nhiên hãy lưu ý rằng bây giờ bạn đang đặt gánh nặng hiểu biết (nghĩa là, hiểu rằng hằng số có ngữ nghĩa phức tạp có nghĩa là "hằng số" trong một trường hợp và "tạo ra ngữ nghĩa đóng" dựa trên một cờ nằm trong kiểu hệ thống ) trên mỗi trình cung cấp thực hiện phân tích ngữ nghĩa của cây biểu thức, không chỉ dựa trên các nhà cung cấp của Microsoft. Có bao nhiêu trong số các nhà cung cấp bên thứ ba đó sẽ làm sai?
"Trích dẫn" đang vẫy một lá cờ đỏ lớn nói rằng "này anh bạn, nhìn qua đây, tôi là một biểu thức lambda lồng nhau và tôi có ngữ nghĩa kỳ quặc nếu tôi đóng trên một biến bên ngoài!" trong khi "Constant" nói "Tôi không hơn gì một giá trị; hãy sử dụng tôi khi bạn thấy phù hợp." Khi một thứ gì đó phức tạp và nguy hiểm, chúng tôi muốn làm cho nó nổi lên những lá cờ đỏ, chứ không phải che giấu sự thật đó bằng cách bắt người dùng tìm hiểu hệ thống loại để tìm xem giá trị này có phải là giá trị đặc biệt hay không.
Hơn nữa, ý kiến cho rằng tránh dư thừa thậm chí là một mục tiêu là không chính xác. Chắc chắn, tránh dư thừa không cần thiết, khó hiểu là một mục tiêu, nhưng hầu hết dư thừa là một điều tốt; sự dư thừa tạo ra sự rõ ràng. Các phương pháp nhà máy mới và các loại nút rẻ . Chúng tôi có thể tạo ra bao nhiêu tùy thích để mỗi cái đại diện cho một hoạt động rõ ràng. Chúng ta không cần phải dùng đến những thủ thuật khó chịu như "điều này có nghĩa là một thứ trừ khi trường này được đặt thành thứ này, trong trường hợp đó nó có nghĩa là một thứ khác."