2010-03-11 33 views
49

Tôi đã gặp phải sự cố khác khi sử dụng C# 4.0 với các tham số tùy chọn.Gọi phương thức với tham số tùy chọn thông qua phản chiếu

Làm cách nào để gọi hàm (hoặc đúng hơn là hàm tạo, tôi có đối tượng ConstructorInfo) mà tôi biết nó không yêu cầu bất kỳ tham số nào?

Đây là mã tôi sử dụng bây giờ:

type.GetParameterlessConstructor() 
    .Invoke(BindingFlags.OptionalParamBinding | 
      BindingFlags.InvokeMethod | 
      BindingFlags.CreateInstance, 
      null, 
      new object[0], 
      CultureInfo.InvariantCulture); 

(Tôi vừa mới thử với nhau BindingFlags).

GetParameterlessConstructor là phương pháp tiện ích tùy chỉnh mà tôi đã viết cho Type.

Trả lời

106

Theo MSDN, sử dụng thông số mặc định, bạn phải vượt qua Type.Missing.

Nếu hàm tạo của bạn có ba đối số tùy chọn thì thay vì truyền một mảng đối tượng trống, bạn vượt qua một mảng đối tượng ba phần tử, trong đó mỗi giá trị của phần tử là Type.Missing, ví dụ:

type.GetParameterlessConstructor() 
    .Invoke(BindingFlags.OptionalParamBinding | 
      BindingFlags.InvokeMethod | 
      BindingFlags.CreateInstance, 
      null, 
      new object[] { Type.Missing, Type.Missing, Type.Missing }, 
      CultureInfo.InvariantCulture); 
+3

Câu trả lời này thực sự tốt hơn câu trả lời được đánh dấu là đúng! –

+0

Tôi có thể chứng thực rằng tác phẩm này hoạt động. Tôi đồng ý rằng đây là giải pháp tốt hơn trong hầu hết các trường hợp và có lẽ nên được đánh dấu như vậy. – N8allan

+0

Bạn có thể gọi đơn giản là "Gọi" (không có tham số) trên 'MethodInfo' hoặc' ConstructorInfo' kết quả không? – Alxandr

22

Tham số tùy chọn được biểu thị bằng thuộc tính thông thường và được trình biên dịch xử lý.
Chúng không có hiệu lực (ngoài cờ siêu dữ liệu) trên IL và không được hỗ trợ trực tiếp bởi sự phản chiếu (ngoại trừ các thuộc tính IsOptionalDefaultValue).

Nếu bạn muốn sử dụng các tham số tùy chọn có phản xạ, bạn cần phải chuyển giá trị mặc định của họ theo cách thủ công.

+0

Cảm ơn bạn. Tôi đã đưa ra một giải pháp thực hiện chính xác điều đó. Nó trông không đẹp lắm, nhưng nó hoạt động. Ngoài ra, IsOptional không phải là tài sản duy nhất, DefaultValue cũng tồn tại, vì vậy tôi chỉ xây dựng một mảng trong tất cả các DefaultValues. – Alxandr

1

Với khuôn khổ opensource ImpromptuInterface như các phiên bản 4 bạn có thể sử dụng DLR trong C# 4.0 để gọi constructor trong một very late bound way và nó hoàn toàn nhận thức được các nhà xây dựng với tên/đối số tùy chọn, điều này chạy nhanh hơn Activator.CreateInstance(Type type, params object[] args) 4 lần và bạn don' t phải phản ánh các giá trị mặc định.

using ImpromptuInterface; 
using ImpromptuInterface.InvokeExt; 

...

//if all optional and you don't want to call any 
Impromptu.InvokeConstructor(type) 

hoặc

//If you want to call one parameter and need to name it 
Impromptu.InvokeConstructor(type, CultureInfo.InvariantCulture.WithArgumentName("culture")) 
3

Tôi sẽ chỉ thêm một số mã ... vì. Mã không vui, tôi đồng ý, nhưng nó khá thẳng về phía trước. Hy vọng rằng điều này sẽ giúp một người tình cờ hiểu được điều này. Nó được thử nghiệm, mặc dù có lẽ không cũng như bạn muốn trong một môi trường sản xuất:

gọi method methodName trên đối tượng obj với đối số args:

public Tuple<bool, object> Evaluate(IScopeContext c, object obj, string methodName, object[] args) 
    { 
     // Get the type of the object 
     var t = obj.GetType(); 
     var argListTypes = args.Select(a => a.GetType()).ToArray(); 

     var funcs = (from m in t.GetMethods() 
        where m.Name == methodName 
        where m.ArgumentListMatches(argListTypes) 
        select m).ToArray(); 

     if (funcs.Length != 1) 
      return new Tuple<bool, object>(false, null); 

     // And invoke the method and see what we can get back. 
     // Optional arguments means we have to fill things in. 
     var method = funcs[0]; 
     object[] allArgs = args; 
     if (method.GetParameters().Length != args.Length) 
     { 
      var defaultArgs = method.GetParameters().Skip(args.Length) 
       .Select(a => a.HasDefaultValue ? a.DefaultValue : null); 
      allArgs = args.Concat(defaultArgs).ToArray(); 
     } 
     var r = funcs[0].Invoke(obj, allArgs); 
     return new Tuple<bool, object>(true, r); 
    } 

Và ArgumentListMatches chức năng là dưới đây, về cơ bản có các vị trí của logic có thể được tìm thấy trong GetMethod:

public static bool ArgumentListMatches(this MethodInfo m, Type[] args) 
    { 
     // If there are less arguments, then it just doesn't matter. 
     var pInfo = m.GetParameters(); 
     if (pInfo.Length < args.Length) 
      return false; 

     // Now, check compatibility of the first set of arguments. 
     var commonArgs = args.Zip(pInfo, (margs, pinfo) => Tuple.Create(margs, pinfo.ParameterType)); 
     if (commonArgs.Where(t => !t.Item1.IsAssignableFrom(t.Item2)).Any()) 
      return false; 

     // And make sure the last set of arguments are actually default! 
     return pInfo.Skip(args.Length).All(p => p.IsOptional); 
    } 

Rất nhiều LINQ, và điều này chưa được kiểm tra hiệu suất!

Ngoài ra, điều này sẽ không xử lý các chức năng hoặc cuộc gọi phương thức chung. Điều đó làm cho điều này xấu hơn đáng kể (như trong các cuộc gọi GetMethod lặp đi lặp lại).

1

Tất cả các câu hỏi biến mất khi bạn nhìn thấy mã của bạn decompiled:

C#:

public MyClass([Optional, DefaultParameterValue("")]string myOptArg) 

MSIL:

.method public hidebysig specialname rtspecialname instance void .ctor([opt]string myOptArg) cil managed 

Như bạn thấy, tham số tùy chọn là một thực thể riêng biệt thực sự mà được trang trí với các thuộc tính cụ thể và phải được tôn trọng cho phù hợp khi gọi qua phản xạ, như được mô tả trước đó.

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