2011-04-22 27 views
17

Tôi đang làm việc trên một thư viện cho phép người dùng nhập các biểu thức tùy ý. Thư viện của tôi sau đó biên dịch các biểu thức như một phần của một biểu thức lớn hơn thành một đại biểu. Bây giờ, vì những lý do vẫn chưa biết, việc biên dịch biểu thức với Compile đôi khi/thường dẫn đến mã chậm hơn nhiều so với nếu nó không phải là một biểu thức được biên dịch. Tôi asked a question about this trước đây và một giải pháp khác là không sử dụng Compile, nhưng CompileToMethod và tạo phương thức static trên một loại mới trong một hội động mới. Điều đó hoạt động và mã nhanh. Tuy nhiên, người dùng có thể nhập các biểu thức tùy ý và nó chỉ ra rằng nếu người dùng gọi một chức năng ngoài công lập hoặc truy cập một trường ngoài công khai trong biểu thức, nó sẽ ném một số System.MethodAccessException (trong trường hợp của một phương thức phi công cộng). khi đại biểu được gọi. Những gì tôi có thể làm ở đây là tạo ExpressionVisitor mới để kiểm tra xem biểu thức có truy cập bất kỳ điều gì không công khai và sử dụng chậm hơn Compile trong những trường hợp đó hay không, nhưng tôi muốn có các thành viên ngoài công lập. Hoặc tìm hiểu xem có bất kỳ điều gì tôi có thể làm về số Compile chậm hơn (đôi khi) hay không..NET: Truy cập các thành viên ngoài công lập từ một assembly động

Mã đầy đủ để tái tạo vấn đề này:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Linq.Expressions; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace DynamicAssembly 
{ 
    public class Program 
    { 
    private static int GetValue() 
    { 
     return 1; 
    } 

    public static int GetValuePublic() 
    { 
     return 1; 
    } 

    public static int Foo; 

    static void Main(string[] args) 
    { 
     Expression<Func<int>> expression =() => 10 + GetValue(); 

     Foo = expression.Compile()(); 

     Console.WriteLine("This works, value: " + Foo); 

     Expression<Func<int>> expressionPublic =() => 10 + GetValuePublic(); 

     var compiledDynamicAssemblyPublic = (Func<int>)CompileExpression(expressionPublic); 

     Foo = compiledDynamicAssemblyPublic(); 

     Console.WriteLine("This works too, value: " + Foo); 

     var compiledDynamicAssemblyNonPublic = (Func<int>)CompileExpression(expression); 

     Console.WriteLine("This crashes"); 

     Foo = compiledDynamicAssemblyNonPublic(); 
    } 

    static Delegate CompileExpression(LambdaExpression expression) 
    { 
     var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
     new AssemblyName("MyAssembly"+ Guid.NewGuid().ToString("N")), 
     AssemblyBuilderAccess.Run); 

     var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module"); 

     var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public); 

     var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
     MethodAttributes.Public | MethodAttributes.Static); 

     expression.CompileToMethod(methodBuilder); 

     var resultingType = typeBuilder.CreateType(); 

     var function = Delegate.CreateDelegate(expression.Type, 
     resultingType.GetMethod("MyMethod")); 

     return function; 
    } 
    } 
} 
+0

Tôi không có câu trả lời cho bạn, nhưng tại sao cần hỗ trợ gọi các phương thức riêng tư? – jlew

+0

Bởi vì người dùng hy vọng nó sẽ là có thể. Bởi vì chúng * có thể truy cập được khi anh ta tạo ra biểu thức, như '() => CallPrivateMethod()', nhưng chúng sẽ thất bại khi chạy. Không có gì để anh ta chỉ ra rằng nó không hoạt động cho đến khi anh ta chạy nó và nó bị treo và bỏng. Đó là thực sự xấu và vi phạm các quy tắc của "ít ngạc nhiên" vì vậy tôi không thể biện minh làm điều đó và tôi sẽ phải giải quyết cho mã chậm. – JulianR

+0

