C#最好返回List还是修改现有的?

时间:2014-07-29 17:25:40

标签: c# performance list parameters return

一般来说,最好是这样做:

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);
    }
}

在这种情况下哪个更好?

7 个答案:

答案 0 :(得分:5)

通常,我通常会尝试避免更改/改变现有集合。因此,我几乎总是喜欢你的第二个选择。

话虽如此,它确实有创建 new List<T>的缺点,这意味着更多的内存分配。如果(并且仅当)性能是特定代码段中的问题,您可能需要考虑直接修改输入列表,但我建议选择一个方法名称,这样可以明显地改变集合。< / p>

答案 1 :(得分:5)

所以你遇到的问题是你有一个递归方法,其中对方法的每次调用都在概念上将一些项添加到结果集合中。这导致了两种概念方法,您可以正确识别它们:

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

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

  3. 选项1,尽管它使程序员更容易,但实际上非常浪费(如果你没有通过使用其他类型的集合进行某种优化,如上所述)。如果你选择了部分选项,我会强烈建议从呼叫者那里抽象出来。具体来说,使重载接受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,那么最好将“获取所有子项”的逻辑操作从“过滤集合到只是这些类型的控件“。)

答案 2 :(得分:4)

在您的具体情况下,我会推荐一个发电机:

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<…>(…));

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

答案 3 :(得分:3)

在一般情况下,没有一个人回答恕我直言。这实际上取决于背景和环境。

在这种特定情况下,我可能会返回一个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();

答案 4 :(得分:1)

之间没有[可检测的]差异
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<int>Foo()调用回{{​​1}}。除此之外,基本上没有区别。

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

更简单,你可以完全摆脱Foo()。类似的东西:

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

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

应该做你。

答案 5 :(得分:0)

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

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;
}

答案 6 :(得分:-2)

我通常做的是

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

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

希望它有所帮助^^