可以检查核心 C# 功能的内部

本文关键字:功能 内部 核心 检查 | 更新日期: 2023-09-27 18:36:10

我今天被打动了,倾向于比较Buffer.BlockCopyArray.CopyTo.的内脏,我很好奇Array.CopyTo是否在幕后被称为Buffer.BlockCopy。 这背后没有任何实际目的,我只是想进一步了解 C# 语言及其实现方式。 不要跳枪指责我微优化,但你可以指责我好奇!

当我在mscorlib上运行ILdasm时.dll我收到了这个Array.CopyTo

.method public hidebysig newslot virtual final 
    instance void  CopyTo(class System.Array 'array',
                          int32 index) cil managed
{
  // Code size       0 (0x0)
} // end of method Array::CopyTo

这是为了Buffer.BlockCopy

.method public hidebysig static void  BlockCopy(class System.Array src,
                                            int32 srcOffset,
                                            class System.Array dst,
                                            int32 dstOffset,
                                            int32 count) cil managed internalcall
{
  .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 ) 
} // end of method Buffer::BlockCopy

坦率地说,这让我感到困惑。 我从来没有在我不是创建的dll/exe上运行过ILdasm。 这是否意味着我将无法看到这些功能是如何实现的? 四处搜索只发现了一个堆栈溢出的问题,Marc Gravell 说

[ Buffer.BlockCopy ] 基本上是原始内存副本的包装器

虽然很有见地,但它并不能回答我的问题,如果Array.CopyTo打电话给Buffer.BlockCopy. 我特别感兴趣的是,我是否能够看到这两个函数是如何实现的,以及我将来是否有关于 C# 内部的问题,如果我可以调查它。 还是我运气不好?

可以检查核心 C# 功能的内部

这里最大的问题是,从您发布的内容来看,您正在查看引用程序集,尤其是 .NET 4 的引用程序集。 哪些是特殊程序集,它们已剥离所有 IL,仅包含元数据。 这是 .NET 4 中的新功能,它解决了早期版本的 .NET 中的一个旧问题。 其中引用程序集只是 GAC 中安装的实际程序集的副本。

这造成了麻烦,在以后的版本和服务包中进行了更改,这些更改正在破坏程序。 特别是WaitHandle.WaitOne(int)重载是臭名昭著的,它是在.NET 3.0(又名.NET 2.0 SP1)中添加的。 并且不知不觉地被程序员使用,发现重载比神秘的 WaitOne(int,bool) 重载更容易使用。 但是他们的程序将不再在原始的.NET 2.0发布版本上运行,从而产生MissingMethodException的问题。

添加这种重载通常是一件非常顽皮的事情,他们修改了mscorlib.dll但没有更改其[AssemblyVersion]。 通过在 .NET 4 中提供单独的引用程序集,此问题不会再发生。 Microsoft现在可以修改 .NET 类型的公共接口,而不会破坏任何内容。 并且兴致勃勃地这样做了,几个 .NET 4 中间版本已经在没有人注意到的情况下被滑流。

因此,请务必反汇编 mscorlib.dll 的真实版本,即 GAC 中的版本。 对于 .NET 4,它存储在不同的目录中,c:''windows''microsoft.net''assembly,而不是c:''windows''assembly。 它不再受资源管理器 shell 命名空间扩展保护,只需使用"文件 + 打开"浏览 GAC 目录即可。 您可以在 C:''Windows''Microsoft.NET''assembly''GAC_32''mscorlib''v4.0_4.0.0.0__b77a5c561934e089 目录中找到 32 位版本。

这还不是故事的结尾,当你向下钻取时,你会发现 Array.CopyTo() 调用一个名为 Array.Copy() 的内部帮助程序方法,该方法具有 [MethodImpl(MethodImplOptions.InternalCall)] 属性。 同样,没有方法主体。 该属性告知实时编译器该方法实际上是在 CLR 内部的C++中实现的。 请参阅此答案以了解如何查看此类方法的源代码。

ILSpy 是一个免费的 .NET 反编译器。 您可以使用它来检查任何 .NET DLL,包括 mscorlib。

