8

我有一个类,它比较相同对象的 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。

4

6 回答 6

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<T> 类型,那么您可以使用 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 的文档)。

于 2008-10-16T21:20:30.207 回答
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 的列表。

于 2008-10-16T21:42:29.880 回答
1

如果 StepDocs 和 StepUsers 都实现了 IComparable<T>,并且它们存储在实现 IList<T> 的集合中,那么您可以使用以下辅助方法来简化此功能。只需调用两次,一次使用 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);
};
于 2008-10-16T22:24:01.217 回答
0

你的目标是什么框架?(这将使答案有所不同。)

为什么这是一个空函数?

签名不应该是这样的:

DiffResults results = object.CompareTo(object2);
于 2008-10-16T21:25:26.763 回答
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
    }
}

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

于 2008-10-16T21:50:45.060 回答
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 类似(TextBox t in col && TextBox d in des)

于 2009-11-10T11:43:03.217 回答