【问题标题】:Can I set a value on a struct through reflection without boxing?我可以在不装箱的情况下通过反射在结构上设置值吗?
【发布时间】:2012-03-29 14:36:38
【问题描述】:

实际上,我应该问:我怎样才能做到这一点并且保持 CLS 兼容?因为我能想到的唯一方法如下,但使用__makerefFieldInfo.SetValueDirect 或仅使用System.TypedReference 通常会使 CLS 合规性无效。

// 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);

SetValueDirect 的兼容对应物是SetValue,但它以对象为目标,因此我的结构将被装箱,使我在副本上设置一个值,而不是在原始变量上。

据我所知,SetValue 的通用对应项不存在。有没有其他方法可以通过反射设置(对a的引用)结构的字段?

【问题讨论】:

    标签: c# reflection struct boxing


    【解决方案1】:

    在 SetValueDirect 上制作符合 cls 的包装器:

      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);
      }
    }
    

    【讨论】:

    • 也许我不理解 CLSCompliancy,我认为这意味着您不能使用 不兼容的功能。如果允许这样做,确实会让事情变得容易得多。
    • @Abel:符合 CLS 意味着您的公共成员仅引用符合 CLS 的类型。它没有说明您的成员中包含的内容。
    • 糟糕,我的评论编辑丢失了。是的,我注意到,从top three bullets on MSDN on CLS Compliance 中可以清楚地看到。仍然感觉非常奇怪,我需要一个 undocumented keyword __makeref 才能让它工作。
    • @Abel 也许你把 clscompliant/non-clscompliant 和 safe/non-safe 混为一谈了。对于符合 cls 的代码,则公共成员签名必须仅符合 cls,但对于安全的代码,方法体也必须是安全的
    【解决方案2】:

    对于属性,如果你有结构和属性类型,你可以从属性设置器创建一个委托。正如您所指出的,字段没有设置器,但您可以创建一个行为完全相同的设置器:

    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);
        }
    }
    

    【讨论】:

    • 是的,没错,但我的问题是关于字段,而不是关于属性。细微但重要的区别:字段没有专门的设置器。
    • @Abel 突然间它看起来不那么优雅了,但它仍然适用于字段。 (如果你经常使用它们,你可以缓存创建的方法。)
    • 所以现在我们有 (1) __makeref 未记录的关键字,(2) 发出 IL 操作码。不知道我更喜欢哪一个,它们看起来都太复杂了。但Emit 符合 CLS 而TypedReference 不符合。
    • +1 这是一个了不起的答案!我知道可以摆脱如此烦人的__makeref,就在这里!
    • 我尝试了您的 propertySetter 方法,发现与使用标准 PropertyInfo.GetSetter.Invoke 方法相比,性能有了巨大的提升。
    【解决方案3】:

    不确定这是否符合您的约束条件,但通过将结构实例声明为 ValueTypeSetValue 将按预期工作。

        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
    

    有关更多信息,请参阅this answer

    【讨论】:

    • +1:您的回答当然很有趣,但是您的代码确实没有对原始结构进行操作。相反,它为 SetValue 装箱并取消装箱,因为您将其强制转换为与 WriteLine 一起使用。要亲自查看,请查看 IL。
    • 我没有看IL但是我验证了最后一行确实显示了4096,那么它怎么可能不在原始实例上运行呢?
    • 好的,现在我确实查看了 IL。我不是很流利,但我看到了你所说的盒子电话。在 locals 部分,当 C# 只有一个时,它声明了两个 TestFields 实例。很奇怪。是您关心的实际拳击还是正确设置的值?
    • > “它怎么不能在原始实例上运行”:将值类​​型传递给接受类型object的方法的那一刻,或者当你调用一个实例方法(即1.ToString()),valye 类型被装箱。您可以使用object o = anyvaluetype 强制装箱,它可以用来代替您的第一行。因为(奇怪,我知道)ValueType 不是 值类型而是一个类,所以您在第一行强制装箱。结果,SetValue 没有进行额外的装箱,而是对 object fields 进行操作。将此对象转换回结构会取消装箱。
    • 感谢您解决这个问题。一直在烦我。解释得这么清楚听起来很明显。
    猜你喜欢
    • 1970-01-01
    • 2016-12-16
    • 2015-09-23
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    相关资源
    最近更新 更多