2010-10-21 22 views
18

Tôi muốn nóiTôi có thể chụp một biến cục bộ thành một biểu thức LINQ như một hằng số thay vì một tham chiếu đóng không?

int x = magic(), y = moremagic(); 
return i => i + (x/y); 

và có x được chụp như một hằng số thay vì một tài liệu tham khảo khác nhau. Ý tưởng là x sẽ không bao giờ thay đổi và do đó khi biểu thức được biên dịch, trình biên dịch có thể làm việc gấp liên tục và tạo mã hiệu quả hơn - tức là tính x/y một lần thay vì trên mọi cuộc gọi, thông qua các tham chiếu con trỏ vào bản ghi đóng.

Không có cách nào để đánh dấu x là chỉ đọc trong một phương thức và trình biên dịch không đủ thông minh để phát hiện rằng nó không thay đổi sau khi tạo biểu thức.

Tôi ghét phải xây dựng biểu thức bằng tay. Có ý tưởng tuyệt vời nào không?

CẬP NHẬT: Tôi đã kết thúc bằng cách sử dụng tuyệt vời LinqKit để xây dựng một bộ đánh giá một phần sẽ thực hiện các thay thế mà tôi muốn. Biến đổi chỉ an toàn nếu bạn biết rằng các tài liệu tham khảo có liên quan sẽ không thay đổi, nhưng nó hoạt động cho mục đích của tôi. Có thể hạn chế việc đánh giá từng phần chỉ để các thành viên trực tiếp đóng cửa của bạn, mà bạn kiểm soát, bằng cách thêm một kiểm tra thêm hoặc hai trong đó, đó là khá rõ ràng về kiểm tra mã mẫu được cung cấp trong LinqKit.

/// <summary>Walks your expression and eagerly evaluates property/field members and substitutes them with constants. 
/// You must be sure this is semantically correct, by ensuring those fields (e.g. references to captured variables in your closure) 
/// will never change, but it allows the expression to be compiled more efficiently by turning constant numbers into true constants, 
/// which the compiler can fold.</summary> 
public class PartiallyEvaluateMemberExpressionsVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitMemberAccess(MemberExpression m) 
    { 
     Expression exp = this.Visit(m.Expression); 

     if (exp == null || exp is ConstantExpression) // null=static member 
     { 
      object @object = exp == null ? null : ((ConstantExpression)exp).Value; 
      object value = null; Type type = null; 
      if (m.Member is FieldInfo) 
      { 
       FieldInfo fi = (FieldInfo)m.Member; 
       value = fi.GetValue(@object); 
       type = fi.FieldType; 
      } 
      else if (m.Member is PropertyInfo) 
      { 
       PropertyInfo pi = (PropertyInfo)m.Member; 
       if (pi.GetIndexParameters().Length != 0) 
        throw new ArgumentException("cannot eliminate closure references to indexed properties"); 
       value = pi.GetValue(@object, null); 
       type = pi.PropertyType; 
      } 
      return Expression.Constant(value, type); 
     } 
     else // otherwise just pass it through 
     { 
      return Expression.MakeMemberAccess(exp, m.Member); 
     } 
    } 
} 
+0

chỉ là những gì tôi đang tìm kiếm! – jeroenh

+1

Tôi biết nó đã được một thời gian, nhưng điều này đã giúp tôi tiết kiệm rất nhiều thời gian. Cảm ơn. – Stargazer

Trả lời

4

Không có cách nào để thực hiện việc này trong C#. Trình biên dịch không hỗ trợ ghi các biến theo giá trị/const. Bạn cũng không thể chuyển đổi một giá trị không const thành một giá trị const khi chạy theo cách này.

Ngoài ra trình biên dịch C# chỉ làm việc gấp liên tục trong quá trình biên dịch ban đầu cho các giá trị không đổi đã biết. Nếu nó có thể đóng băng một giá trị tại thời gian chạy vào một hằng số nó sẽ không tham gia vào trình biên dịch liên tục gấp vì nó xảy ra trong thời gian chạy.

2

