使用LINQ获取IEnumberable <t>中的第一个(也是唯一的)元素有什么好处吗?

时间:2016-04-28 13:21:05

标签: c# linq

这两行之间是否有任何显着差异?

var o = xmlFile.Descendants("SomeElement").ElementAt(0).Value;

var o = xmlFile.Descendants("SomeElement").First().Value;

XmlFileXDocument个对象,Descendants(XName name)返回IEnumberable<XElement>

我知道如果集合为空,First();会抛出异常而您可能想要使用FirstOrDefault();,但在这种情况下这样做很好;我已经针对XDocument验证了我的XmlSchemaSet对象,因此我知道该元素存在。我想直接访问Value会在集合为空时抛出异常,因为ElementAt(0)也不会返回任何内容。

但是,是的;显然,如果我不需要,我不想添加using指令。在这种情况下,有没有理由想要使用LINQ?我无法想象在任何一种情况下都会有任何真正的性能差异。

我问,因为用户可以上传包含任意数量需要处理的XML文件的zip文件。 1&#34;记录&#34;每个XML文件。

编辑:我原来的问题是&#34;如何在不添加using System.Linq;的情况下从IEnumerable获取第一个元素,然后我找到了ElementAt ,没有意识到它们都是LINQ的一部分。

所以我想我真正想知道的是,上述任何一个片段与此之间是否存在差异:

var descendants = xmlFile.Descendants("SomeElement");
var enumerator = descendants.GetEnumerator();
var node = (enumerator.MoveNext()) ? enumerator.Current : null;

我肯定地说LINQ更具可读性,仅此一点可能值得使用。但同样,用户可以上传我认为最多10 MB的zip文件,每个XML文件的大小从2千字节到10千字节不等,具体取决于它的架构。这样就有很多文件。

4 个答案:

答案 0 :(得分:3)

检查来源。 ElementAtFirst都是在System.Linq.Enumerable上定义的扩展方法(正如问题评论中Lee所述)。

更新

我也包含了Single的实现,因为它已经讨论过,对于这个特定的问题,它将是一个更好的选择。从根本上说,这归结为抛出的可读性和异常,因为它们都使用相同的方式访问第一个元素。

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


    public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) {
        if (source == null) throw Error.ArgumentNull("source");
        IList<TSource> list = source as IList<TSource>;
        if(list != null) return list[index];
        if (index < 0) throw Error.ArgumentOutOfRange("index");
        using (IEnumerator<TSource> e = source.GetEnumerator()) {
            while (true) {
                if (!e.MoveNext()) throw Error.ArgumentOutOfRange("index");
                if (index == 0) return e.Current;
                index--;
            }
        }
    }

    public static TSource Single<TSource>(this IEnumerable<TSource> source) {
        if (source == null) throw Error.ArgumentNull("source");
        IList<TSource> list = source as IList<TSource>;
        if (list != null) {
            switch (list.Count) {
                case 0: throw Error.NoElements();
                case 1: return list[0];
            }
        }
        else {
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                if (!e.MoveNext()) throw Error.NoElements();
                TSource result = e.Current;
                if (!e.MoveNext()) return result;
            }
        }
        throw Error.MoreThanOneElement();
    }

答案 1 :(得分:2)

唯一真正的区别在于名称,但无论如何它都很重要。如果您只希望第一项使用Enumerable.First / FirstOrDefault,如果您想要第一项,可能稍后也需要第二项,第三项等,请使用ElementAt / ElementAtOrdefault

意图应该是自我解释的。可读性是这里的关键因素。

您可以找到源代码here,例如:

Enumerable.ElementAtEnumerable.First

您可以看到两种方法都针对支持通过索引进行访问的集合进行了优化。

答案 2 :(得分:1)

它们可以互换使用,因为它们都在System.Linq.Enumerable中定义。 但这里有一些细微差别:

1)如果没有返回结果,.First将抛出异常。

2)如果索引器超出范围,.ElementAt(0)将抛出异常。

使用FirstOrDefault()和/或ElementAtOrDefault(0)

可以避免这两种例外情况

答案 3 :(得分:1)

此处的其他答案指出,您提供的两个选项实际上都使用LINQ。但是您更新的问题询问这是否等同于原始的LINQ调用:

var descendants = xmlFile.Descendants("SomeElement");
var enumerator = descendants.GetEnumerator();
var node = (enumerator.MoveNext()) ? enumerator.Current : null;

嗯,不,不完全。首先,请注意IEnumerator<T>实现IDisposable,但您的代码永远不会调用Dispose(尽管我怀疑在这种情况下实际上会产生任何影响)。其次,您的代码处理空数据集的方式与这些LINQ方法不同(您的实现更像FirstOrDefault)。更相同的版本是:

XElement node;
using (var enumerator = xmlFile.Descendants("SomeElement").GetEnumerator()) 
{
    if (!enumerator.MoveNext()) 
    {
       throw new Exception(...); 
    }
    node = enumerator.Current;
}

或没有using

XElement node;
var enumerator = xmlFile.Descendants("SomeElement").GetEnumerator();
try {
    if (!enumerator.MoveNext()) { throw new Exception(...); }
    node = enumerator.Current;
} finally {
    enumerator.Dispose();
}

但事实上,我们根本不需要Enumerator。我们可以像这样摆脱对Descendants的调用:

var n = xmlFile.FirstNode;
var node = n as XElement;
while (node == null && n != null) 
{
    node = (n = n.NextNode) as XElement;
}
while (node != null &&  node.Name != "SomeElement") 
{
    node = (n = node.FirstNode ?? node.NextNode ?? node.Parent?.NextNode) as XElement;
    while (node == null && n != null) 
    {
        node = (n = n.NextNode) as XElement;
    }
}
if (node == null) 
{
    throw new Exception(""); 
}

现在,如果您对此进行分析,您会发现使用更复杂的解决方案可以提高边缘性能。这是我放在一起的相当基本的基准测试的结果(第一列没有编译器优化,第二列是编译器优化):

Method       Mean (/o-)   Mean (/o+)
First()      0.1468333    0.1414340
ElementAt()  0.1452045    0.1419018
No Linq      0.1334992    0.1259622
While Loop   0.0895821    0.0693819

但是,节省一些处理器周期通常不是企业级应用程序中最关心的问题。鉴于维护代码的典型成本,您通常应该尝试优化可读性,在我看来,这是 lot 更容易阅读:

var node = xmlFile.Descendants("SomeElement").First();