2

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

当我在 mscorlib.dll 上运行 ILdasm 时,我收到了这个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。这是否意味着我将无法看到这些功能是如何实现的?四处搜索只发现了一个 stackoverflow 问题,Marc Gravell 说

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

虽然很有见地,但它并不能回答我的问题 if Array.CopyTocalls Buffer.BlockCopy。我特别感兴趣的是我是否能够看到这两个函数是如何实现的,如果我以后对 C# 的内部有疑问,我是否有可能对其进行调查。还是我运气不好?

4

5 回答 5

9

从您发布的内容来看,这里最大的问题是您正在查看参考程序集,特别是对于 .NET 4 的参考程序集。它们是特殊的程序集,它们的所有 IL 都被剥离了,只包含元数据。这是 .NET 4 中的新功能,它解决了 .NET 早期版本中的一个老问题。参考程序集只是安装在 GAC 中的实际程序集的副本。

这引起了麻烦,在后来的版本和服务包中进行了破坏程序的更改。尤其是 WaitHandle.WaitOne(int) 重载是臭名昭著的,它是在 .NET 3.0(又名 .NET 2.0 SP1)中添加的。并且无意中被程序员使用,他们发现重载 heckofalot 比神秘的 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。它不再受资源管理器外壳命名空间扩展保护,您可以简单地使用 File + Open 浏览 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++ 中实现的。请参阅此答案以了解如何查看此类方法的源代码。

于 2012-06-02T16:50:27.803 回答
4

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

Array.CopyTo来电Array.CopyArray.Copy和都是方法,这意味着它们是在本机代码Buffer.BlockCopyextern定义的,而不是在托管的 .NET 代码中定义的,所以我无法再告诉您它们是如何工作的。

于 2012-06-02T16:27:36.443 回答
1

我要回答我自己的问题。我意识到这是一种糟糕的风格,但如果不是以前的答案为我提供的精彩资源,我无法制定答案。谢谢你。

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

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

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

让我区分每个要执行的功能必须为真的检查类型:

块复制:

  1. 目的地和来源不能为空
  2. Destination 和 Source 数组由原语或字符串组成,但没有对象。
  3. 长度和偏移量必须有效

数组.复制:

  1. 目的地和来源不能为空
  2. Destination 和 Source 必须是一个数组
  3. 对方法表和等级进行多项检查
  4. 更深入的长度和偏移检查
  5. 类型必须以某种方式匹配(通过取消/装箱、强制转换或扩大)

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

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

  • 如果源和目标数组的类型相同,Copy 调用 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. 有符号和无符号短
  6. 有符号和无符号整数
  7. 有符号和无符号长
  8. 浮动和双重
  9. 有符号和无符号 IntPtr
  10. ELEMENT_TYPE_R(不确定这是什么)
于 2012-06-03T01:10:34.613 回答
0

Both Array.CopyTo and Buffer.BlockCopy are (ultimately) implemented with the pseudo-attribute:

[MethodImpl(MethodImplOptions.InternalCall)]

If you take a look at the second question on this page from general clever-man, Steven Toub, there's and excellent explanation of what this means.

于 2012-06-02T17:08:58.047 回答
0

当我在 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 代码。

如果你想检查方法,你必须拿出反汇编程序。

于 2012-06-02T16:35:07.763 回答