Trình biên dịch không thực hiện loại "lưu trữ giá trị" này. Hằng số liên tục được thực hiện tại thời gian biên dịch chỉ cho các hằng số, không phải cho các trường chỉ đọc và chắc chắn không phải cho các biến cục bộ không có giá trị đã biết tại thời gian biên dịch.

Bạn phải tự mình thực hiện, nhưng phải duy trì tham chiếu đóng cửa (vì giá trị thực tế không thể xác định được tại thời gian biên dịch, đó là lý do tại sao nó có khả năng được đóng trong khi biểu thức được tạo) :

int x = magic(), y = moremagic(); 
int xy = x/y; 
return i => i + xy; 
+0

thực sự, mặc dù điều này vẫn liên quan đến xy được tra cứu trên mọi cách sử dụng. Và ví dụ của tôi là tùy ý phức tạp - sẽ rất tuyệt khi có trình biên dịch C# đơn giản hóa phương trình lớn mà kết quả, thay vì tự làm nó. –

0

x không thể là hằng số, vì bạn đang thực hiện phép thuật thời gian chạy để xác định nó là gì. Tuy nhiên, nếu bạn biết rằng xy không thay đổi, hãy thử:

int x = magic(), y = moremagic(); 
int xOverY = x/y; 
return i => i + xOverY; 

Tôi cũng nên đề cập đến rằng mặc dù mã IL được biên soạn cho i => i + (x/y) sẽ hiển thị các bộ phận, trình biên dịch JIT là gần như chắc chắn sẽ tối ưu hóa lối này.

+0

Tôi không nghĩ rằng nó có thể tối ưu hóa các phân chia, bởi vì nó phải lấy x và y từ đóng cửa mỗi lần –

+0

Có lẽ bạn đã đúng. Tôi không biết nhiều về trình biên dịch JIT của .NET. Dường như với tôi rằng trình biên dịch sẽ dễ dàng nhận ra rằng các giá trị của x và y sẽ không bao giờ thay đổi cho một trường hợp cụ thể của việc đóng (chúng về cơ bản được lưu thành các trường chỉ đọc trên một lớp mới, dựa trên IL) và sửa đổi lớp lúc chạy để loại bỏ nhu cầu của chúng. – StriplingWarrior

0

Một kỹ thuật tôi đã sử dụng trong vb2005 là sử dụng một nhà máy đại biểu chung để thực thi các giá trị đóng theo giá trị. Tôi chỉ thực hiện nó cho các subs chứ không phải là các hàm, nhưng nó cũng có thể được thực hiện cho các hàm. Nếu được mở rộng theo cách đó:

 
FunctionOf.NewInv() 

sẽ là hàm tĩnh sẽ chấp nhận làm tham số hàm (được mô tả sau), T3 và T4. Hàm được truyền vào phải chấp nhận các tham số kiểu T2, T3 và T4 và trả về một T1.Hàm trả về bởi NewInv sẽ chấp nhận một tham số kiểu T2 và gọi hàm được truyền vào với tham số đó và tham số cho NewInv.

Các invocation sẽ giống như thế:

 
return FunctionOf.NewInv((i,x,y) => i+x/y, x, y) 
-1

Nếu bạn (như tôi) đang tạo ra một số người xây dựng biểu thức SQL truy vấn bạn có thể consirer như sau: đầu tiên tạo ra một biến lớp, làm cho nó một hằng số và sau đó truy cập nó như thế này:

var constant= Expression.Constant(values); 
var start = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.Start)); 
var end = Expression.MakeMemberAccess(constant, values.GetMemberInfo(f => f.End)); 

var more = Expression.GreaterThanOrEqual(memberBody, start); 
var less = Expression.LessThanOrEqual(memberBody, end); 
+0

Không có lợi thế để làm điều này chỉ bằng cách sử dụng một đóng cửa. – Servy

+0

@Servy làm thế nào để bạn tạo ra một đóng cửa với các biểu thức? Có các giá trị trong truy vấn của tôi với Expression.Constant bây giờ có> @parameters – xumix

+0

Bạn sử dụng lambda và đóng biến, như được hiển thị giống như tất cả các câu trả lời khác và câu hỏi. – Servy

Các vấn đề liên quan