c#中string[]能隐式转为object[]是因clr支持引用类型一维数组的协变,但写入非string元素会抛arraytypemismatchexception;值类型数组不支持,泛型集合因类型安全不支持协变。
![c# 数组协变性问题 c#为什么string[]可以转换为object[]](https://img.php.cn/upload/article/001/221/864/177414672437715.png?x-oss-process=image/resize,p_40)
为什么 string[] 能隐式转成 object[]?
这是 C# 对引用类型数组启用的「数组协变(array covariance)」,属于语言层面的特殊规则,和泛型无关。它允许将子类型数组(如 string[])当作父类型数组(如 object[])使用——但仅限于**一维数组**,且**仅对引用类型生效**。
根本原因是 .NET 运行时(CLR)在底层把所有引用类型数组都视为 System.Array 的子类,并共享同一套内存布局和运行时类型检查逻辑。编译器在编译期允许这种转换,但会在运行时插入类型检查来防止不安全写入。
string[] → object[] 转换后写入会出什么错?
转换本身成功,但一旦尝试往这个 object[] 引用里存非 string 的对象,就会在运行时抛出 ArrayTypeMismatchException。
例如:
string[] strs = { "a", "b" };
object[] objs = strs; // ✅ 编译通过,运行时也 OK
objs[0] = new object(); // ❌ 运行时报错:ArrayTypeMismatchException
- 这是因为底层数组实际类型仍是
string[],CLR 在赋值前会检查new object()是否能被安全存入string[] - 这种检查只发生在写入时,读取永远安全(
object o = objs[0];没问题) - 值类型数组(如
int[])完全不支持协变:int[]无法转成object[],编译直接报错
和泛型集合(如 List<t></t>)为什么不协变?
因为泛型是「类型安全优先」的设计,编译器在编译期就封死了不安全路径。而数组协变是历史包袱(为兼容早期 Java 风格设计),CLR 选择用运行时检查兜底。
对比:
List<string> list = new List<string>(); // 下面这行编译失败: // IList<object> ilist = list;</object></string></string>
-
IList<t></t>是可变接口,如果允许协变,ilist.Add(new object())就会破坏List<string></string>的类型约束 - 但
IEnumerable<out t></out>声明了out,只读、协变安全,所以IEnumerable<string></string>可隐式转IEnumerable<object></object> - 数组没这种泛型机制,只能靠运行时硬拦写操作
实际开发中该不该依赖数组协变?
尽量避免。它容易掩盖类型意图,且运行时异常比编译期错误更难调试。
- 传参时若函数只需要读数组内容,优先用
IReadOnlyList<object></object>或IEnumerable<object></object>,明确表达只读语义 - 需要写入时,老老实实用
object[]初始化,或用Array.ConvertAll(strs, s => (object)s)显式复制 - 注意 LINQ 方法(如
.ToArray())返回的是新数组,不会继承原数组的协变“身份”,返回类型严格匹配泛型参数
协变数组的坑不在转换那一刻,而在你忘了它背后还是那个窄类型的容器——只要写,就可能崩。











