好的,我们有这个混乱的东西:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(x => {
var count = Users.ToList().Where(t => t.ServerId == x.Id).Count();
items.Add(new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}@{x.ServerName} ({count} users on)" });
});
return items;
进行一系列小心谨慎、显然正确的重构,逐步改进代码。
首先要做的是:将那些复杂的操作抽象成它们自己的方法。
请注意,我已经用有帮助的server
替换了无用的x
。
int UserCount(Server server) =>
Users.ToList().Where(t => t.ServerId == server.Id).Count()
为什么Users
上会有ToList
方法?看起来不太对啊。
int UserCount(Server server) =>
Users.Where(t => t.ServerId == server.Id).Count()
我们注意到有一个内置方法可以同时执行这两个操作:
int UserCount(Server server) =>
Users.Count(t => t.ServerId == server.Id)
同样地,创建一个项目也是如此:
SelectListItem CreateItem(Server server, int count) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}@{server.ServerName} ({count} users on)"
};
现在我们的属性体是:
var items = new List<SelectListItem>();
Servers.ToList().ForEach(server =>
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
});
return items;
已经好多了。
如果你只是要传递一个lambda表达式,请不要将ForEach
作为方法使用!语言中已经有更好的内置机制!当你可以简单地写foreach(var item in items) { ... }
时,就没有理由写items.Foreach(item => {...});
。这样做更简单、更易于理解和调试,并且编译器可以更好地进行优化。
var items = new List<SelectListItem>();
foreach (var server in Servers.ToList())
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
好多了。
为什么在Servers
上有一个ToList
?完全没有必要!
var items = new List<SelectListItem>();
foreach(var server in Servers)
{
var count = UserCount(server);
items.Add(CreateItem(server, count);
}
return items;
变得更好了。我们可以消除不必要的变量。
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server, UserCount(server));
return items;
嗯,这使我们了解到 CreateItem
可能正在进行计数。让我们重新编写它。
SelectListItem CreateItem(Server server) =>
new SelectListItem
{
Value = server.Id.ToString(),
Text = $"{server.InstanceCode}@{server.ServerName} ({UserCount(server)} users on)"
};
现在我们的属性主体是:
var items = new List<SelectListItem>();
foreach(var server in Servers)
items.Add(CreateItem(server);
return items;
这应该很熟悉。我们重新发明了 Select
和 ToList
:
var items = Servers.Select(server => CreateItem(server)).ToList()
现在我们注意到 lambda 可以被方法组替换:
var items = Servers.Select(CreateItem).ToList();
我们把这整个混乱的过程简化为一行代码,
清晰明了地展示出它所完成的功能。它做什么?它为每个服务器创建一个条目,并将它们放入列表中。
代码应该像它所完成的功能那样读起来,而不是它是如何实现的。
仔细研究我在这里使用的技巧:
- 将复杂的代码提取到帮助方法中
- 用真正的循环替换
ForEach
- 消除不必要的
ToList
- 当您意识到可以进行改进时,请重新审视之前的决策
- 认识到何时在重新实现简单的帮助方法
- 不要止步于一个改进!每一个改进都使得另一个改进成为可能。
如果我想按用户计数对此下拉列表进行排序怎么办?
那就按用户计数排序!我们将其抽象为帮助方法,因此我们可以使用它:
var items = Servers
.OrderBy(UserCount)
.Select(CreateItem)
.ToList();
我们现在注意到我们调用了
UserCount
两次。我们在意吗?也许。调用两次可能会导致性能问题,或者更糟糕的是,可能不是幂等的!如果有任何一个问题,那么我们需要撤销之前做出的决定。在理解模式下处理这种情况比流畅模式更容易,所以让我们将其重写为一个理解:
var query = from server in Servers
orderby UserCount(server)
select CreateItem(server);
var items = query.ToList();
现在我们回到之前的内容:
SelectListItem CreateItem(Server server, int count) => ...
现在我们可以说,
var query = from server in Servers
let count = UserCount(server)
orderby count
select CreateItem(server, count);
var items = query.ToList();
我们每台服务器只调用一次UserCount
。
为什么要回到理解模式?因为在流畅模式下这样做会弄乱:
var query = Servers
.Select(server => new { server, count = UserCount(server) })
.OrderBy(pair => pair.count)
.Select(pair => CreateItem(pair.server, pair.count))
.ToList();
看起来有点丑。(在C# 7中,您可以使用元组而不是匿名类型,但思路是一样的。)
var items = Servers.Select(x => { var count = Users.Where(t => t.ServerId == x.Id).Count(); return new SelectListItem { Value = x.Id.ToString(), Text = $"{x.InstanceCode}@{x.ServerName} ({count} users on)" }); }).ToList();
这段代码可能会让你走上正确的轨道。 - mjwillsWhere
之前调用ToList
吗?你可能希望它在SQL端运行,而不是将整个表下载到内存中,然后进行过滤。你应该考虑完全删除ToList
,因为它只会减慢速度而没有任何好处。 - Bradley Uffner