Array.CopyTo打电话给Array.Copy. Array.CopyBuffer.BlockCopy 都是extern方法,这意味着它们是在本机代码而不是托管 .NET 代码中定义的,因此我无法进一步告诉您它们的工作原理。

我将回答我自己的问题。 我意识到这是不好的风格,但如果不是以前的答案为我提供了丰富的资源,我不可能制定答案。 谢谢。

首先,那些来到这里并想知道如何检查C#函数内部的人,Tim发布了一个很棒的资源,ILSpy。 这适用于未在外部定义方法的情况。 当它们在外部定义时,似乎获得答案的唯一希望是下载SSCLI 2.0。 由于这是针对 .Net 2.0 而不是 4.0 的,因此我提供的信息可能已经过时。 但是,我将继续假设所讨论的方法没有太大变化。 在浏览完源文件后,我相信我可以回答"Array.CopyTo 是否在幕后调用 Buffer.BlockCopy?

在我进入问题的核心之前,其他人已经指出CopyTo调用Array.Copy。 Array.Copy 是在外部定义的,所以我将我的查询更改为"Array.Copy 是否在幕后调用 Buffer.BlockCopy? 我发现有趣的一点花絮来自MSDN上的Array.CopyTo文档

如果没有明确要求实现 System.Collections.ICollection,请使用 [Array。复制以避免额外的间接寻址。

让我区分每个函数必须执行的检查类型:

块复制:

  1. 目标和源不能为空
  2. 目标数组和源数组由基元或字符串组成但没有对象。
  3. 长度和偏移量必须有效

Array.Copy:

  1. 目标和源不能为空
  2. 目标和源必须是数组
  3. 使用方法表和排名进行多项检查
  4. 更深入的长度和偏移检查
  5. 类型必须以某种方式匹配(取消/装箱、强制转换或加宽)

在这些检查之后,BlockCopy 调用m_memmove,这是不言自明的,但非常有趣。 m_memmove依赖于架构;放弃了 32 位机器上的传统 memmove,转而支持一次手卷 16 字节的副本。

可以想象,数组包含的不仅仅是基元。

  • 如果源数组和目标数组的类型相同,则复制调用m_memmove。 然后,如果基础类型不是基元,则制定垃圾回收操作。
  • 否则,根据传入的元素类型,Copy 将拆箱,框、投射或加宽每个元素,因为它转移它们。 这些据我所知,函数不调用m_memmove,而是一次转换一个元素。

因此,要回答我最初的问题,"Array.Copy 是否在幕后调用 Buffer.BlockCopy? 如果我们考虑这两种方法在处理m_memmove方面的相似之处,那就有点了。 BlockCopy 将始终调用m_memmove而 Copy 仅在处理完全相同类型的数组时才调用它。 然后,我的建议是,如果有人想将字节数组复制到整数,或者他们只关心原始数据的类似内容,请使用 BlockCopy,因为它将能够利用m_memmove。

供参考:.Net定义为原语的内容(取自cortypeinfo.h)

  1. 无效
  2. 布尔
  3. 有符号和无符号字节
  4. 有符号和无符号短
  5. 有符号和无符号 int
  6. 有符号和无符号长
  7. 浮动和双
  8. 有符号和无符号的 IntPtr
  9. ELEMENT_TYPE_R(不确定这是什么)

当我在Buffer.BlockCopy上使用Telerik JustDecompile时,我发现:

[SecuritySafeCritical]
public static extern void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);

这意味着它不是使用生成 IL 代码的东西实现的。

和 Array.CopyTo:

public void CopyTo(Array array, int index) {
  if (array != null && array.Rank != 1) {
    throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"));
  }
  Array.Copy(this, this.GetLowerBound(0), array, index, this.Length);
}

Array.Copy 揭示:

[SecuritySafeCritical]
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) {
  Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
}

该重载:

[SecurityCritical]
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);

所以这也不是 IL 代码。

如果要检查方法,则必须拿出反汇编器。

Array.CopyToBuffer.BlockCopy(最终)都是用伪属性实现的:

[MethodImpl(MethodImplOptions.InternalCall)]

如果你看看这个页面上的第二个问题,来自一般聪明人,史蒂文·图布,有很好的解释这意味着什么。