C# 使用LINQ获取IEnumerable中的第一个(也是唯一一个)元素有什么好处吗<;T>;?
这两条线之间有什么显著的区别吗C# 使用LINQ获取IEnumerable中的第一个(也是唯一一个)元素有什么好处吗<;T>;?,c#,linq,C#,Linq,这两条线之间有什么显著的区别吗 var o = xmlFile.Descendants("SomeElement").ElementAt(0).Value; 以及: XmlFile是一个XDocument对象,并且子体(XName)返回IEnumerable 我知道First()将引发异常但在这种情况下这没关系;我已经针对XmlSchemaSet验证了我的XDocument对象,因此我知道元素存在。我假设如果集合为空,直接访问Value将抛出异常,因为ElementAt(0)也不会返回任何内容
var o = xmlFile.Descendants("SomeElement").ElementAt(0).Value;
以及:
XmlFile
是一个XDocument
对象,并且子体(XName)
返回IEnumerable
我知道First()如果集合为空并且您可能希望使用FirstOrDefault(),则code>将引发异常代码>但在这种情况下这没关系;我已经针对XmlSchemaSet
验证了我的XDocument
对象,因此我知道元素存在。我假设如果集合为空,直接访问Value
将抛出异常,因为ElementAt(0)
也不会返回任何内容
但是是的;一、 显然,如果不需要,我不喜欢使用
指令添加。在这种情况下,有没有理由使用LINQ?我无法想象在这两种情况下有任何真正的性能差异
我这样问是因为用户可以上传一个包含任意数量需要处理的XML文件的zip文件。每个XML文件1条“记录”
编辑:我最初的问题是“如何从IEnumerable中获取第一个元素而不使用System.Linq;
添加,然后我在
中找到了元素,没有意识到它们都是Linq的一部分
所以我想我真正想知道的是,上面的代码片段和下面的代码片段之间是否有区别:
var descendants = xmlFile.Descendants("SomeElement");
var enumerator = descendants.GetEnumerator();
var node = (enumerator.MoveNext()) ? enumerator.Current : null;
我肯定会说LINQ更具可读性,仅此一点就可能值得使用。但是,用户可以上传一个10 MB的zip文件,每个XML文件的大小从2 KB到10 KB不等,这取决于它是哪个模式。因此,这是一个相当多的文件。检查源代码。这两个元素都位于
和First
是上定义的扩展方法(如问题注释中所述)
更新
我还包括了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();
}
publicstatictsource优先(此IEnumerable源){
if(source==null)抛出错误.ArgumentNull(“source”);
IList list=源作为IList;
如果(列表!=null){
如果(list.Count>0)返回列表[0];
}
否则{
使用(IEnumerator e=source.GetEnumerator()){
如果(e.MoveNext())返回e.Current;
}
}
抛出错误。NoElements();
}
公共静态TSource ElementAt(此IEnumerable源,int索引){
if(source==null)抛出错误.ArgumentNull(“source”);
IList list=源作为IList;
if(list!=null)返回列表[索引];
如果(索引<0)抛出错误。ArgumentOutOfRange(“索引”);
使用(IEnumerator e=source.GetEnumerator()){
while(true){
如果(!e.MoveNext())抛出错误.ArgumentOutOfRange(“索引”);
如果(索引==0)返回当前值;
索引--;
}
}
}
公共静态TSource Single(此IEnumerable源){
if(source==null)抛出错误.ArgumentNull(“source”);
IList list=源作为IList;
如果(列表!=null){
开关(list.Count){
案例0:抛出错误.NoElements();
案例1:返回列表[0];
}
}
否则{
使用(IEnumerator e=source.GetEnumerator()){
如果(!e.MoveNext())抛出错误.NoElements();
t源结果=e.电流;
if(!e.MoveNext())返回结果;
}
}
抛出错误。超过一个元素();
}
它们可以互换使用,因为它们都是在System.Linq.Enumerable
中定义的。
但这里有一些细微的区别:
1) 如果没有返回结果,.First
将抛出异常
2) .ElementAt(0)
将在索引器超出范围时引发异常
使用FirstOrDefault()
和/或ElementAtOrDefault(0)可以避免这两种异常
唯一真正的区别是名称,但无论如何它都很重要。如果您只想让第一个项目可枚举。首先,如果您想让第一个项目,但可能以后还要让第二个、第三个项目等等。然后使用ElementAt
/ElementAtOrdefault
意图应该是不言自明的。可读性是这里的关键因素
您可以找到源代码,例如:
及
您可以看到,这两种方法都针对支持通过索引访问的集合进行了优化。这里的其他答案指出,您提供的两个选项实际上都使用LINQ。但您更新的问题是,这是否等同于原始LINQ调用:
var descendants = xmlFile.Descendants("SomeElement");
var enumerator = descendants.GetEnumerator();
var node = (enumerator.MoveNext()) ? enumerator.Current : null;
嗯,不,不完全是。首先,请注意,IEnumerator
实现了IDisposable
,但是您的代码永远不会调用Dispose
(尽管我怀疑在这种情况下这实际上会有任何影响)。其次,您的代码处理空数据集的方式不同于那些LINQ方法(您的实现更像是FirstOrDefault
)。更等效的版本是:
XElement node;
using (var enumerator = xmlFile.Descendants("SomeElement").GetEnumerator())
{
if (!enumerator.MoveNext())
{
throw new Exception(...);
}
node = enumerator.Current;
}
或者不使用,使用:
XElement node;
var enumerator = xmlFile.Descendants("SomeElement").GetEnumerator();
try {
if (!enumerator.MoveNext()) { throw new Exception(...); }
node = enumerator.Current;
} finally {
enumerator.Dispose();
}
但事实上,我们根本不需要枚举器
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("");
}
现在,如果您分析这一点,您会发现更复杂的解决方案会带来一些边际性能提升。下面是我整理的一个相当基本的基准测试的结果(第一列没有编译器优化,第二列有编译器优化):
但是,可以节省一些处理器周期
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
var node = xmlFile.Descendants("SomeElement").First();