【问题标题】:Func<> or method in test code?测试代码中的 Func<> 或方法?
【发布时间】:2013-04-17 10:05:56
【问题描述】:

我在测试代码中看到了这个循环:

foreach ( StuffId Id in Result.GetIdList() )
{
    if ( Id.Level == 3 )
    {
        Level3Id = Id.ToString();
    }
    if ( Id.Level == 5 )
    {
        Level5Id = Id.ToString();
    }
}

其他测试意味着每个级别只有一个 Id,或者当每个级别有多个时,ID 将相同。

我现在对 LINQ 有点痴迷,我首先重构了这个:

IEnumerable<StuffId> Ids = Result.GetIdList();

Level3Id = Ids.Where( x => x.Level == 3 ).First().Id.ToString();
Level5Id = Ids.Where( x => x.Level == 5 ).First().Id.ToString();

然后代码重复困扰着我,所以我重构了这个:

IEnumerable<StuffId> Ids = Result.GetIdList();
Func<int,string> IdFromLevel = 
    level => Ids.Where( x => x.Level == level ).First().Id.ToString();

Level3Id = IdFromLevel(3);
Level5Id = IdFromLevel(5);

一位同事想知道为什么我没有使用方法代替委托。我的推理是一种方法会稍微“混乱”一些,因为我必须另外传入集合,并且使用委托对于简单的测试来说没什么大不了的(简洁、可读且没有分支是好的品质)。

当然,我查看了 SO,发现了这个看似相关的问题:

C#: Func<> instead of methods?

共识似乎倾向于一种方法而不是委托。我的情况也一样吗?

【问题讨论】:

  • 是的,最好选择方法......出于同样的原因,在回答该问题时提到......
  • foreach 代码使用最后一个元素的值,您的 linq 代码是第一个元素。所以它们不等价,除非只有一个匹配项,在这种情况下,您的 LINQ 代码应该使用 Single 而不是 First
  • 您的 LINQ 的效率也低于原始循环,因为它会枚举集合两次。
  • @CodesInChaos:我在我的问题中承认了您的观点:“测试意味着每个级别只有一个 ID,或者当每个级别有多个 ID 时,ID 将是相同的”。在使用 First 之前,我可以按降序排列索引,但会是相同的(由其他测试保证)。
  • @Pranay Rana: 好的,所以你忽略了我认为有利于 lambda 的东西(简洁、可读、没有分支、对 lambda 有轻微的痴迷)但是 *为什么* 你打折吗?链接的答案不是测试代码,因此具有不同的意图。

标签: c# linq refactoring func


【解决方案1】:

这是一个重用的问题。如果您使用的方法可能在其他情况下可重用,则应单独定义它们。
但是,如果您只有简短的语句而且会有所不同,您应该坚持使用匿名函数/lambda 表达式,这些可能会导致更好的运行时行为。

来自SO

您发布的代码没有任何优势。在您的代码中,使用委托只会增加复杂性以及额外的运行时成本 - 所以您最好直接调用该方法。

但是,委托有很多用途。 “传递”给其他方法是主要用法,但存储一个函数并在以后使用它也非常有用。

LINQ 完全建立在这个概念之上。当你这样做时:

var results = myCollection.Where(item => item == "Foo");

您将委托(定义为 lambda:item => item == "Foo")传递给 LINQ 库中的 Where 函数。这就是让它正常工作的原因。

【讨论】:

  • 好的,that 问题是否适用:) “您发布的代码没有优势”——大概适用于另一个问题。在 my 问题中,我陈述了 对我 的优点,即简洁、无分支、可读(由于“自包含”)。
  • “使用委托,您可以内联您的实现并可以访问范围内的变量”——这对我来说听起来也不错(我指出我有点做了)。
  • 这就是我写“From SO:”的原因
【解决方案2】:
【解决方案3】:

我会选择第一个区块:

foreach (StuffId Id in Result.GetIdList())
{
    if (Id.Level == 3)
    {
        Level3Id = Id.ToString();
    }
    if (Id.Level == 5)
    {
        Level5Id = Id.ToString();
    }
}

这只会循环一次集合。我看到你不担心这里的性能,但对我来说这不是性能或优化的问题。这是做一些逻辑上正确的事情的问题。如果可以一步完成,为什么要做两次(只要不损害可读性)。

额外的好处是您不会在Func&lt;,&gt; 和方法以及相关的复杂性之间陷入两难境地。就字符数或打字的难易程度而言,它几乎相同,除了你是水平书写(在第二种情况下)而不是垂直书写。您甚至可以在 foreach 块内将上述内容写成两行。

如果你一心想分别写这两个动作,我会根据这个函数在这种情况下是否在当前方法的范围之外有相关性来做出选择。在我看来,它是一个微不足道的谓词,仅与两个任务相关。所以我喜欢:

var Ids = Result.GetIdList();
Func<int, string> IdFromLevel = level => Ids.Single(x => x.Level == level).Id.ToString();

Level3Id = IdFromLevel(3);
Level5Id = IdFromLevel(5);

更喜欢Single这里而不是First..

【讨论】:

    猜你喜欢
    • 2011-06-22
    • 1970-01-01
    • 2013-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 1970-01-01
    相关资源
    最近更新 更多