Q1)我想知道调用s.Last()
linq扩展方法是否与执行s[s.Length-1]
一样有效。我更喜欢第一个选项,但我不知道实现是否利用了当前类型。
Q2)这可能是另一个有趣的问题。 linq扩展方法在使用时是否利用了类型,或者只是将对象视为IEnumerable
?
答案 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方法会考虑更有效的方式来访问各种接口提供的数据。其他示例包括First
,Count
和ElementAt
。
答案 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>
(记住成员重载决议是在编译时决定的)。对于字符串来说,这不是什么大问题,但对于其他优化,它可以是。