2009-07-31 30 views
5

Bạn có thể tạo một đại biểu của một phương thức cá thể mà không chỉ rõ cá thể tại thời gian tạo không? Nói cách khác, bạn có thể tạo ra một đại biểu "tĩnh" mà phải mất vì nó là tham số đầu tiên của cá thể phương thức nên được gọi?"Uncurrying" một phương thức thể hiện trong .NET

Ví dụ: làm thế nào tôi có thể xây dựng đại biểu sau bằng cách sử dụng sự phản chiếu?

Func<int, string> = i=>i.ToString(); 

Tôi biết thực tế là tôi có thể sử dụng methodInfo.Invoke, nhưng điều này chậm hơn và không kiểm tra tính chính xác của loại cho đến khi được gọi.

Khi bạn có MethodInfo của một đặc biệt tĩnh phương pháp, chúng ta có thể xây dựng một đại biểu sử dụng Delegate.CreateDelegate(delegateType, methodInfo), và tất cả các thông số của phương pháp tĩnh vẫn tự do.

Như Jon Skeet đã chỉ ra, bạn có thể chỉ cần áp dụng tương tự để tạo một đại biểu mở của một phương thức thể hiện nếu phương thức không ảo trên một kiểu tham chiếu. Việc quyết định phương thức nào để gọi trên một phương thức ảo là phức tạp, vì vậy nó không có tầm quan trọng, và các kiểu giá trị trông giống như chúng không hoạt động chút nào.

Đối với các loại giá trị, CreateDelegate triển lãm hành vi thực sự kỳ lạ:

var func37 = (Func<CultureInfo,string>)(37.ToString); 
var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null); 
var func42 = (Func<CultureInfo,string>)Delegate.CreateDelegate(typeof(Func<CultureInfo,string>), 42, toStringMethod,true); 
Console.WriteLine(object.ReferenceEquals(func37.Method,func42.Method)); //true 
Console.WriteLine(func37.Target);//37 
Console.WriteLine(func42.Target);//42 
Console.WriteLine(func37(CultureInfo.InvariantCulture));//37 
Console.WriteLine(func42(CultureInfo.InvariantCulture));//-201040128... WTF? 

Calling CreateDelegate với null như các đối tượng mục tiêu ném một ngoại lệ ràng buộc nếu phương pháp dụ thuộc về một kiểu giá trị (chỉ hoạt động này với nhiều loại tài liệu tham khảo).

Một vài năm theo dõi sau: Mục tiêu sai-bound khiến func42(CultureInfo.InvariantCulture); trở "-201040128" thay vì "42" trong ví dụ của tôi là bộ nhớ tham nhũng mà có thể cho phép thực thi mã từ xa (cve-2010-1898); điều này đã được khắc phục vào năm 2010 trong bản cập nhật bảo mật ms10-060. Các khung hiện hành in chính xác 42! Điều đó không trả lời câu hỏi này dễ dàng hơn, nhưng giải thích hành vi đặc biệt kỳ lạ trong ví dụ.

Trả lời

9

Bạn đã thực sự lựa chọn một ví dụ đặc biệt khó khăn, vì hai lý do:

  • ToString() là một phương pháp ảo thừa hưởng từ object nhưng bị ghi đè trong Int32.
  • int là một loại giá trị, và có những quy tắc lạ với Delegate.CreateDelegate() khi nói đến các kiểu giá trị và phương pháp dụ - về cơ bản các thông số hiệu quả đầu tiên trở thành ref int hơn int

Tuy nhiên, đây là một ví dụ cho String.ToUpper, mà không có một trong những vấn đề:

using System; 
using System.Reflection; 

class Test 
{ 
    static void Main() 
    { 
     MethodInfo method = typeof(string).GetMethod 
      ("ToUpper", BindingFlags.Instance | BindingFlags.Public, 
      null, new Type[]{}, null); 

     Func<string, string> func = (Func<string, string>) 
      Delegate.CreateDelegate(typeof(Func<string, string>), 
            null, 
            method); 

     string x = func("hello"); 

     Console.WriteLine(x); 
    } 
} 

Nếu đó là đủ tốt cho bạn, tuyệt vời ... nếu bạn thực sự muốn int.ToString, tôi sẽ phải cố gắng một chút khó khăn hơn :)

Dưới đây là một ví dụ cho một loại giá trị, sử dụng một loại đại biểu mới có tham số đầu tiên của mình bằng cách tham khảo:

using System; 
using System.Reflection; 

public struct Foo 
{ 
    readonly string value; 

    public Foo(string value) 
    { 
     this.value = value; 
    } 

    public string DemoMethod() 
    { 
     return value; 
    } 
} 

