Могу ли я установить значение структуры через отражение без бокса?

На самом деле, я должен был спросить: как я могу сделать это и оставаться совместимым с CLS? Потому что единственный способ, которым я могу это сделать, заключается в следующем, но использование __makeref, FieldInfo.SetValueDirect или просто System.TypedReference в целом делает недействительным CLS Compliance.

// 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, насколько мне известно, не существует. Есть ли другой способ установить поле (ссылки на) структуры через отражение?


person Abel    schedule 29.03.2012    source источник


Ответы (3)


Сделайте cls-совместимую оболочку для 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);
  }
}
person Serj-Tm    schedule 29.03.2012
comment
Возможно, я не понимаю CLSCompliancy, я думал, что это означает, что вы не можете использовать несовместимые функции. Если это разрешено, это действительно делает все намного проще. - person Abel; 29.03.2012
comment
@Abel: Совместимость с CLS означает, что ваши общедоступные члены ссылаются только на типы, совместимые с CLS. Он ничего не говорит о том, что содержится в ваших членах. - person Gabe; 29.03.2012
comment
Упс, мой комментарий-редактирование потерялся. Да, я заметил, это ясно из трех основных пунктов на MSDN по CLS Compliance< /а>. Тем не менее кажется ужасно странным, что мне нужно недокументированное ключевое слово __makeref чтобы заставить это работать. - person Abel; 29.03.2012
comment
@Abel Может быть, вы путаете cls-совместимый/не-cls-совместимый с безопасным/небезопасным. Для кода, совместимого с cls, подписи открытых членов должны быть только cls-совместимыми, но для безопасного кода должны быть безопасными и тела методов. - person Serj-Tm; 29.03.2012

Для свойств, если у вас есть типы структуры и свойств, вы можете создать делегата из установщика свойств. Как вы указываете, у полей нет сеттеров, но вы можете создать тот, который ведет себя точно так же:

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);
    }
}
person Community    schedule 29.03.2012
comment
Да, это правда, но мой вопрос о полях, а не о свойствах. Небольшое, но важное отличие: поля не имеют специального сеттера. - person Abel; 29.03.2012
comment
@Abel Внезапно это кажется менее элегантным, но все еще работает с полями. (Вы можете кэшировать созданные методы, если вы их часто используете.) - person ; 29.03.2012
comment
Итак, теперь у нас есть (1) __makeref недокументированное ключевое слово, (2) выдача кодов операций IL. Не уверен, какой из двух я предпочитаю, они оба кажутся слишком запутанными. Но Emit соответствует CLS, а TypedReference — нет. - person Abel; 29.03.2012
comment
+1 Это потрясающий ответ! Я знал, что можно избавиться от столь раздражающего __makeref, и вот оно! - person Sergey Kalinichenko; 07.09.2012
comment
Я попробовал ваш метод propertySetter и увидел значительное улучшение производительности по сравнению со стандартным методом PropertyInfo.GetSetter.Invoke. - person Jonathan Allen; 24.11.2017

Не уверен, что это будет соответствовать вашим ограничениям, но если объявить экземпляр структуры как ValueType, SetValue будет работать так, как ожидалось.

    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

См. этот ответ для получения дополнительной информации.

person Ian Horwill    schedule 29.03.2012
comment
+1: Ваш ответ, безусловно, интересен, но ваш код не работает с исходной структурой. Вместо этого он упаковывается для SetValue и распаковывается, потому что вы приводите его для использования с WriteLine. Чтобы убедиться в этом, проверьте IL. - person Abel; 29.03.2012
comment
Я не смотрел на IL, но убедился, что последняя строка действительно отображает 4096, так как же она может не работать в исходном экземпляре? - person Ian Horwill; 29.03.2012
comment
Хорошо, теперь я посмотрел на IL. Я не очень бегло говорю, но я вижу телефонные звонки, о которых вы говорите. В разделе locals он объявляет два экземпляра TestFields, тогда как C# имеет только один. Очень странно. Вас интересует фактическая упаковка или правильность установки значения? - person Ian Horwill; 29.03.2012
comment
как он может не работать с исходным экземпляром: в ту минуту, когда вы передаете тип значения методу, который принимает тип object, или когда вы вызываете метод экземпляра (т.е. 1.ToString()), тип valye в коробке. Вы можете форсировать бокс с помощью object o = anyvaluetype, который можно использовать вместо первой строки. Поскольку (странно, я знаю) ValueType является не типом значения, а классом, вы заставили боксировать в первой строке. В результате SetValue не выполняет дополнительную упаковку, а работает с объектом fields. Приведение этого объекта обратно к структуре распаковывает его. - person Abel; 29.03.2012
comment
Спасибо, что прояснили это. Меня доставало. Звучит очевидно, когда так ясно объяснено. - person Ian Horwill; 31.03.2012
comment
Некоторым людям нравится делать вид, что типы значений происходят от System.ValueType, которое, в свою очередь, происходит от System.Object; это только половина правды. Экземпляр объекта типа значения действительно будет соответствовать этому описанию, но места хранения типа значения не содержат такие экземпляры. Вместо этого они просто содержат содержимое всех открытых и закрытых полей, которые должны содержаться в таких объектах. Существует расширяющее преобразование каждого типа хранилища типа значения в соответствующий ему тип объекта-экземпляра и сужающее обратное преобразование из System.Object, интерфейсных типов или дженериков, ограниченных классом. - person supercat; 02.04.2012
comment
Обратите внимание, что, хотя в спецификации языка C# указано, что объект, хранящийся в хранилище типа значения, имеет тот же тип, что и экземпляр объекта типа значения, и хотя оба они описываются одним и тем же объектом System.Type, на самом деле они ведут себя как хотя это разные виды. Например, приведение от последнего к Object сохраняет ссылку, а приведение от первого к Object — нет. Обратите внимание, что на самом деле нельзя вызвать GetType для хранилища типа значения. Вместо этого будет создан новый экземпляр объекта его типа, и для этого будет вызван GetType. - person supercat; 02.04.2012