简化LINQ表达式

6

我有一段代码,我真的不太喜欢,如果有办法简化它 - 那就太好了。

A a; // I want to get rid of this variable
if((a = collection.FirstOrDefault(x => x.Field == null)) != null)
{
  throw new ScriptException("{0}", a.y); //I need to access other field of the object here, that's why I had to declare a variable outside of the expression
}

3
除非你想要对表达式进行两次计算,否则你将需要使用这个变量。 - Jonesopolis
它有什么问题? - musefan
@Anarion:是的,因为你把赋值语句放在if语句里面,让代码变得难以阅读。 - musefan
你引发了一场相当的讨论。 - Jonesopolis
@musefan 是的,我能看到。晚上10点,太累了,来SO寻求帮助。 - Anarion
显示剩余5条评论
7个回答

7

不要只查找第一个匹配项并处理,而是将结果视为集合。使用Where遍历所有匹配项的foreach。由于异常将使您退出循环,因此最终结果相同,只是代码更加清晰:

foreach(var a in collection.Where(x => x.Field == null))
    throw new ScriptException("{0}", a.y);

如果您想让读者更清楚地了解循环最多只执行一次,可以添加一个Take调用来澄清代码而不进行任何功能上的更改:

foreach(var a in collection.Where(x => x.Field == null).Take(1))
    throw new ScriptException("{0}", a.y);

这样做也更容易聚合所有无效项,而不仅仅是第一个:
var exceptions = collection.Where(a => a.Field == null)
    .Select(a => new ScriptException("{0}", a.y))
    .ToList();
if (exceptions.Any())
    throw new AggregateException(exceptions);

哦,那个......我不知道,我认为这是误导性的代码,因为循环永远不会执行。虽然更简洁,但我认为原始代码更易于理解。 - Dan Puzey
@musefan 不,我不想要它,我想要把A去掉。 - Anarion
1
在循环中使用throw语句对我来说似乎是一种代码异味。在我的答案中,我汇总了y元素,这可能更有效。 - Magus
1
@lazyberezovsky 重点是它不在外部范围内,这似乎是目标。你可以重复这个代码5次而不需要改变变量名,不像你的解决方案,你正在填充命名空间中不需要的变量。接下来,有什么难以理解的呢?如果序列中有一个项目,它会抛出一个异常,如果没有,它就不会。它在逻辑上与在if内部抛出相等复杂度。 - Servy
1
@lazyberezovsky,有什么难以理解的吗?它的阅读方式与其功能完全一致。实际上,它确切地涵盖了要求。你只是不太习惯处理序列。相比函数式编程风格,你更熟悉命令式编程风格。对于来自不同背景的人来说,这种解决方案可能会更易读。代码本身并没有固有的可读性高或低;它只是一种你个人不习惯看到的模式。随着你的接触增多,你会逐渐习惯它。 - Servy
显示剩余9条评论

7

如果你将变量赋值和定义结合起来,你的代码会更加易读:

A a = collection.FirstOrDefault(x => x.Field == null);

if(a != null)    
   throw new ScriptException("{0}", a.y);

1
谢谢。易读且简单。感谢其他所有人 - 提供了许多好的答案。 - Anarion
@IAbstract 抱歉,打错字了。已经修正! - Sergey Berezovskiy
无法选择这个和@Servy的。你的可读性很好。Serby的“出奇制胜”)) - Anarion
@Anarion 由你决定 :) 你是在编写难题还是干净自我描述的代码? ;) - Sergey Berezovskiy

2

由于你需要在 if 外分配变量并在内部引用值,因此无法避免声明变量。(唯一的方法是执行两次过滤,这可能很昂贵)。

话虽如此,你可以利用这个特性来使代码更易读:

A a = collection.FirstOrDefault(x => x.Field == null); // assign here
if(a != null) // more easily-readable comparison here
{
  throw new ScriptException("{0}", a.y); 
}

1
在这种情况下,无法摆脱A a。您需要存储来自LINQ语句的返回值以供稍后使用,并且与using块不同,if语句不允许您在其表达式中定义变量。
个人而言,我会这样做:
A a = collection.FirstOrDefault(x => x.Field == null);
if(a != null)
{
    throw new ScriptException("{0}", a.y);
}

这并没有摆脱本地变量,因为问题所要求的是。 - Servy
我知道。我是说在这种情况下是不可避免的。我会像其他人提到的那样,将声明和赋值组合起来,使代码更清晰。 - Chris Mantle
@lazyberezovsky "// 我想要摆脱这个变量" - Servy
@Servy "如果有办法能够以某种方式简化它 - 那将非常好" :) - Sergey Berezovskiy
@lazyberezovsky 是的,他要求两者都做到。这个只实现了其中一个,而没有实现另一个。 - Servy

1
所以你的逻辑是:如果有任何一个项目没有字段,则抛出异常。
var a = collection.Where(x => x.Field == null);
if(a.Any())
{
  throw new ScriptException("{0}", a.First().y);
}

"甚至更好的方法可能是将它们整理在一起:"
var a = collection.Where(x => x.Field == null).Select(x => x.y);
if(a.Any())
{
  throw new ScriptException("{0}", string.Join(',', a));
}

那样你就可以看到所有的实例。

1
请注意,这会枚举集合两次,而不像其他答案只枚举一次。 - Servy

0
你可以使用 .Select 来去除对 a 的外部引用,尽管结果仍然有点混乱:
collection.Where(x => x.Field == null)
    .Select(a => string.Format("{0}", a.y))
    .Take(1).ToList().ForEach(msg => 
        throw new ScriptException(msg);
    );

Take(1) 如果没有匹配项,则返回一个空的可枚举对象,因此在 ForEach 中的块将运行零次或一次。


这并没有过滤掉Field为空的项目。 - Servy
你需要在选择之前进行过滤,否则你无法访问该字段,你的选择会出现问题,这只是我之前发布的答案的较差版本。 - Servy
谢谢大家……现在还太早,我的大脑还没有完全运转过来。(应该是正确的了) - McGarnagle
@Servy,你能告诉我他在使用什么类型的“Select”吗?这对我来说很奇怪。 - King King
1
@KingKing,这不是有效的,就像我在评论中所说的那样。他要么添加了自己的内容但没有分享实现方式,要么省略了string.Format调用。 - Servy
显示剩余3条评论

0
这个对你有用吗?虽然不太好看,但你可以在 lambda 中抛出异常。
collection.FirstOrDefault(x => { 
                                 if(x.Field == null)
                                 {
                                       throw new ScriptException("{0}", x.y);
                                 }
                                 return false;
                                }
                          );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接