Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/tfs/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么';包含直接调用最终重载吗?_C#_String_Oop_Language Design_Contains - Fatal编程技术网

C# 为什么';包含直接调用最终重载吗?

C# 为什么';包含直接调用最终重载吗?,c#,string,oop,language-design,contains,C#,String,Oop,Language Design,Contains,该方法在内部看起来是这样的 public bool Contains(string value) { return this.IndexOf(value, StringComparison.Ordinal) >= 0; } 调用的IndexOf重载如下所示 public int IndexOf(string value, StringComparison comparisonType) { return this.IndexOf(value, 0, this.Length,

该方法在内部看起来是这样的

public bool Contains(string value)
{
   return this.IndexOf(value, StringComparison.Ordinal) >= 0;
}
调用的
IndexOf
重载如下所示

public int IndexOf(string value, StringComparison comparisonType)
{
   return this.IndexOf(value, 0, this.Length, comparisonType);
}
这里对最终重载进行了另一个调用,该重载随后使用签名调用相关的
CompareInfo.IndexOf
方法

public int IndexOf(string value, int startIndex, int count, StringComparison comparisonType)
因此,调用最终重载将是最快的(尽管在大多数情况下可能被视为微观优化)

我可能遗漏了一些明显的东西,但是为什么
包含
方法不直接调用最终重载,因为在中间调用中没有完成其他工作,而且在两个阶段都有相同的信息

如果最终重载的签名发生更改,那么只需要进行一次更改(即中间方法的更改),这是唯一的优势,还是设计中还有更多的更改

根据评论进行编辑(有关速度差异的解释,请参阅更新2)

为了澄清我在某个地方犯错误时所得到的性能差异: 我运行(循环了5次以避免抖动偏差),并使用此扩展方法与
String.Contains
方法进行比较

public static bool QuickContains(this string input, string value)
{
   return input.IndexOf(value, 0, input.Length, StringComparison.OrdinalIgnoreCase) >= 0;
}
环看起来像这样

for (int i = 0; i < 1000000; i++)
{
   bool containsStringRegEx = testString.QuickContains("STRING");
}
sw.Stop();
Console.WriteLine("QuickContains: " + sw.ElapsedMilliseconds);
使用
StringComparison.Ordinal
在调用和删除
ToUpper
的2重载
索引中,
QuickContains
方法实际上变得最慢<代码> <代码> >和<代码>包含在性能方面基本上是一致的。显然是
ToUpper
调用扭曲了
Contains
IndexOf
之间的差异

不确定为什么
QuickContains
扩展方法变得最慢。(可能与
包含
具有
[\uu DynamicallyInvokable,TargetedPatchingOptOut(“跨NGen图像边界内联的性能关键”)]
属性有关)

关于为什么不直接调用4重载方法的问题仍然存在,但这似乎不会影响性能(正如Adrian和delnan在评论中指出的那样)。

我已经有一段时间(几年)没有研究汇编了,我对MSIL和JIT几乎一无所知,所以这将是一个很好的练习-无法抗拒,这里有一些可能是多余的经验数据。
IndexOf
重载是否内联

这里有一个小小的控制台应用程序:

class Program
{
    static void Main(string[] args)
    {
        "hello".Contains("hell");
    }
}
JIT在一个优化的发布版本中生成这个,任何CPU,以32位运行。我缩短了地址,删除了一些不相关的行:

--- ...\Program.cs 
            "hello".Contains("hell");
[snip]
17  mov         ecx,dword ptr ds:[0320219Ch] ; pointer to "hello"
1d  mov         edx,dword ptr ds:[032021A0h] ; pointer to "hell"
23  cmp         dword ptr [ecx],ecx 
25  call        680A6A6C                     ; String.Contains()
[snip]
0x00000025处的
调用
如下:

字符串。包含

00  push        0                 ; startIndex = 0
02  push        dword ptr [ecx+4] ; count = this.Length (second DWORD of String)
05  push        4                 ; comparisonType = StringComparison.Ordinal
07  call        FF9655A4          ; String.IndexOf()
0c  test        eax,eax 
0e  setge       al                ; if (... >= 0)
11  movzx       eax,al 
14  ret 
果然,它似乎直接调用了最后的
String.IndexOf
重载,其中包含四个参数:三个
push
ed;
edx
value
:“地狱”)中有一个<代码>这是
ecx
中的(“你好”)。要确认,这就是0x00000005处的
调用

