保存循环或使用2种方法 - 约定与性能

时间:2009-12-23 20:22:16

标签: c# performance

因此,每个关心OOP中的最佳实践并保持代码清洁的人都知道这些方法不应该做多件事。方法是离散的单位,做一件事并离开。

但是,如果您要将两个方法组合在一起,并将第二个方法中已有的现有for循环重新组合使用,那么可以节省一些处理并提高性能,这是一种情况:

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemList.Count; s++)
    {
        if(!itemList[s].ItemIsOnSite)
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }              
    }
}

private void RemovePriceChangedItems()
{
    for (int s = 0; s < itemList.Count; s++)
    {
        if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }
}

这些在页面加载时调用。一个删除已停止的项目。另一个删除具有某些已更改的定价选项的项目,并将其从同一列表中删除。

现在,如果我们坚持使用最佳实践,可以说这些是完全独立的两个目的,因此我们不应该将这两种方法中的逻辑结合起来。然后,这将使该方法做两件事,我还必须提出一些新的名称,如RemoveDiscontinuedAndPriceChangeItems()或通用名称,不告诉我杰克sh **像RemoveInvalidItemsFromList():< / p>

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemsList.Count; s++)
    {
        if((!itemList[s].ItemIsOnSite))
        {
            RemoveItem(orderItemsSavedList[s].Id);  // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
        else if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }
然而,考虑到性能方面,调用2个循环遍历同一列表的方法来删除一些项目,在循环中会更加昂贵。

那么,任何反对组合的人还是你在这种情况下合并?想听听那里的一些意见。

12 个答案:

答案 0 :(得分:20)

为什么不重构,以便每个方法执行单个操作,而不是执行循环。然后在循环体中根据需要调用每个方法。

<强>更新 这是一个基于您的方法的简单示例。显然,在你的应用程序中,Loop方法会有所不同,但是,我没有任何其他上下文来处理你正在做的事情。另外,我将你的for循环改为foreach。

private void Loop()
{ 
    foreach (Item i in itemList)
    {
         if(!item.ItemIsOnSite)
         {
            RemoveDiscontinuedItems(i)
         }
         if(!item.PricingOptionIsValid)
         {
            RemovePriceChangedItems(i)
         }

    }
}

private void RemoveDiscontinuedItems(itemType item)
{

    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection              

}

private void RemovePriceChangedItems(itemType item)
{
    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection

}

答案 1 :(得分:7)

我认为你的因素是在错误的地方。

如果你这样写:

public void ConditionallyRemoveItems(Func<Item,bool> predicate)
{
    for (int s=0; s < itemsList.Count; s++) {
        if (predicate(itemList[s])) {
            RemoveItem(orderItemsSavedList[s].Id);
            itemList.RemoveAt(s);
            s--;
        }
    }
 }

 // ...
 ConditionallyRemoveItems(i => !i.ItemIsOnSize || !PricingOptionIsValid(i));

我也不太喜欢你弄乱循环变量的风格 - 我更喜欢这种风格:

List<Item> itemsToRemove = new List<Items>();
foreach (Item i in itemList) {
    if (predicate(i)) {
        itemsToRemove.Add(i);
    }
}
foreach (Item i in itemsToRemove)
    itemList.Remove(i);

如果你不喜欢它的表现,你可以随时这样做:

List<Item> itemsToKeep = new List<Items>();
foreach (Item i in itemList) {
    if (!predicate(i)) {
        itemsToKeep.Add(i);
    }
 }
 itemList = itemsToKeep;

答案 2 :(得分:6)

一些事情:

  • 我不明白为什么你在删除项目时向前循环。您应该向后循环,并在执行删除时避免混乱的索引操作。

  • 根据属性删除一个项目与另一个项目的关系是不正确的。您应该将其视为过滤列表。为此,您应该有一个方法需要Predicate<T>,然后返回IEnumerable<T>然后您可以枚举(或IList<T>,如果您想要变异列表)。这样,您就可以对列表进行过滤和条件分离(这是更好的分离IMO)。

  • 如果您可以访问LINQ,那么实际上没有理由这样做。您应该在原始列表中使用where过滤器,如果需要,将过滤逻辑分解为单独的方法,这些方法将获取该项并查看是否应该返回该项。然后,您可以构建where子句(或Predicate<T>以传递给Where扩展方法)。

答案 3 :(得分:2)

这可能是最简单的方法,使用循环

//counting backwards is easier when you are doing removals from a list
for( int i = lst.Count -1; i>= 0; i--)
{
    if(condition1 || condition2)
    {
         RemoveFromDB(lst[i]);
         lst.RemoveAt(i); 
    }
}

你可以重构它以使用框架提供的功能方法:

var toRemove = lst.FindAll( 
        item => !PricingOptionIsValid(item) || !item.ItemIsOnSite() 
       );
toRemove.ForEach( item => 
        {
            RemoveFromDB(item.ID);
            lst.Remove(item);
        });

你可以在没有toRemove变量的情况下编写它,将ForEach链接到FindAll上

答案 4 :(得分:2)

关于如何简化和澄清当前代码,有几个很好的建议。

在考虑性能总是从干净简洁的代码开始,在您确认需要优化之前不要担心优化。

几乎所有人都用高级语言写作:
a)可以更快。

