2012-03-29 15 views
9

Thực ra, tôi nên hỏi: làm cách nào tôi có thể thực hiện điều này vẫn tuân thủ CLS? Bởi vì cách duy nhất tôi có thể nghĩ về việc này là như sau, nhưng sử dụng hoặc __makeref, FieldInfo.SetValueDirect hoặc chỉ System.TypedReference nói chung vô hiệu hóa tuân thủ CLS.Tôi có thể đặt giá trị trên cấu trúc thông qua phản xạ không có quyền anh không?

// code illustrating the issue: 
TestFields fields = new TestFields { MaxValue = 1234 }; // test struct with one field 

FieldInfo info = fields.GetType().GetField("MaxValue"); // get the FieldInfo 

// actual magic, no boxing, not CLS compliant: 
TypedReference reference = __makeref(fields); 
info.SetValueDirect(reference, 4096); 

Các đối tác phù hợp của SetValueDirectSetValue, nhưng phải mất một đối tượng như mục tiêu, do đó cấu trúc của tôi sẽ được đóng hộp, làm cho tôi đặt một giá trị trên một bản sao, không phải là biến ban đầu.

Một đối tác chung cho SetValue không tồn tại như tôi biết. Có cách nào khác để thiết lập các lĩnh vực của một (tham chiếu đến a) struct thông qua sự phản ánh?

Trả lời

5

Hãy wrapper cls tuân thủ trên SetValueDirect:

var item = new MyStruct { X = 10 }; 

    item.GetType().GetField("X").SetValueForValueType(ref item, 4); 


[CLSCompliant(true)] 
static class Hlp 
{ 
    public static void SetValueForValueType<T>(this FieldInfo field, ref T item, object value) where T : struct 
    { 
    field.SetValueDirect(__makeref(item), value); 
    } 
} 
+0

Có lẽ tôi không hiểu CLSCompliancy, tôi nghĩ rằng nó có nghĩa là bạn không thể _use_ tính năng không tuân thủ. Nếu điều này được cho phép, nó làm cho mọi thứ dễ dàng hơn rất nhiều. – Abel

+1

@Abel: CLS Compliant có nghĩa là các thành viên công cộng của bạn chỉ tham khảo các loại tuân thủ CLS. Nó không nói gì về những gì có trong các thành viên của bạn. – Gabe

+0

Rất tiếc, nhận xét của tôi đã bị mất. Có, tôi nhận thấy, rõ ràng từ [ba dấu đầu dòng trên MSDN về Tuân thủ CLS] (http://msdn.microsoft.com/en-us/library/bhc3fa7f.aspx). Tuy nhiên nó cảm thấy hết sức kỳ lạ mà tôi cần một [từ khóa không có giấy tờ __makeref] (http://www.codeproject.com/Articles/38695/UnCommon-C-keywords-A-Look#makref) để có được điều này để làm việc. – Abel

2

Không chắc chắn điều này có phù hợp với các ràng buộc của bạn hay không, nhưng bằng cách khai báo cá thể struct là ValueType, SetValue sẽ hoạt động như mong đợi.

ValueType fields = new TestFields { MaxValue = 1234 }; // test struct with one field 
    FieldInfo info = typeof(TestFields).GetField("MaxValue"); // get the FieldInfo 
    info.SetValue(fields, 4096); 
    Console.WriteLine(((TestFields)fields).MaxValue); // 4096 

Xem this answer để biết thêm thông tin.

+1

+1: Câu trả lời của bạn chắc chắn thú vị, nhưng mã của bạn không hoạt động trên cấu trúc ban đầu. Thay vào đó, nó được đóng hộp cho SetValue và unboxed bởi vì bạn cast nó để sử dụng với WriteLine. Để thấy điều đó cho chính mình, hãy kiểm tra IL. – Abel

+0

Tôi đã không nhìn vào IL nhưng tôi xác minh rằng dòng cuối cùng thực sự hiển thị 4096, vậy làm thế nào nó có thể không được hoạt động trên trường hợp ban đầu? –

+0

OK, giờ tôi đã xem IL. Tôi không thông thạo lắm nhưng tôi thấy các cuộc gọi mà bạn đang nói đến. Từ phần người dân địa phương, nó tuyên bố hai trường hợp của TestFields khi C# chỉ có một. Rất lạ. Đó có phải là boxing thực sự mà bạn quan tâm hoặc giá trị được đặt chính xác không? –

6

Đối với tài sản, nếu bạn có các loại cấu trúc và tài sản, bạn có thể tạo ra một đại biểu từ các setter tài sản. Như bạn đã chỉ ra, các trường không có trình cài đặt, nhưng bạn có thể tạo các trường hoạt động giống hệt nhau:

delegate void RefAction<T1, T2>(ref T1 arg1, T2 arg2); 

struct TestFields 
{ 
    public int MaxValue; 

    public int MaxValueProperty 
    { 
     get { return MaxValue; } 
     set { MaxValue = value; } 
    } 
}; 

static class Program 
{ 
    static void Main(string[] args) 
    { 
     var propertyInfo = typeof(TestFields).GetProperty("MaxValueProperty"); 
     var propertySetter = (RefAction<TestFields, int>)Delegate.CreateDelegate(typeof(RefAction<TestFields, int>), propertyInfo.GetSetMethod()); 

     var fieldInfo = typeof(TestFields).GetField("MaxValue"); 

     var dynamicMethod = new DynamicMethod(String.Empty, typeof(void), new Type[] { fieldInfo.ReflectedType.MakeByRefType(), fieldInfo.FieldType }, true); 
     var ilGenerator = dynamicMethod.GetILGenerator(); 
     ilGenerator.Emit(OpCodes.Ldarg_0); 
     ilGenerator.Emit(OpCodes.Ldarg_1); 
     ilGenerator.Emit(OpCodes.Stfld, fieldInfo); 
     ilGenerator.Emit(OpCodes.Ret); 
     var fieldSetter = (RefAction<TestFields, int>)dynamicMethod.CreateDelegate(typeof(RefAction<TestFields, int>)); 

     var fields = new TestFields { MaxValue = 1234 }; 
     propertySetter(ref fields, 5678); 
     fieldSetter(ref fields, 90); 
     Console.WriteLine(fields.MaxValue); 
    } 
} 
+0

Vâng, đó là sự thật, nhưng câu hỏi của tôi là về các lĩnh vực chứ không phải về tài sản. Sự khác biệt nhỏ nhưng quan trọng: các trường không có trình thiết lập chuyên dụng. – Abel

+0

Rất tiếc, đúng, xin lỗi về điều đó, sẽ chỉnh sửa. – hvd

+0

@Abel Đột nhiên nó có vẻ kém thanh lịch hơn, nhưng nó vẫn hoạt động với các trường. (Bạn có thể cache các phương thức đã tạo nếu bạn sử dụng chúng rất nhiều.) – hvd