【问题标题】:C# better to return a List or modify an existing one?C# 更好地返回列表或修改现有列表?
【发布时间】:2014-09-21 04:26:34
【问题描述】:

一般来说,这样做更好吗:

public void Foo1(List<int> list)
{
    list.Add(1);
}

或者这个:

public List<int> Foo2()
{
    List<int> list = new List<int>();
    list.Add(1);
    return list;
}

我问的原因是因为我目前正在使用第一种方式(除了方法不同并且显然更复杂),这需要我始终使用两行来调用该方法(这加起来到很多额外的行):

List<int> list = new List<int>();
Foo1(list);

而第二种方式我可以只使用一行:

List<int> list = Foo2();

那么考虑到时间和空间,哪种方式更好?

编辑:好的,更具体地说,我有一个方法可以将 T 类型的所有控件从 ControlCollection 添加到 List。

public static void GetControlsRec<T>(Control.ControlCollection controlCollection, List<T> resultCollection) where T : Control
{
    foreach (Control control in controlCollection)
    {
        if (control is T)
            resultCollection.Add((T)control);

        if (control.HasChildren)
            GetControlsRec(control.Controls, resultCollection);
    }
}

在这种情况下哪个会更好?

【问题讨论】:

  • 更有意义的那个更好。如果该方法看起来像是对列表进行了变异,请让它这样做。如果该方法在更改列表时最有用,请这样命名。否则,不要。
  • 这完全取决于上下文。有时可以修改一些东西;其他时候最好不要。仅凭这么多信息我们无法判断。
  • 好的,我添加了更多细节。

标签: c# performance list parameters return


【解决方案1】:

一般来说,我通常会尽量避免更改/改变现有集合。因此,我几乎总是更喜欢您的第二个选项。

话虽如此,它确实有创建一个 new List&lt;T&gt; 的缺点,这意味着更多的内存分配。如果(且仅当)性能是该特定代码段中的问题,您可能需要考虑直接修改输入列表,但我建议选择一个方法名称,这使得您显然正在改变集合。