class Test 
{ 
    delegate TResult RefFunc<TArg, TResult>(ref TArg arg); 

    static void Main() 
    { 
     MethodInfo method = typeof(Foo).GetMethod 
      ("DemoMethod", BindingFlags.Instance | BindingFlags.Public, 
      null, new Type[]{}, null); 
     RefFunc<Foo, string> func = (RefFunc<Foo, string>) 
      Delegate.CreateDelegate(typeof(RefFunc<Foo, string>), 
            null, 
            method); 

     Foo y = new Foo("hello"); 
     string x = func(ref y); 

     Console.WriteLine(x); 
    } 
} 
+1

Đây là một trong những trường hợp mà nó trở nên rõ ràng rằng C# vẫn còn chỗ để phát triển như một ngôn ngữ chức năng. Xử lý các chức năng như công dân hạng nhất vẫn không liền mạch như chúng ta có thể muốn. Có cách nào để khai thác các tính năng động trong C# 4 để làm việc này dễ dàng hơn không? – LBushkin

+1

@LBushkin: Tôi không nghĩ vậy. Trong thực tế, gõ động và lambdas không đi quá tốt với nhau để bắt đầu - trình biên dịch phải biết loại nào để chuyển đổi biểu thức lambda thành thời gian biên dịch. –

+0

Tôi * đã * được thử nghiệm trên int.ToString, nhưng để sử dụng thực tế tôi cho rằng tôi có thể làm mà không có các phương thức ảo - mặc dù không phải không có cấu trúc. Nhưng dù sao, cảm ơn cho người đứng đầu, tôi đã bỏ qua sự phức tạp của các phương pháp ảo, và thông báo lỗi không chính xác thông tin ... –

3

Tôi không chắc chắn, nhưng có thể là Open delegates có thể giúp bạn.

Cập nhật: Theo dõi link này, nếu trang đầu tiên không hoạt động.

+0

liên kết đó dẫn đến một trang 404 - có thể bạn gõ sai? –

+0

Liên kết hoạt động tốt cho tôi. –

+1

Thật kỳ lạ, việc nhấp vào liên kết sẽ đưa tôi đến trang 404, làm mới trang sẽ hiển thị cùng một 404 - Nhưng nhấn enter trong thanh url rồi (ví dụ: xóa người giới thiệu) sẽ trả về trang. Một số lỗi Trình duyệt/Máy chủ, rõ ràng (chỉ có ở FF3.5.1 - chrome hoạt động tốt). Nhưng dù sao, tìm thấy trang bây giờ ;-) –

0

Cách goog có thể sử dụng loại "động" trong .NET 4.0. Tuy nhiên, Delegate cần thể hiện (đối với các phương thức không tĩnh). Những vấn đề phức tạp hơn sau đó lokks lúc đầu vì polymorfism vv ...

2

Bạn có thể sử dụng Lambdas để có được một wrapper tĩnh "hơi" được biên soạn cho phương pháp dụ của bạn.

Mẫu bên dưới không phải là chính xác nhanh chóng, nhưng nó phải nhanh hơn đáng kể so với bất kỳ thao tác động đơn giản nào.

Sản lượng

100000 iterations took 4 ms 
1000000 iterations took 18 ms 
10000000 iterations took 184 ms 

class Program 
{ 

    public sealed class Test 
    { 
     public String Data { get; set; } 
     public override string ToString() 
     { 
     return Data; 
     } 
    } 

    static void Main(string[] args) 
    { 
     TestRun(100000); 
     TestRun(1000000); 
     TestRun(10000000); 
    } 

    private static void TestRun(int iterations) 
    { 
     var toString = typeof(Test).GetMethod("ToString", 
              BindingFlags.Instance 
              | BindingFlags.Public, 
              null, 
              Type.EmptyTypes, 
              null); 
     var call = GetCall<Test, String>(toString); 
     var tests 
     = (from i in Enumerable.Range(1, iterations) 
      select new Test { Data = "..." + i }).ToList(); 

     var sw = Stopwatch.StartNew(); 
     tests.ForEach(i => call(i)); 
     sw.Stop(); 
     Console.WriteLine("{0} iterations took {1} ms", iterations, sw.ElapsedMilliseconds); 
    } 

    private static Func<T, M> GetCall<T, M>(MethodInfo methodInfo) 
    { 
     var input = Expression.Parameter(typeof(T), "input"); 
     MethodCallExpression member = Expression.Call(input, methodInfo); 
     var lambda = Expression.Lambda<Func<T, M>>(member, input); 

     return lambda.Compile(); 
    } 
} 
+0

Đó là một ý tưởng gọn gàng, cảm ơn! –

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