删除C#中重复的硬编码循环和条件

时间:2008-10-16 21:18:08

标签: c# refactoring loops conditional-statements

我有一个类来比较相同对象的2个实例,并生成它们的差异列表。这是通过循环遍历密钥集合并使用已更改内容的列表填充一组其他集合来完成的(这在查看下面的代码后可能更有意义)。这样可以生成一个对象,让我知道在“旧”对象和“新”对象之间添加和删除的确切内容。
我的问题/担忧是......它真的很难看,有很多循环和条件。是否有更好的方法来存储/接近这一点,而不必过分依赖无穷无尽的硬编码条件?

    public void DiffSteps()
    {
        try
        {
            //Confirm that there are 2 populated objects to compare
            if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
            {
                //<TODO> Find a good way to compare quickly if the objects are exactly the same...hash?

                //Compare the StepDoc collections:
                OldDocs = SavedStep.StepDocs;
                NewDocs = NewStep.StepDocs;
                Collection<StepDoc> docstoDelete = new Collection<StepDoc>();

                foreach (StepDoc oldDoc in OldDocs)
                {
                    bool delete = false;
                    foreach (StepDoc newDoc in NewDocs)
                    {
                        if (newDoc.DocId == oldDoc.DocId)
                        {
                            delete = true;
                        }
                    }
                    if (delete)
                        docstoDelete.Add(oldDoc);
                }

                foreach (StepDoc doc in docstoDelete)
                {
                    OldDocs.Remove(doc);
                    NewDocs.Remove(doc);
                }


                //Same loop(s) for StepUsers...omitted for brevity

                //This is a collection of users to delete; it is the collection
                //of users that has not changed.  So, this collection also needs to be checked 
                //to see if the permisssions (or any other future properties) have changed.
                foreach (StepUser user in userstoDelete)
                {
                    //Compare the two
                    StepUser oldUser = null;
                    StepUser newUser = null;

                    foreach(StepUser oldie in OldUsers)
                    {
                        if (user.UserId == oldie.UserId)
                            oldUser = oldie;
                    }

                    foreach (StepUser newie in NewUsers)
                    {
                        if (user.UserId == newie.UserId)
                            newUser = newie;
                    }

                    if(oldUser != null && newUser != null)
                    {
                        if (oldUser.Role != newUser.Role)
                            UpdatedRoles.Add(newUser.Name, newUser.Role);
                    }

                    OldUsers.Remove(user);
                    NewUsers.Remove(user);
                }

            } 
        }
        catch(Exception ex)
        {
            string errorMessage =
                String.Format("Error generating diff between Step objects {0} and {1}", NewStep.Id, SavedStep.Id);
            log.Error(errorMessage,ex);
            throw;
        }
    }

目标框架是3.5。

6 个答案:

答案 0 :(得分:7)

您使用的是.NET 3.5吗?我确信LINQ to Objects会使很多很多更简单。