b)快速充足。

答案 5 :(得分:1)

似乎标准方法,如果可能的话,是从循环内容中创建一个函数,以及执行这两个任务的函数:

doBothThings()
{
    for(sharedLoop)
    {
        function1(loop counter);
        function2(loop counter);
    }
}

为您提供性能,但仍然将两个功能分成不同的功能。显然,如果两个函数在循环之前/之后涉及代码,那就不那么简单了。

答案 6 :(得分:1)

在C#中,我几乎肯定不会重复删除逻辑。考虑类似的事情:

    private delegate bool RemovalExpression(ItemType item);
    private void RemoveItems(RemovalExpression shouldRemove)
    {
        for (int s = 0; s < itemList.Count; s++)
        {
            if (shouldRemove(itemList[s]))
            {
                RemoveItem(itemList[s].Id);
                itemList.RemoveAt(s);

                s--;
            }
        }
    }

可以自然地用作:

RemoveItems(item => !item.ItemIsOnSite);
RemoveItems(item => !PricingOptionIsValid(item));
RemoveItems(item => (!item.ItemIsOnSite || !PricingOptionIsValid(item)));

同样从它的声音来看,你不应该担心在这一点上循环微优化。如果您没有明确表示您在项目移除中花费不成比例的时间的数据,则无法知道哪个构造将“更快”,也无法知道您的选择是否值得时间投入,或不必要的并发症,或对业绩的彻底悲观化。

因此,为了可维护性而写;简单明了,不重复逻辑。

答案 7 :(得分:1)

由于您的两种方法都是从列表中删除项目,因此组合循环并不一定有意义。您应该确定您使用的两种方法之间是否存在显着的性能差异。

例如,如果PricingOptionIsValid是一项昂贵的操作(命中数据库或其他任何操作),那么在第一次循环中修剪尽可能多的项目后,您可能希望在第二次循环中调用它。

如果测试的顺序无关紧要,则应首先放置更可能的分支,然后将分支放在第二位。如果ItemIsOnSite仅在1%的时间内为假,那么您将花费大量的周期来跳过它。

您也可以考虑使用迭代器而不是操纵循环变量。无论是那个或只是在循环中找到要删除的项目,然后再做一个循环来删除它们。

答案 8 :(得分:1)

使用LINQ。而且,我们不是超过15分钟限制吗?

答案 9 :(得分:1)

如果我们正在讨论适当的OO,那么保留itemList和调用者同步的RemoveItem的责任可能不是一个好主意。

我个人会使用一个可以挂钩的OnRemove事件列表,并添加RemoveItem作为事件。这使得调用者无需记住调用RemoveItem。

这使代码更简单,允许您使用类似:

private void RemoveDiscontinuedItems()
{
    itemList.RemoveAll(x => !x.ItemIsOnSite);
}

private void RemovePriceChangedItems()
{
    itemList.RemoveAll(x => !PricingOptionIsValid(x));
}

代码更清晰,逻辑和目的更明显。

性能显然不必担心,除非它成为一个问题,但你必须记住用极值测试(在这种情况下是一个大的列表)。

如果您发现多次遍历列表实际上是一个瓶颈,那么我建议这样的事情:

private bool IsDiscontinuedItem(Item item)
{
    return !item.ItemIsOnSite;
}

private bool IsPriceChangedItem(Item item)
{
    return !PricingOptionIsValid(item);
}

private bool IsInvalidItem(Item item)
{
    return IsDiscontinuedItem(item) ||
           IsPriceChangedItem(item);
}

private void RemoveInvalidItems()
{
    itemList.RemoveAll(IsInvalidItem)
}

答案 10 :(得分:1)

做任何有意义的事情,降低复杂性,并且最容易维护。最佳实践是指南。

答案 11 :(得分:0)

这是我的版本:

    private void RemoveDiscontinuedItems()
    {
        RemoveItems(item => item.ItemIsOnSite);
    }

    private void RemovePriceChangedItems()
    {
        RemoveItems(item => PricingOptionIsValid(item));
    }

    private void RemoveAll()
    {
        RemoveItems(item => item.ItemIsOnSite || PricingOptionIsValid(item));
    }

    private void RemoveItems(Predicate<Item> removeIfTruePredicate)
    {
        itemList.RemoveAll(
            item =>
            {
                if(removeIfTruePredicate(item))
                {
                    RemoveItem(item);
                    return true;
                }

                return false;
            });
    }

请注意,这是基于您对可变列表的使用。我很想考虑是否真的需要一个可变列表。如果没有,我宁愿使用更多功能的样式,并使用linq where子句创建一个新列表,排除需要删除的项目。

考虑到你对性能的渴望,我的猜测是RemoveAll(我假设你正在使用List<T>)比创建新列表更快。当然,你真的需要检查一下这一点。