检查集合中是否已存在对象

5

我正在学习编程,我的问题是我有一堆对象,我想将这些对象添加到一个列表中,但仅当该列表不包含该对象时。其次,如果该对象已经被包含在内,我希望忽略该对象并添加下一个对象。我认为我已经解决了第一部分,只需要在第二部分上寻求帮助。非常感谢。

PartyGroup partyGroup = new PartyGroup(); 

using (AseDataReader reader = command.ExecuteReader()) 
{ 
    while (reader.Read()) 
    {  
        if (!myPartyGroupList.Contains(partyGroup)) 
        { 
            partyGroup.PartyGroupID = Convert.ToInt32(reader["party_group_id"]); 
            partyGroup.PartyGroupName = reader["party_group_name"].ToString(); 
            partyGroup.PersonList = myPersonList; 

            myPartyGroupList.Add(partyGroup); 
        } 
        else 
        { 
            //?? 
        } 
    } 
} 
6个回答

5

您已经完美掌握了第一部分。

只需删除“else”子句,您的程序将在下一个迭代中自动添加下一个元素。像这样:

while (reader.Read()) 
{ 
    if (!myPartyGroupList.Contains(partyGroup)) 
    { 
        partyGroup.PartyGroupID = Convert.ToInt32(reader["party_group_id"]); 
        partyGroup.PartyGroupName = reader["party_group_name"].ToString(); 
        partyGroup.PersonList = myPersonList; 

        myPartyGroupList.Add(partyGroup); 

    } 
} 

+1 你比我先做到了。@Ben:如果你什么都不想发生,那么你就什么都不做。不将项目添加到列表中就什么都没做。通过不做任何操作,你自动转到while循环中的下一个项目。 - Paul Sasik
1
难道不应该在检查列表中是否存在partyGroup之前,为其分配新值吗?这段代码不是会添加第一个,然后因为它认为已经存在而不再添加另一个,因为值没有更新吗? - Tester101
啊,谢谢你们,那很有道理。然而,我只得到了第一个元素,所以我怀疑我的 SQL 有问题。谢谢。 - Ben
是的,Tester101,我相信这就是正在发生的事情。 - Ben
1
第一行partyGroup应该写成:partyGroup = new partyGroup(); - Flipster

4

你的代码存在一些问题。首先,你在每次迭代中都重复使用了同一个对象。

请考虑

List<Foo> foos = new List<Foo>();
Foo foo = new Foo();
foo.Bar = "Alpha";
foos.Add(foo);
foo.Bar = "Beta";
foos.Add(foo);

您会注意到您的列表将有2个项目,但它们将引用相同的对象。如果您遍历列表并检查Bar,每个都将返回"Beta"

您想为每个项目创建一个新的Foo

List<Foo> foos = new List<Foo>();
Foo foo = new Foo();
foo.Bar = "Alpha";
foos.Add(foo);
Foo anotherFoo = new Foo();
anotherFoo.Bar = "Beta";
foos.Add(anotherFoo);

在循环术语中,这基本上意味着在循环内部创建对象,而不是在外部创建。
while (someCondition)
{
    Foo foo = new Foo();
    // do your work, populate the object, etc.
    // then check contains 
    if (!myList.Contains(foo))
        myList.Add(foo);
}

关于检查集合是否已包含对象,您是否正确地重写了EqualsGetHashCode方法?处理类时,默认行为是仅检查对象引用是否相等。如果您关心对象所封装的,那么您需要自己提供逻辑。在您的类中,您需要重写EqualsGetHashCode方法来实现您所需的确定相等性的方法。
class Foo
{
    public string Bar { get; set; }

    public override int GetHashCode()
    {
        return this.Bar.GetHashCode();
    }

    public override bool Equals(object other)
    {
        Foo otherFoo = other as Foo;
        if (otherFoo == null)
            return false;
        else
            return this.Bar == otherFoo.Bar;
    }
}

现在,当Contains试图确定对象是否已经在列表中时,它将基于对象中包含的值而不是内存引用来进行。


3

在比较时最好使用标识符进行比较,而在您的情况下是PartyGroupId。如果使用contains,则使用列表中对象的哈希值进行比较。

因此,您可以创建自定义的IEqualityComparer实现或使用Linq的Where子句来处理比较,而不是将比较留给.NET。


using (AseDataReader reader = command.ExecuteReader()) 
{ 
    while (reader.Read()) 
    {  
        int groupId = Convert.ToInt32(reader["party_group_id"]);

        if (partyGroupsList.Where(partyGroup => partyGroup.PartyGroupID == groupId).Any() == false)
        {
           PartyGroup newPartyGroup = new PartyGroup()
                                      {
                                          PartyGroupID = groupId,
                                          PartyGroupName = reader["party_group_name"].ToString(),
                                          PersonList = myPersonList
                                      };

           partyGroupsList.Add(newPartyGroup);                 
        } 

        // If object already exists in the list then do not add, continue 
        // to the next row.   
    } 
} 

另一个建议是将PartyGroup类成员重命名为:

class PartyGroup
 {
   public int ID { get; set; }
   public string Name { get; set; }
   public IList PersonList { get; set; }
}

