C# FieldInfo.SetValue с параметром массива и произвольным типом элемента

Я пытаюсь установить поле массива, используя отражение следующим образом:

FieldInfo field = ...
A[] someArray = GetElementsInSomeWay();
field.SetValue(this, someArray);

Поле имеет тип B[]. B наследуется от A, а точный тип B во время компиляции неизвестен. GetElementsInSomeWay() возвращает A[], но все реальные элементы внутри являются B. GetElementsInSomeWay() является библиотечным методом и не может быть изменен.

Максимум, что я могу сделать, это получить B с System.Type type = field.FieldType.GetElementType(). Однако я не могу привести массив к требуемому типу, например. someArray as type[], потому что [] требует точного типа перед объявлением типа массива. Или я что-то здесь упускаю? Могу ли я объявить массив какого-либо типа, если тип становится известен во время выполнения с помощью переменной System.Type?

Выполнение этого прямым способом приводит к следующей ошибке (здесь A — это UnityEngine.Component, а B — это AbilityResult, который также может быть одним из нескольких десятков других классов, унаследованных (возможно, через длинную цепочку наследования) от UnityEngine.Component):

ArgumentException: Object type UnityEngine.Component[] cannot be converted to target type: AbilityResult[]
Parameter name: val
System.Reflection.MonoField.SetValue (System.Object obj, System.Object val, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Globalization.CultureInfo culture) (at /Applications/buildAgent/work/3df08680c6f85295/mcs/class/corlib/System.Reflection/MonoField.cs:133)
System.Reflection.FieldInfo.SetValue (System.Object obj, System.Object value) (at /Applications/buildAgent/work/3df08680c6f85295/mcs/class/corlib/System.Reflection/FieldInfo.cs:150)

person iseeall    schedule 09.12.2012    source источник
comment
Либо я не понимаю, либо это невозможно. Поле не может иметь тип B[], где B неизвестен во время компиляции.   -  person Wiktor Zychla    schedule 09.12.2012


Ответы (2)


Я думаю, вам нужно лучше понимать дисперсию массивов. Такие массивы, как object[] и string[], появились уже в .NET 1, задолго до дженериков (.NET 2). В противном случае их можно было бы назвать Array<object> и Array<string>.

Теперь мы все знаем, что любой string является object. Если этот факт подразумевает для некоторой «конструкции» Xxx, что любое Xxx<string> является Xxx<object>, то мы называем это ковариация. Начиная с .NET 4, некоторые универсальные интерфейсы и универсальные типы делегатов могут быть ковариированы, и это помечено ключевым словом out в их определении, как в

public interface IEnumerable<out T>
{
  // ...
}

Если отношение меняется на противоположное при «применении» Xxx<>, то это называется контравариантностью. Таким образом, «string равно object» с контравариантностью становится «Xxx<object> равно Xxx<string>».

Теперь вернемся к массивам. Здесь возникает естественный вопрос: являются ли массивы ковариантными или контравариантными или ни теми, ни другими («инвариантными»)? Поскольку вы можете как читать, так и записывать в каждую «ячейку» массива, они не могут быть полностью ковариантными или контравариантными. Но попробуйте этот код:

string[] arr1 = { "these", "are", "strings", };
object[] arr2 = arr1;                           // works! covariance!
var runtimeType = arr2.GetType();               // System.String[]

Таким образом, T[] является ковариантным в .NET. Но разве я только что не сказал, что могу писать (например, in, а не out) в массив? Что, если я продолжу так:

arr2[0] = new object();

Я пытаюсь поместить object, который не является string, в нулевой слот. Приведенная выше строка должна компилироваться (тип времени компиляции arr2 равен object[]). Но это также должно увеличить время выполнения. Это проблема с массивами и ковариацией.

Так что насчет контравариантности? Попробуй это:

object[] arrA = { new object(), DateTime.Now, "hello", };
string[] arrB = arrA;  // won't compile, no cotravariance

Выполнение явного приведения от object[] к string[] по-прежнему не будет работать во время выполнения.

А теперь ответ на ваш вопрос: вы пытаетесь применить контравариантность к массивам. AbilityResult равно Component, но это не означает, что Component[] равно AbilityResult[]. Тот, кто написал метод GetElementsInSomeWay(), решил создать метод new Component[]. Даже если все компоненты, которые он туда вложил, AbilityResult, контравариантности все равно нет. Вместо этого автор GetElementsInSomeWay() мог сделать new AbilityResult[]. Он все еще мог иметь возвращаемый тип Component[] из-за ковариантности массивов .NET.

Урок, который нужно усвоить: реальный тип массива (тип времени выполнения, раскрытый .GetType()) не изменится только в результате приведения. .NET допускает ковариацию (переменная типа Component[] может содержать объект, реальный тип которого AbilityResult[]). И, наконец, .NET не допускает контравариантность (переменная типа AbilityResult[] никогда не содержит ссылку на объект реального типа Component[]).

Надеюсь это поможет. В противном случае мой ответ должен дать вам некоторые термины, которые вы можете найти в Google, чтобы найти объяснения, превосходящие мои.

person Jeppe Stig Nielsen    schedule 09.12.2012
comment
Спасибо, кажется я понял проблему. Преобразование Component[] в AbilityResult[] невозможно, потому что контравариантность запрещена, но для field.SetValue требуется значение точно такого же типа поля. Это означает, что, вероятно, нет другого пути, кроме как создать новый массив требуемого типа AbilityResult[] и скопировать в него все элементы (что сработает, поскольку первый массив Component[] изначально содержал объекты AbilityResult) - person iseeall; 10.12.2012

После некоторого поиска я наткнулся на этот вопрос: info">Как создать массив C# с помощью Reflection и ввести только информацию?

Возможное решение моей проблемы:

A[] someArray = GetElementsInSomeWay();
System.Type type = field.FieldType.GetElementType();
Array filledArray = Array.CreateInstance(type, someArray.Length);
Array.Copy(someArray, filledArray, someArray.Length);
field.SetValue(this, filledArray);

Я только что проверил, это работает.

Однако я все же хотел бы избежать копирования элементов. В моем случае массивы довольно маленькие (максимум 3-5 элементов), но тем не менее было бы неплохо увидеть более чистое решение, если оно есть.

person iseeall    schedule 09.12.2012