字符串s[s.Length-1]与s.Last()



Q1)我想知道调用s.Last() linq扩展方法是否和调用s[s.Length-1]一样高效。我更喜欢第一个选项,但我不知道实现是否利用了当前类型。

Q2)这可能是另一个有趣的问题。linq扩展方法在使用时是利用了类型,还是只将对象视为IEnumerable

不,它不会像直接索引那样高效,直接索引是O(1)。我们可以在Enumerable.Last的参考来源中看到:

    public static TSource Last<TSource>(this IEnumerable<TSource> source) {
        if (source == null) throw Error.ArgumentNull("source");
        IList<TSource> list = source as IList<TSource>;
        if (list != null) {
            int count = list.Count;
            if (count > 0) return list[count - 1];
        }
        else {
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                if (e.MoveNext()) {
                    TSource result;
                    do {
                        result = e.Current;
                    } while (e.MoveNext());
                    return result;
                }
            }
        }
        throw Error.NoElements();
    }

由于String不实现IList<char>,它将转到使用枚举器的分支,要求检查所有字符,直到找到最后一个字符(即O(n))。

正如您所看到的,在某些情况下,LINQ方法考虑了访问各种接口提供的数据的更有效的方式。其他示例包括FirstCountElementAt

它没有那么有效,如果您在实现IList但不用于字符串的东西上调用它,它会有一个特殊情况。以下是Reflector的实现。

[__DynamicallyInvokable]
public static TSource Last<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    IList<TSource> list = source as IList<TSource>;
    if (list != null)
    {
        int count = list.Count;
        if (count > 0)
        {
            return list[count - 1];
        }
    }
    else
    {
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                TSource current;
                do
                {
                    current = enumerator.Current;
                }
                while (enumerator.MoveNext());
                return current;
            }
        }
    }
    throw Error.NoElements();
}

您可以看到它枚举了整个序列,然后只返回最后一个元素。

如果您非常关心string.Last()的性能,那么您可以通过实现自己的Last()过载来两全其美。如果您的过载比Enumerable.Last()更匹配,则将使用您的过载。

internal class Program
{
    private static void Main()
    {
        Console.WriteLine("Hello".Last());
    }
}
public static class StringExtensions
{
    public static char Last(this string text)
    {
        if (text == null)
        {
            throw new ArgumentNullException("text");
        }
        int length = text.Length;
        if (length == 0)
        {
            throw new ArgumentException("Argument cannot be empty.", "text");
        }
        return text[length - 1];
    }
}

如果你想冒这个风险,去掉论据检查,你也可以这样做,但我不会。

我进行了测试以确认StringExtensions.Last()正在被调用,尽管我经常使用这种技术来确定它是否有效。:-)

注意:为了调用重载,必须将变量声明为字符串,以便编译器知道它是字符串。如果IEnumerable<char>在运行时恰好是字符串,则不会调用更有效的方法,例如:

private static void Main()
{
    IEnumerable<char> s = "Hello";
    Console.WriteLine(s.Last());
}

这里不调用StringExtensions.Last(),因为编译器不知道s是一个字符串,只知道它是IEnumerable<char>(记住,成员重载解析是在编译时决定的)。对于字符串来说,这不是什么大问题,但对于其他优化来说,这可能是个问题。

相关内容

  • 没有找到相关文章

最新更新