谢谢Devendra,这个选项对我有用。我想知道你是否能解释一下以下这行代码,以帮助我理解:if (myPartyGroupList.Where(partyGroup => partyGroup.PartyGroupID == groupId).Any() == false) - Ben
如果(myPartyGroupList.Where(partyGroup => partyGroup.PartyGroupID == groupId).Any()== false)可以解释为:Part1:从partyGroupList中选择partyGroup WHERE partyGroup.PartyGroupID = groupID; Part2:在之前的查询中是否有任何结果?-最终结果是一个布尔值(true/false)。如果找到结果行,则结果为true,否则结果为false。有关更多信息,请参阅LINQ扩展的MSDN资源。 - Devendra D. Chavan
我明白了,那很有道理。所以目前我对于每个PartyGroup都获得相同的PersonList。是否有一种方法能够只获取partyGroupId与我的PartyGroup对象中当前groupId相匹配的PersonList。听起来有道理吗? - Ben
只有在找到groupID的匹配项时,才会创建PartyGroup对象(newPartyGroup)。将从myPersonList分配一个值给这个新创建的对象的PersonList属性。 - Devendra D. Chavan
谢谢,这正是我所想的。这就是为什么我有点困惑,每个PartyGroup都会得到相同的人员组(PersonList)。 - Ben
抱歉,我想澄清一下 - 问题在于myPersonList包含了整个结果集 - 包括每个人的所有groupIds。因此,每次创建一个新的PartyGroup时,它都会添加myPersonList中的所有值。所以我的问题是,有没有办法告诉它只添加那些groupId与我们正在处理的当前groupID相匹配的myPersonList中的值。(也许我在Person类中漏掉了一个groupId属性)。谢谢。 - Ben

1
你可以考虑使用一个 Hashset<PartyGroup> 来填充数据,然后再将其转换为列表。如果你有大量的项目,这样做会比为每个项目检查列表要快得多。
Hashset<PartyGroup> pgHash = new Hashset<PartyGroup>();

using (AseDataReader reader = command.ExecuteReader()) 
{ 
    while (reader.Read()) 
    {
        PartyGroup pg = new PartyGroup();  
        partyGroup.PartyGroupID = Convert.ToInt32(reader["party_group_id"]); 
        partyGroup.PartyGroupName = reader["party_group_name"].ToString(); 
        partyGroup.PersonList = myPersonList; 
        // Add won't add an item if it already exists in the collection.
        pgHash.Add(partyGroup); 
    } 
}
// Now convert the result to a list.
myPartyGroupList = pgHash.ToList();

如果你的PartyGroup类没有实现IEquatable<PartyGroup>,那么你就需要提供一个相等比较器。以下代码应该可以解决问题:
public class PartyGroupComparer:IEqualityComparer<PartyGroup>
{
    public bool Equals(PartyGroup g1, PartyGroup g2)
    {
        return g1.PartyGroupId.Equals(g2.PartyGroupId);
    }

    public int GetHashCode(PartyGroup g)
    {
        return g.PartyGroupId;
    }
}

然后你的初始化变成了:

IEqualityComparer<PartyGroup> pgComparer = new PartyGroupComparer();
HashSet<PartyGroup> pgHash = new HashSet<PartyGroup>(pgComparer);

另一个选择 HashSet 的方案,正如其他人所指出的那样,是使用 Dictionary。这将避免你需要做相等比较器。当你完成之后,你仍需要转换成列表。但是这非常容易:

Dictionary<int, PartyGroup> dict = new Dictionary<int, PartyGroup>();
// populate dictionary as suggested in other answer

// now convert values to a list.
myPartyGroupList = dict.Values.ToList();

0

试试这个

while (reader.Read()) 
{ 
    partyGroup.PartyGroupID = Convert.ToInt32(reader["party_group_id"]); 
    partyGroup.PartyGroupName = reader["party_group_name"].ToString(); 
    partyGroup.PersonList = myPersonList; 

    if (!myPartyGroupList.Contains(partyGroup)) 
    { 
        myPartyGroupList.Add(partyGroup); 
    } 
} 

谢谢,不过这次它显示了下一个元素,但没有显示第一个 :| - Ben

0
首先,您正在检查对象实例是否在集合中,但是您只创建了一个实例一次(在while循环之外)。因此,当您检查!myPartyGroupList.Contains(partyGroup)时,它将在第一次返回false,因此您将向集合添加obj,然后每次都会返回false。
我建议使用一个使用Id属性作为字典键的字典。
像这样:
Dictionary <int,PartyGroup> myPartyGroupList = new Dictionary <int,PartyGroup>();

using (AseDataReader reader = command.ExecuteReader()) 
{ 
    while (reader.Read()) 
    {  
        int id=Convert.ToInt32(reader["party_group_id"]); 
        if (!myPartyGroupList.ContainsKey( id )) 
        { 
            PartyGroup partyGroup = new PartyGroup(); 

            partyGroup.PartyGroupID = id; 
            partyGroup.PartyGroupName = reader["party_group_name"].ToString(); 
            partyGroup.PersonList = myPersonList; 

            myPartyGroupList.Add(id, partyGroup);  // key, value? check param order here
        } 
    } 
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接