要考虑的另一件事是,如果你有很多具有共同模式的代码,只有一些事情会发生变化(例如“我在比较哪个属性?”那么这是一个很好的候选者,可以采用通用方法代表该差异的代表。

编辑:好的,现在我们知道我们可以使用LINQ:

第1步:减少嵌套 首先,我会采取一个级别的嵌套。而不是:

if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
{
    // Body
}

我会这样做:

if (NewStep.Id != Guid.Empty && SavedStep.Id != Guid.Empty)
{
    return;
}
// Body

像这样的早期回报可以使代码更具可读性。

第2步:查找要删除的文档

如果你只需要为Enumerable.Intersect指定一个关键函数,那就更好了。您可以指定相等比较器,但即使使用实用程序库,构建其中一个也很痛苦。好吧。

var oldDocIds = OldDocs.Select(doc => doc.DocId);
var newDocIds = NewDocs.Select(doc => doc.DocId);
var deletedIds = oldDocIds.Intersect(newDocIds).ToDictionary(x => x);
var deletedDocs = oldDocIds.Where(doc => deletedIds.Contains(doc.DocId));

第3步:删除文档
使用现有的foreach循环,或更改属性。如果您的属性实际上是List&lt; T&gt;类型然后你可以使用RemoveAll。

第4步:更新和删除用户

foreach (StepUser deleted in usersToDelete)
{
    // Should use SingleOfDefault here if there should only be one
    // matching entry in each of NewUsers/OldUsers. The
    // code below matches your existing loop.
    StepUser oldUser = OldUsers.LastOrDefault(u => u.UserId == deleted.UserId);
    StepUser newUser = NewUsers.LastOrDefault(u => u.UserId == deleted.UserId);

    // Existing code here using oldUser and newUser
}

进一步简化操作的一个选项是使用UserId实现IEqualityComparer(以及使用DocId的文档实现一个)。

答案 1 :(得分:2)

当您至少使用.NET 2.0时,我建议在StepDoc上实现Equals和GetHashCode(http://msdn.microsoft.com/en-us/library/7h9bszxx.aspx)。作为如何清理代码的提示,您可以使用以下内容:

 Collection<StepDoc> docstoDelete = new Collection<StepDoc>();
foreach (StepDoc oldDoc in OldDocs)
                    {
                        bool delete = false;
                        foreach (StepDoc newDoc in NewDocs)
                        {
                            if (newDoc.DocId == oldDoc.DocId)
                            {
                                delete = true;
                            }
                        }
                        if (delete) docstoDelete.Add(oldDoc);
                    }
                    foreach (StepDoc doc in docstoDelete)
                    {
                        OldDocs.Remove(doc);
                        NewDocs.Remove(doc);
                    }

用这个:

oldDocs.FindAll(newDocs.Contains).ForEach(delegate(StepDoc doc) {
                        oldDocs.Remove(doc);
                        newDocs.Remove(doc);
                    });

这假设oldDocs是StepDoc的列表。

答案 2 :(得分:1)

如果StepDocs和StepUsers都实现了IComparable&lt; T&gt;,并且它们存储在实现IList&lt; T&gt;的集合中,那么您可以使用以下帮助器方法来简化此功能。只需调用两次,一次使用StepDocs,一次使用StepUsers。使用beforeRemoveCallback实现用于执行角色更新的特殊逻辑。我假设这些集合不包含重复项。我遗漏了参数检查。

public delegate void BeforeRemoveMatchCallback<T>(T item1, T item2);

public static void RemoveMatches<T>(
                IList<T> list1, IList<T> list2, 
                BeforeRemoveMatchCallback<T> beforeRemoveCallback) 
  where T : IComparable<T>
{
  // looping backwards lets us safely modify the collection "in flight" 
  // without requiring a temporary collection (as required by a foreach
  // solution)
  for(int i = list1.Count - 1; i >= 0; i--)
  {
    for(int j = list2.Count - 1; j >= 0; j--)
    {
      if(list1[i].CompareTo(list2[j]) == 0)
      {
         // do any cleanup stuff in this function, like your role assignments
         if(beforeRemoveCallback != null)
           beforeRemoveCallback(list[i], list[j]);

         list1.RemoveAt(i);
         list2.RemoveAt(j);
         break;
      }
    }
  }
} 

以下是更新代码的beforeRemoveCallback示例:

BeforeRemoveMatchCallback<StepUsers> callback = 
delegate(StepUsers oldUser, StepUsers newUser)
{
  if(oldUser.Role != newUser.Role)
    UpdatedRoles.Add(newUser.Name, newUser.Role);
};

答案 3 :(得分:0)

您定位的是哪个框架? (这会对答案产生影响。)

为什么这是一个无效功能?

签名不应该像:

DiffResults results = object.CompareTo(object2);

答案 4 :(得分:0)

如果你想隐藏树状结构的遍历,你可以创建一个隐藏“丑陋”循环结构的IEnumerator子类,然后使用CompareTo接口:

MyTraverser t =new Traverser(oldDocs, newDocs);

foreach (object oldOne in t)
{
    if (oldOne.CompareTo(t.CurrentNewOne) != 0)
    {
        // use RTTI to figure out what to do with the object
    }
}

然而,我完全不确定这会特别简化任何事情。我不介意看到嵌套的遍历结构。代码是嵌套的,但并不复杂或特别难以理解。

答案 5 :(得分:0)

在foreach中使用多个列表很容易。这样做:

foreach (TextBox t in col)
{
    foreach (TextBox d in des) // here des and col are list having textboxes
    {
        // here remove first element then and break it
        RemoveAt(0);
        break;
    }
}

它的工作方式类似于foreach(在col&amp;&amp; TextBox d中的文本框t在des中)