30

回答以下问题: 如何将 MatchCollection 转换为字符串数组

给定两个 Linq 表达式:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .OfType<Match>() //OfType
    .Select(m => m.Groups[0].Value)
    .ToArray();

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .Cast<Match>() //Cast
    .Select(m => m.Groups[0].Value)
    .ToArray();

OfType<> 由用户Alex进行了基准测试,速度稍快(并由我本人确认)。

这对我来说似乎违反直觉,因为我认为 OfType<> 必须同时进行“是”比较强制转换(T)。

任何启示都将不胜感激为什么会这样:)

4

4 回答 4

16

我的基准测试与您的基准测试不一致。

我运行了与 Alex 相同的基准测试,得到了相反的结果。然后我稍微调整了基准,并再次观察到CastOfType.

它没有太多内容,但我相信它Cast确实有优势,因为它的迭代器更简单。(不is检查。)

编辑:实际上经过一些进一步的调整,我设法CastOfType.

以下是迄今为止我发现的最大差异的基准代码:

Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();

var ma = Enumerable.Range(1, 100000).Select(i => i.ToString()).ToArray();

var x = ma.OfType<string>().ToArray();
var y = ma.Cast<string>().ToArray();

for (int i = 0; i < 1000; i++)
{
    if (i%2 == 0)
    {
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
    }
    else
    {
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
    }
}
Console.WriteLine("OfType: " + sw1.ElapsedMilliseconds.ToString());
Console.WriteLine("Cast: " + sw2.ElapsedMilliseconds.ToString());
Console.ReadLine();

我所做的调整:

  • 在开始时执行一次“生成字符串列表”工作,然后“结晶”它。
  • 在开始计时之前执行每个操作之一 - 我不确定这是否有必要,但我认为这意味着 JITter 预先生成代码而不是在我们计时时生成代码?
  • 多次执行每个操作,而不仅仅是一次。
  • 更改顺序以防万一。

在我的机器上,这导致 ~350msCast和 ~18000ms OfType

我认为最大的不同是我们不再MatchCollection计算找到下一场比赛需要多长时间。(或者,在我的代码中,需要多长时间int.ToString()。)这大大降低了信噪比。

编辑:正如sixlettervariables 指出的那样,造成这种巨大差异的原因是,Cast如果它可以投射整个IEnumerable. 当我从使用切换Regex.Matches到数组以避免测量正则表达式处理时间时,我也切换到使用可转换的东西IEnumerable<string>,从而激活了这种短路。当我改变我的基准以禁用这种短路时,我获得了一点优势,Cast而不是一个巨大的优势。

于 2012-07-11T12:04:34.047 回答
11

OfType()应该更慢,因为在is实际显式转换操作之前进行安全类型检查,同时Cast()只进行显式转换。

从理论上讲OfType,如果许多元素具有“错误类型”,那么循环会更快,因此循环会在is检查后进一步枚举,如果Cast()在同一个集合上,您最终会得到一个InvalidCastException“错误类型”的每个元素,所以这会相对较慢.

使用 ILSpy 提取的源代码:

// System.Linq.Enumerable
private static IEnumerable<TResult> OfType<TResult>(IEnumerable source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }

    foreach (object current in source)
    {
        // **Type check**
        if (current is TResult)
        {
            // **Explicit cast**
            yield return (TResult)current;
        }
    }
    yield break;
}

// System.Linq.Enumerable
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    if (enumerable != null)
    {
        return enumerable;
    }
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }

    foreach (object current in source)
    {
        // **Explicit cast only**
        yield return (TResult)current;
    }
    yield break;
}
于 2012-07-11T12:12:49.327 回答
7

OfType只需在您的方法中颠倒and的顺序,Cast您就会注意到没有区别。第一个总是比第二个跑得快。这是一个糟糕的微基准测试案例。

将您的代码包装在一个循环中以随机顺序运行它们:

OfType: 1224
Cast: 2815
Cast: 2961
OfType: 3010
OfType: 3027
Cast: 2987
...

然后再一次:

Cast: 1207
OfType: 2781
Cast: 2930
OfType: 2964
OfType: 2964
OfType: 2987
...

取出Regex.Matches,这似乎会导致问题:

Cast: 1247
OfType: 210
OfType: 170
Cast: 171
...

OfType: 1225
Cast: 202
OfType: 171
Cast: 192
Cast: 415

所以不行。OfType不比 快Cast。不,Cast不比OfType.

于 2012-07-11T13:17:50.767 回答
1

实际上 isof() 首先检查类型然后转换它,而 cast() 只是做第二部分。所以显然 isof() 会比直接投射慢

http://codenets.blogspot.in/2010/06/cast-vs-oftype.html

于 2012-07-11T12:04:21.907 回答