为什么这个字符串扩展方法不会抛出异常? [英] Why does this string extension method not throw an exception?

查看:172
本文介绍了为什么这个字符串扩展方法不会抛出异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个C#字符串扩展方法应该返回一个的IEnumerable< INT> 在字符串中的子串的所有指标。它完美地达到预期的目的和预期的返回结果(通过我的测试之一作为证明,虽然不是下面的一个),但另一个单元测试已经发现了一个问题吧:它不能处理空参数

I've got a C# string extension method that should return an IEnumerable<int> of all the indexes of a substring within a string. It works perfectly for its intended purpose and the expected results are returned (as proven by one of my tests, although not the one below), but another unit test has discovered a problem with it: it can't handle null arguments.

下面是扩展方法,我测试:

Here's the extension method I'm testing:

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
    if (searchText == null)
    {
        throw new ArgumentNullException("searchText");
    }
    for (int index = 0; ; index += searchText.Length)
    {
        index = str.IndexOf(searchText, index);
        if (index == -1)
            break;
        yield return index;
    }
}

下面是标记出的问题测试:

Here is the test that flagged up the problem:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Extensions_AllIndexesOf_HandlesNullArguments()
{
    string test = "a.b.c.d.e";
    test.AllIndexesOf(null);
}

在试运行对我的扩展方法,它失败,与标准误差。消息的方法,没有抛出异常

When the test runs against my extension method, it fails, with the standard error message that the method "did not throw an exception".

这是令人困惑:我已经清楚地通过了进入的功能,但由于某些原因,比较空== NULL 将返回。因此,不会抛出异常并且代码继续

This is confusing: I have clearly passed null into the function, yet for some reason the comparison null == null is returning false. Therefore, no exception is thrown and the code continues.

我已确认这是不是与测试的一个错误:与调用运行在我的主要项目中的方法时, Console.WriteLine 在空比较如果块,没有显示在控制台上,没有异常被捕获的任何块我想补充。此外,使用 string.IsNullOrEmpty 而不是 == NULL 有同样的问题。

I have confirmed this is not a bug with the test: when running the method in my main project with a call to Console.WriteLine in the null-comparison if block, nothing is shown on the console and no exception is caught by any catch block I add. Furthermore, using string.IsNullOrEmpty instead of == null has the same problem.

为什么这个所谓简单的比较失败了?

Why does this supposedly-simple comparison fail?

推荐答案

您正在使用回报收益率。这样做时,编译器将重写你的方法成返回一个实现状态机生成的类的功能。

You are using yield return. When doing so, the compiler will rewrite your method into a function that returns a generated class that implements a state machine.

从广义上讲,它重写当地人的那类领域和收益率收益之间你的算法的每个部分指令变成了状态。您可以使用反编译一下这个方法编译之后成为检查(确保关闭这将产生收益回报率智能反编译)。

Broadly speaking, it rewrites locals to fields of that class and each part of your algorithm between the yield return instructions becomes a state. You can check with a decompiler what this method becomes after compilation (make sure to turn off smart decompilation which would produce yield return).

但底线是:您的方法的代码不会被执行,直到你开始迭代

通常方法来检查的前提条件是一分为二你的方法:

The usual way to check for preconditions is to split your method in two:

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
    if (str == null)
        throw new ArgumentNullException("str");
    if (searchText == null)
        throw new ArgumentNullException("searchText");

    return AllIndexesOfCore(str, searchText);
}

private static IEnumerable<int> AllIndexesOfCore(string str, string searchText)
{
    for (int index = 0; ; index += searchText.Length)
    {
        index = str.IndexOf(searchText, index);
        if (index == -1)
            break;
        yield return index;
    }
}

这工作,因为第一种方法的行为就像你期待(立即执行),并且将返回用第二种方法实现的状态机。

This works because the first method will behave just like you expect (immediate execution), and will return the state machine implemented by the second method.

请注意,您也应该检查 STR 空,因为扩展方法的可以的被要求值,因为他们只是语法糖。

Note that you should also check the str parameter for null, because extensions methods can be called on null values, as they're just syntactic sugar.

如果您想了解什么,编译器对您的代码,这里的你的方法,用dotPeek反编译使用的显示编译器生成代码的选项

If you're curious about what the compiler does to your code, here's your method, decompiled with dotPeek using the Show Compiler-generated Code option.

public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
  Test.<AllIndexesOf>d__0 allIndexesOfD0 = new Test.<AllIndexesOf>d__0(-2);
  allIndexesOfD0.<>3__str = str;
  allIndexesOfD0.<>3__searchText = searchText;
  return (IEnumerable<int>) allIndexesOfD0;
}

[CompilerGenerated]
private sealed class <AllIndexesOf>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
  private int <>2__current;
  private int <>1__state;
  private int <>l__initialThreadId;
  public string str;
  public string <>3__str;
  public string searchText;
  public string <>3__searchText;
  public int <index>5__1;

  int IEnumerator<int>.Current
  {
    [DebuggerHidden] get
    {
      return this.<>2__current;
    }
  }

  object IEnumerator.Current
  {
    [DebuggerHidden] get
    {
      return (object) this.<>2__current;
    }
  }

  [DebuggerHidden]
  public <AllIndexesOf>d__0(int <>1__state)
  {
    base..ctor();
    this.<>1__state = param0;
    this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
  }

  [DebuggerHidden]
  IEnumerator<int> IEnumerable<int>.GetEnumerator()
  {
    Test.<AllIndexesOf>d__0 allIndexesOfD0;
    if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
    {
      this.<>1__state = 0;
      allIndexesOfD0 = this;
    }
    else
      allIndexesOfD0 = new Test.<AllIndexesOf>d__0(0);
    allIndexesOfD0.str = this.<>3__str;
    allIndexesOfD0.searchText = this.<>3__searchText;
    return (IEnumerator<int>) allIndexesOfD0;
  }

  [DebuggerHidden]
  IEnumerator IEnumerable.GetEnumerator()
  {
    return (IEnumerator) this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
  }

  bool IEnumerator.MoveNext()
  {
    switch (this.<>1__state)
    {
      case 0:
        this.<>1__state = -1;
        if (this.searchText == null)
          throw new ArgumentNullException("searchText");
        this.<index>5__1 = 0;
        break;
      case 1:
        this.<>1__state = -1;
        this.<index>5__1 += this.searchText.Length;
        break;
      default:
        return false;
    }
    this.<index>5__1 = this.str.IndexOf(this.searchText, this.<index>5__1);
    if (this.<index>5__1 != -1)
    {
      this.<>2__current = this.<index>5__1;
      this.<>1__state = 1;
      return true;
    }
    goto default;
  }

  [DebuggerHidden]
  void IEnumerator.Reset()
  {
    throw new NotSupportedException();
  }

  void IDisposable.Dispose()
  {
  }
}

这是无效的C#代码,因为编译器被允许做的事情语言不允许,但它是合法的IL - 例如命名变量的方式,你不能以避免名称冲突。

This is invalid C# code, because the compiler is allowed to do things the language doesn't allow, but which are legal in IL - for instance naming the variables in a way you couldn't to avoid name collisions.

但正如你所看到的, AllIndexesOf 只构造并返回一个对象,它的构造也只能初始化一些状态。 的GetEnumerator 仅复制的对象。当你开始枚举(通过调用的MoveNext 法),真正的工作就完成了。

But as you can see, the AllIndexesOf only constructs and returns an object, whose constructor only initializes some state. GetEnumerator only copies the object. The real work is done when you start enumerating (by calling the MoveNext method).

这篇关于为什么这个字符串扩展方法不会抛出异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