Làm cho tinh thần, nếu người dùng là một lập trình viên C# (như trái ngược với ai đó gõ biểu thức vào một hình thức, ví dụ). Bạn đã chuẩn bị bản phát hành so với chế độ gỡ lỗi cho đại biểu được biên soạn chưa? Làm thế nào để họ so sánh với nhau? – jlew

Trả lời

5

Sự cố không phải là quyền do không có sự cho phép có thể cho phép bạn truy cập vào trường ngoài công lập hoặc thành viên của lớp khác mà không phản ánh. Điều này tương tự với tình huống mà bạn đã biên dịch hai hội đồng không động và một assembly gọi một phương thức công khai trong assembly thứ hai. Sau đó, nếu bạn thay đổi phương thức thành riêng tư mà không biên dịch lại cụm đầu tiên, cuộc gọi hội đồng đầu tiên sẽ là không thành công khi chạy. Nói cách khác, biểu thức trong assembly động của bạn đang được biên dịch thành một lời gọi phương thức thông thường mà nó không có quyền gọi nữa ngoài việc bạn làm từ một lớp khác ngay cả trong cùng một assembly.

Vì không có sự cho phép nào có thể giải quyết vấn đề của bạn, bạn có thể chuyển đổi các tham chiếu trường và phương thức không công khai thành các biểu thức con sử dụng sự phản chiếu.

Dưới đây là ví dụ được lấy từ trường hợp thử nghiệm của bạn.Đây không:

Expression<Func<int>> expression =() => 10 + GetValue(); 

nhưng điều này sẽ thành công:

Expression<Func<int>> expression =() => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); 

Vì đây không sụp đổ với một ngoại lệ, bạn có thể thấy rằng lắp ráp động của bạn không có sự cho phép phản ánh và nó có thể truy cập vào các phương pháp tư nhân, nó chỉ không thể làm điều đó bằng cách sử dụng một phương pháp thông thường gọi là CompileToMethod kết quả in.

1

Tôi đã từng có một vấn đề truy cập vào các yếu tố private của một lớp, từ tạo ra mã IL sử dụng DynamicMethod.

Hóa ra là đã có một tình trạng quá tải của các nhà xây dựng của lớp DynamicMethod tiếp nhận các loại hình lớp thành truy cập riêng Mà sẽ được phép:

http://msdn.microsoft.com/en-us/library/exczf7b9.aspx

Liên kết này Bao gồm các mẫu như thế nào để truy cập dữ liệu cá nhân ... Tôi biết rằng điều này không liên quan gì đến các cây biểu thức, nhưng nó có thể cung cấp cho bạn một số manh mối về cách thực hiện nó.

Có thể có một số loại điều tương tự khi biên dịch cây biểu thức ... hoặc bạn có thể tạo cây biểu thức đó dưới dạng DynamicMethod.

+0

Cảm ơn. Nhưng 'CompileToMethod' chỉ có một cách để được gọi là: nó cần phải được truyền một thể hiện của' MethodBuilder'. Và bạn chỉ có thể nhận được một 'MethodBuilder' từ một' TypeBuilder' và bạn cần một 'ModuleBuilder' cho điều đó và cho * that * bạn cần một' AssemblyBuilder'. – JulianR

1

Nếu hội đồng không động được xây dựng bởi bạn, bạn thực sự có thể bao gồm InternalsVisibleTo cho cụm động (thậm chí làm việc với tên mạnh)). Điều đó sẽ cho phép sử dụng các thành viên nội bộ, có thể là đủ trong trường hợp của bạn?

Để có được một ý tưởng, đây là một ví dụ trong đó cho thấy nóng để cho phép lắp ráp động của Moq sử dụng công cụ nội bộ từ lắp ráp khác: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with-moq/

Nếu phương pháp này là không đủ, tôi muốn đi với một sự kết hợp các gợi ý của Rick và Miguel: tạo các "DynamicMethods" proxy cho mỗi lời gọi tới một thành viên không phải công khai và thay đổi cây biểu thức để chúng được sử dụng thay cho các lời gọi ban đầu.

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