如果集合不为空,则标记空集合或枚举成员 (C#)

Flag empty collection or enumerate over members if collection is not empty (C#)

(对我而言)显而易见的解决方案如下:

if (widgets.Count == 0)
{
    //Handle empty collection
}
else 
{
    // Handle non-empty collection
    foreach (widget in widgets)
    {
        // Process widget
    }
}

这似乎是缩进的。我很想用 "else foreach" 删除一定程度的缩进,但当我有这个想法时,我的脑子里立刻响起了一声低沉的(但在不断增强)尖叫:

if (widgets.Count == 0)
{
    //Handle empty collection
}
else foreach (widget in widgets)
{
    // Process widget
}

还有其他想法或建议吗?

您似乎很关心代码的可读性和可维护性。像这样的代码块:

if (someCondition)
{
    // do
    // a
    // lot      
    {
        // moar
        // nesting
    }
}
else
{
    // do 
    // something
    // else
    {
        // possibly with even more nesting
    }
}

通常被认为既不可读也不可维护。当然,您随后可以滥用语法或 use other tricks to reduce the level of nesting,但是您有如此多的嵌套这一事实应该是一个死的赠品,表明一个方法做得太多了。

至少将代码块提取到自己的方法中,自动减少嵌套。

如果将上面的代码放在它自己的方法中开始,您可以从 if() 中 return,完全删除 else 块。

public void DoSomethingWithWidgets(ICollection<Widget> widgets)
{
    if (widgets.Count == 0)
    {
        HandleEmptyCollection();
        return;
    }

    foreach (widget in widgets)
    {
        ProcessWidget(widget);
    }
}

或者将代码完全放在它们自己的 class 中,让 class 自己确定它是否适用于它的输入。你可以这样实现:

public interface IVisitor
{
    bool CanVisit(ICollection<Widget> widgets);

    void Visit(ICollection<Widget> widgets);
}

public class EmptyCollectionVisitor : IVisitor
{
    public bool CanVisit(ICollection<Widget> widgets)
    {
        return widgets.Count == 0;
    }

    public void Visit(ICollection<Widget> widgets)
    {
        // Handle empty collection
    }
}

public class NotEmptyCollectionVisitor : IVisitor
{
    public bool CanVisit(ICollection<Widget> widgets)
    {
        return widgets.Count > 0;
    }

    public void Visit(ICollection<Widget> widgets)
    {
        foreach (var widget in widgets)
        {
            // Process widget
        }
    }
}

然后让所有访问者访问该馆藏(如果可以的话),就完成了。 classes 本身具有更好的可测试性和可重用性。

然后您可以通过遍历注册访客来使用它:

var yourInputCollection = { either a list that is empty or not };

IVisitor[] collectionVisitors = new[] { 
    new EmptyCollectionVisitor(), 
    new NotEmptyCollectionVisitor() 
};

foreach (var visitor in collectionVisitors)
{
    if (visitor.CanVisit(yourInputCollection))
    {
        visitor.Visit(yourInputCollection);
    }
}

是的,这看起来有点矫枉过正,但滥用语法以在尽可能小的水平 space 上容纳尽可能多的代码在我的规模上排名甚至更低。

在这种情况下,我很想使用你之前提到的模式:

if (myVar.Value < 1)
{
    //...
}
else switch (myVar.Value)
{
    case 1:
        //...
        break;
    case 2:
        //...
        break;
}

问题是 Visual Studio 自动缩进会不断更改它并在 else 语句之后缩进代码。

我的建议是使用您问题中的第一个示例。消除你的 else 块对 readability/maintainability 没有多大作用。

世界上没有比一级缩进更糟糕的事情了,所以我肯定更喜欢前者而不是第二种不寻常的风格。

另一种方法是删除 foreach:

的语法糖
using (var en = widgets.GetEnumerator())
{
  if (en.MoveNext())
  {
    do
    {
      var widget = en.Current;
      // process widget.
    } while (en.MoveNext());
  }
  else
  {
    // Handle empty.
  }
}

在语法上它更糟糕,但它确实有一些优势。

  1. 您现在可以将它与任何 IEnumerable<Widget> 一起使用,而不必坚持使用 ICollection<Widget> 这意味着您可以跳过在内存中创建集合,这样您就可以看到计数是多少。

  2. 当我们要(隐藏在foreach中)在第一次调用中查找它是否为空时,我们跳过Count调用和分支会稍微更有效率MoveNext() 无论如何。

当您需要为第一项做一些不同的事情时(例如,从第一项创建一个累加器,然后聚合操作将根据其余项进行更改),类似的方法非常有用。

就是说,除非它处于热路径中或者使用所有可枚举的能力是一个真正的收获,否则我只会将您问题中的第一种情况作为最传统的方法。如果缩进真的那么糟糕(因为其中代码的大小使其难以阅读),您总是可以将其分解为其他方法。

在您的特定情况下,我可能会选择一个辅助变量来跟踪枚举是否为空。

bool empty = true;

foreach (widget in widgets) {
  empty = false;

  // Process widget
}

if (empty) {
  // Handle empty collection
}

这与 Jon Hanna 关于处理任何 IEnumerable<Widget> 而无需枚举两次的回答具有相同的优势,但在我看来,以一种更具可读性的形式,并且自然地避免了对一级块的需要。