string s [s.Length-1] vs s.Last()

时间:2014-04-11 03:53:58

标签: c# string linq

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

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

3 个答案:

答案 0 :(得分:6)

不,它不如直接索引效率高,即O(1)。我们可以在reference source中看到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

答案 1 :(得分:1)

它没有那么高效,如果你在实现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();
}

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

答案 2 :(得分:1)

如果您对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>(记住成员重载决议是在编译时决定的)。对于字符串来说,这不是什么大问题,但对于其他优化,它可以是。