00  push        ebp 
01  mov         ebp,esp 
03  push        edi 
04  push        esi 
05  push        ebx 
06  mov         esi,ecx                  ; this ("hello")
08  mov         edi,edx                  ; value ("hell")
0a  mov         ebx,dword ptr [ebp+10h] 
0d  test        edi,edi                  ; if (value == null)
0f  je          00A374D0 
15  test        ebx,ebx                  ; if (startIndex < 0)
17  jl          00A374FB 
1d  cmp         dword ptr [esi+4],ebx    ; if (startIndex > this.Length)
20  jl          00A374FB 
26  cmp         dword ptr [ebp+0Ch],0    ; if (count < 0)
2a  jl          00A3753F 
[snip]
00推送ebp
01 mov ebp,esp
03推式电子数据交换
04推动esi
05推动ebx
06电影esi、ecx;这个(“你好”)
08 mov edi,edx;价值(“地狱”)
0a mov ebx,dword ptr[ebp+10h]
0d测试edi,edi;如果(值==null)
0f je 00A374D0
15试验ebx,ebx;如果(起始索引<0)
17 jl 00A374FB
1d cmp dword ptr[esi+4],ebx;如果(startIndex>this.Length)
20 jl 00A374FB
26 cmp dword ptr[ebp+0Ch],0;如果(计数<0)
2a jl 00A3753F
[剪报]
。。。这将是以下内容的主体:

public int IndexOf(string value, 
                   int startIndex, 
                   int count, 
                   StringComparison comparisonType)
{
  if (value == null)
    throw new ArgumentNullException("value");
  if (startIndex < 0 || startIndex > this.Length)
    throw new ArgumentOutOfRangeException("startIndex",
             Environment.GetResourceString("ArgumentOutOfRange_Index"));
  if (count < 0 || startIndex > this.Length - count)
    throw new ArgumentOutOfRangeException("count",
             Environment.GetResourceString("ArgumentOutOfRange_Count"));
  ...
}
public int IndexOf(字符串值,
int startIndex,
整数计数,
StringComparison(比较类型)
{
如果(值==null)
抛出新的ArgumentNullException(“值”);
if(startIndex<0 | | startIndex>this.Length)
抛出新ArgumentOutOfRangeException(“startIndex”,
GetResourceString(“ArgumentOutOfRange_索引”);
if(count<0 | | startIndex>this.Length-count)
抛出新ArgumentOutOfRangeException(“计数”,
GetResourceString(“ArgumentOutOfRange_Count”);
...
}

方法调用可能由编译器内联,因此当代码开始运行时,
String.Contains(String value)
的任何出现都可能已经被重写为更复杂的版本(留下的唯一原因是您提到的优点,而不是缺点)。然而,这显然只是猜测。这是个好主意。我刚刚尝试通过创建一个自定义的
Contains
方法来对其进行基准测试,该方法直接调用最终重载,结果比现有的
Contains
方法快50%,因此性能受到了明显的影响(尽管绝对值很小;1米循环中有200毫秒)。@keyboardP我觉得这有点难以置信;即使是最简单的内联启发式和最简单的内联,也应该使这两种变体难以区分。你确定你没有提交吗?@delnan-谢谢你的链接,现在我正在浏览它,但我使用的基准是(很高兴修复那里的任何问题)。我创建了一个新方法,它只需调用带有四个重载的
IndexOf
,并在其他测试中抛出它。)@keyboard还有一件事,第二篇文章似乎没有链接到。您似乎已经涵盖了第1部分和第2部分,代码方面(我不知道您是如何测量的,即外部VS,在发布模式下编译等)。哇,感谢您的努力!性能差异似乎可以忽略不计(对于不区分大小写的搜索),因此归结为设计决策。我想这真的可能只是一种情况,如果4重载签名发生变化,就必须避免两次代码更改(无论多么不可能)
public int IndexOf(string value, 
                   int startIndex, 
                   int count, 
                   StringComparison comparisonType)
{
  if (value == null)
    throw new ArgumentNullException("value");
  if (startIndex < 0 || startIndex > this.Length)
    throw new ArgumentOutOfRangeException("startIndex",
             Environment.GetResourceString("ArgumentOutOfRange_Index"));
  if (count < 0 || startIndex > this.Length - count)
    throw new ArgumentOutOfRangeException("count",
             Environment.GetResourceString("ArgumentOutOfRange_Count"));
  ...
}