【问题标题】:Does C# compiler hardcode a special processing for IEnumerable? [duplicate]C# 编译器是否对 IEnumerable 的特殊处理进行硬编码? [复制]
【发布时间】:2018-01-09 22:46:59
【问题描述】:

作为示例,使用 String 类中的 Join() 方法。当你调用它时 一个字节数组,例如

 byte[] bytes = {3,4,5};
 string str = string.Join(",", bytes);

C# 编译器将 Join 映射到此签名

 public static String Join<T>(String separator, IEnumerable<T> values);

但是,byte[] 隐式派生自类 Array,该类并非派生自泛型 IEnumerable,而是派生自非泛型 IEnumerable,即

 public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable {...}

如果我对自己的类似于这种情况的接口和类做同样的事情,我会得到 预期的编译器错误,因为根据规则您不能强制转换类 从接口 IA(非通用 IEnumerable)派生到接口 IB(通用 IEnumerable) 源自 IA。这意味着 C# 编译器只是硬编码特定名称 IEnumerable。 这是在哪里解释的?

【问题讨论】:

  • IEnumerable 没有特殊处理,但是 Array 协方差有特殊处理。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
  • byte[] 实际上实现了IEnumerable&lt;byte&gt;(更具体地说,it implements IList&lt;byte&gt;)。神奇之处不在IEnumerable,而在于处理数组的方式。查看Array 的元数据不会告诉你一切。
  • @ScottChamberlain 这里没有数组协方差。
  • @Servy 这就是为什么这是评论而不是答案。对于 OP 来说,这更像是一个仅供参考的方面。
  • The documentation 清楚地解释了某些接口的通用形式的实现,包括 IEnumerable&lt;T&gt; 是由运行时提供的。而且,您的问题已经在之前的 Stack Overflow 帖子中得到了很好的解决。

标签: c#


【解决方案1】:

但是,byte[] 隐式派生自类 Array,该类并非派生自泛型 IEnumerable,而是派生自非泛型 IEnumerable,即

没错,但是byte[] 实现了IEnumerable&lt;byte&gt; 本身,这完全有道理。我猜这是为Foo[] 编译特定类型的编译器功能(所以基本上所有都来自Array)。

查看byte[]上实现的接口(使用Type t = typeof(byte[]);获得,查看属性ImplementedInterfaces):

前6个接口可能来自Array,后5个接口是1-6的通用版本。

【讨论】:

  • 有趣的是,foo[] 实现 IReadOnlyList&lt;foo&gt; 的事实似乎没有在任何地方记录。其他接口来自ArrayIList&lt;foo&gt;(后者的实现记录在案,不,IList&lt;foo&gt; 不继承自IReadOnlyList&lt;foo&gt;)。
  • 据我所知,有一篇关于此的 SO 帖子。
【解决方案2】:

数组在 CLR 中是特殊的。特别是只有引用类型数组是协变的。 byte 等值类型不是。就是那样子。 Eric Lippert 十年前曾就此发表过一篇博文。

String.Join 的可用选项中,与byte[] 匹配的唯一版本是String Join&lt;T&gt;(String, IEnumerable&lt;T&gt;)

byte[] 无法转换为string[]object[]IEnumerable&lt;string&gt;

可用选项...

String Join(String, IEnumerable<String>) 
String Join(String, Object[])
String Join(String, String[]) 
String Join(String, String[], Int32, Int32) 
String Join<T>(String, IEnumerable<T>)

类型错误示例 ...

object[] test1 = new byte[0];
string[] test2 = new byte[0];
IEnumerable<string> test3 = new byte[0];
IEnumerable<byte> test4 = new byte[0];

...上面的编译器错误...

Cannot implicitly convert type 'byte[]' to 'object[]'
Cannot implicitly convert type 'byte[]' to 'string[]'
Cannot implicitly convert type 'byte[]' to  to 'System.Collections.Generic.IEnumerable<string>' 

更多转换示例

Foo[] test = new OtherFoo[0]; // allowed
OtherFoo[] test = new Foo[0]; // not allowed

public class Foo
{
}
public class OtherFoo : Foo
{
}

【讨论】:

  • 请注意,数组类型是由 CLR 按需定义的。他们的程序集总是设置为元素类型来自的程序集,即使这不是真的。
  • 是的,他们甚至拥有自己的Opcode.NewArr,而不是标准的NewObj msdn.microsoft.com/en-us/library/…
  • “与byte[] 匹配的唯一版本是String Join&lt;T&gt;(String, IEnumerable&lt;T&gt;)——没错,但 OP 已经知道这一点。他们在问为什么。并且说诸如 “只有引用类型数组是协变的” 之类的话并不能解决所提出的问题。差异与为什么实施IEnumerable&lt;T&gt; 无关。换句话说,您发布的答案绝对不能解决所提出的问题,因此没有用处。
  • 数组协方差不相关在这种特殊情况下,因为 1) String.Join 对任何 IEnumerable&lt;T&gt; 有一个重载 T,以及 2) 每个数组T[] 在运行时实现 IEnumerable&lt;T&gt;。该问题基于编译器执行某种不安全转换(IEnumerable 的向下转换,而不是数组转换)的不正确的前提,但它没有。 String.Join 的其他重载是红鲱鱼。如果从语言中删除数组协方差,结果/惊喜将是相同的,这表明它没有解释力。
  • 你说的没有错,只是不是问题的答案。您似乎在回答“为什么编译器在传递 Foo[] 时不使用采用 object[] 的重载,但在传递 byte[] 时一切正常”的问题,但这没有被问到。 OP 假设并想知道为什么对IEnumerable 进行特殊处理,答案是“没有”。在另一个宇宙中,OP 做出了不同的错误假设并得出了不同的错误结论,你的答案可能很好。
猜你喜欢
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 2012-12-21
  • 1970-01-01
  • 2010-12-23
  • 2010-11-25
  • 2015-09-17
  • 1970-01-01
相关资源
最近更新 更多