【讨论】:

    【解决方案2】:

    因此,您遇到的问题是您有一个递归方法,在该方法中,每次对该方法的调用在概念上都会将一些项目添加到结果集合中。这导致了您正确识别的两种概念方法:

    1. 让每个递归调用返回它所代表的所有结果的集合。这要求每次调用都提取任何递归调用的结果并将它们添加到自己的集合中。这是相当低效和浪费的;你最终会一遍又一遍地复制数据。 (也就是说,除非您使用可以有效地“添加来自同一类型的另一个实例的所有结果”的数据结构。LinkedList(您自己滚动,因为 .NET 版本不支持这一点)可以做得好,或者一些不可变的数据结构。)

    2. 传入一个可变集合类型并让每个递归调用改变集合。这将表现良好,但会导致代码难以推理的问题。您不能只拔出一棵“子树”并孤立地看待它。调用者也很尴尬,因为他们需要创建一个集合,将其留空,存储对它的引用,以便在调用方法后可以访问它等等。这非常令人困惑且容易出错。

    选项 1,尽管它使程序员的事情变得更容易,但确实非常浪费(如果您不通过使用另一种类型的集合进行某种优化,如所述)。如果您确实使用 section 选项,我会强烈建议将其从调用者那里抽象出来。具体来说,使重载接受List 私有,并具有该方法的单独公共重载,而没有将它创建的列表传递给私有重载然后返回该列表的额外参数。这让调用者认为您正在使用类似于第一种方法的方法,同时仍然获得第二种方法的性能优势。但它仍然使开发复杂化。

    另一种选择是完全避免递归,这是我个人的偏好。当您迭代地而不是递归地解决问题时,所有问题都会消失:

    public static IEnumerable<Control> GetAllChildren(this Control root)
    {
        var stack = new Stack<Control>();
        stack.Push(root);
    
        while (stack.Any())
        {
            var next = stack.Pop();
            foreach (Control child in next.Controls)
                stack.Push(child);
            yield return next;
        }
    }
    

    (如果您希望特定类型的所有控件只需在此查询的结果上调用OfType,最好将“获取所有子项”的逻辑操作与“将集合过滤为仅这些控件类型”。)

    【讨论】:

    • 谢谢,非常详细的回答,我更喜欢迭代方式。我将实现更改为使用队列而不是堆栈,因为我想按放入控件的顺序获取控件。
    【解决方案3】:

    在您的具体情况下,我会推荐一个生成器:

    public static IEnumerable<T> GetControlsRec<T>(Control.ControlCollection controlCollection) where T : Control
    {
        foreach (Control control in controlCollection)
        {
            if (control is T)
               yield return (T)control;
    
            if (control.HasChildren)
                foreach (T descendant in GetControlsRec(control.Controls))
                    yield return descendant;
        }
    }
    

    然后:

    list.AddRange(GetControlsRec<…>(…));
    

    (对不起,如果语法不太正确,但你明白了。)

    【讨论】:

      【解决方案4】:

      在一般情况下,恕我直言,没有一个全面的答案。这真的取决于上下文和环境。

      在这种特定情况下,我可能会返回一个 IEnumerable 并让 调用者 决定是否将其放入列表中:

      public static IEnumerable<T> GetControlsRec<T>(Control.ControlCollection controlCollection)
          where T : Control
      {
          foreach (Control control in controlCollection)
          {
              if (control is T)
                  yield return (T)control;
      
              if (control.HasChildren)
                  foreach (T child in GetControlsRec(control.Controls))
                      yield return child;
          }
      }
      

      或者更好的是,给Control 本身添加一个扩展方法,类似于:

      public static IEnumerable<T> GetDecendentsOfType<T>(this Control c)
      {
          foreach (Control control in c.Controls)
          {
              if (control is T)
                  yield return (T)control;
      
              if (control.HasChildren)
                  foreach (T child in control.GetDecendentsOfType<T>())
                      yield return child;
          }
      }
      

      然后可以简单地称为:

      Control myControl = Master.MakeMeAControl();
      List<CheckBox> allCheckBoxesInControl = myControl.GetDecendentsOfType<CheckBox>().ToList();
      

      【讨论】:

        【解决方案5】:

        两者之间没有[可检测的]区别

        List<int> list = new List<int>();
        Foo(list);
        .
        .
        .
        void Foo( List<int> c )
        {
           c.Add(1) ;
           return ;
        }
        

        List<int> list = Foo();
        .
        .
        .
        List<int> Foo()
        {
          List<int> c = new List<int>() ;
          c.Add(1) ;
          return c;
        }
        

        第一个在将调用中的参数传递给Foo() 方面做了第二个没有做的一些工作;第二个做了一些第一个没有做的工作,将List&lt;int&gt;从调用中返回到Foo()。除此之外,基本上没有区别。

        找出您想要使用的语法(以及对您的设计有意义的语法)并使用它。几乎总是,清晰度和可维护性胜过其他任何事情。这样的事情不太可能对性能产生任何影响。

        更简单的是,您可以完全摆脱Foo()。比如:

        List<int> c = Enumerable.Range(1,1).ToList() ;
        

        List<int> c = new List<int>( new int[]{1} ) ;
        

        你应该这样做。

        【讨论】:

          【解决方案6】:

          鉴于您的额外信息,我认为您目前拥有它的方式是正确的。由于您使用递归来走一棵树,因此您可以继续传递列表。如果您没有传递列表,那么您必须创建它(每个级别的递归一次)。

          public static List<T> resultCollection GetControlsRec<T>(Control.ControlCollection controlCollection) where T : Control
          {
              ///Create new collection
              List<T> resultCollection = new List<T>();
              foreach (Control control in controlCollection)
              {
                  if (control is T)
                      resultCollection.Add((T)control);
          
                  if (control.HasChildren)
                      resultCollection.AddRange(GetControlsRec(control.Controls, resultCollection));
              }
          
              return resultCollection;
          }
          

          【讨论】:

            【解决方案7】:

            我通常做的是

            public IList<int> Foo1(IList<int> list)
            {
                list.Add(1);
                return list;
            }
            

            对我来说,它更具可读性,因为您可以清楚地识别输入和输出。 而且因为对象是通过引用传递的,所以性能是一样的。

            希望对你有帮助^^

            【讨论】:

            • 返回值给我的印象是输入不会改变。
            • 除非你像这样使用它:myList = Foo1(myList);
            • 这只是多余的。
            • @rdelcoig 当我看到我现在相信该方法不会改变列表时,因为您觉得有必要将结果分配回初始变量。这是高度误导。
            猜你喜欢
            • 1970-01-01
            • 2011-04-20
            • 2017-03-23
            • 1970-01-01
            • 1970-01-01
            • 2020-05-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多