【问题标题】:C# FieldInfo.SetValue with an array parameter and arbitrary element type具有数组参数和任意元素类型的 C# FieldInfo.SetValue
【发布时间】:2012-12-09 18:46:02
【问题描述】:

我正在尝试使用反射设置一个数组字段,如下所示:

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)

【问题讨论】:

  • 要么我不明白,要么这是不可能的。该字段不能是 B[] 类型,其中 B 在编译时未知。

标签: c# arrays reflection system.type fieldinfo


【解决方案1】:

我认为您需要更好地了解数组的方差。像 object[] 和 string[] 这样的数组已经出现在 .NET 1 中,比泛型 (.NET 2) 早了很长时间。否则它们可能被称为Array<object> 和Array<string>。

现在,我们都知道任何string 都是object。如果这个事实暗示,对于某些“构造”Xxx,任何Xxx<string> 都是Xxx<object>,那么我们称之为covaraince。从 .NET 4 开始,一些泛型接口和泛型委托类型可以协变,并且在其定义中用 out 关键字标记,如

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

如果在“应用”Xxx&lt;&gt; 时关系反转,则称为逆变。所以“string is object”和逆变变成了“Xxx&lt;object&gt; is Xxx&lt;string&gt;”。

现在,回到数组。这里自然的问题是:数组是协变的还是逆变的,或者两者都不是(“不变”)?由于您可以读取和写入数组中的每个“单元”,因此它们不能完全协变或逆变。但是试试这个代码:

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[]。由于 .NET 数组的协方差,他仍然可以有返回类型 Component[]。

经验教训:数组的 real 类型(.GetType() 揭示的运行时类型)不会因为您进行转换而改变。 .NET 确实允许协方差(Component[] 类型的变量可能包含一个实际类型为AbilityResult[] 的对象)。最后,.NET不允许允许逆变(AbilityResult[] 类型的变量从不包含对真实类型Component[] 的对象的引用)。

希望这会有所帮助。否则,我的回答应该会给你一些术语,你可以在谷歌上找到比我更好的解释。

【讨论】:

  • 谢谢,我想我理解了这个问题。从 Component[] 到 AbilityResult[] 的转换是不可能的,因为逆变是被禁止的,但是 field.SetValue 需要该字段类型的值。这意味着除了创建所需类型 AbilityResult[] 的新数组并将所有元素复制到其中之外可能别无他法(因为第一个 Component[] 数组最初包含 AbilityResult 对象,这将起作用)
【解决方案2】:

经过一番搜索,我偶然发现了这个问题:How do I create a C# array using Reflection and only type info?

我的问题的可能解决方案是:

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 个元素),但如果有一个更清洁的解决方案,仍然会很高兴。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-28
    • 2017-10-11
    • 2018-08-02
    • 2011-01-19
    • 2014-12-25
    • 2016-05-13
    • 1970-01-01
    • 2021-11-14
    相关资源
    最近更新 更多