tags:

views:

123

answers:

5

I have two lists A and B, at the beginning of my program, they are both filled with information from a database (List A = List B). My program runs, List A is used and modified, List B is left alone. After a while I reload List B with new information from the database, and then do a check with that against List A.

foreach (CPlayer player in ListA)
      if (ListB.Contains(player))
            -----

Firstly, the object player is created from a class, its main identifier is player.Name. If the Name is the same, but the other variables are different, would the .Contains still return true?

Class CPlayer(
      public CPlayer (string name)
              _Name = name

At the ---- I need to use the item from ListB that causes the .Contains to return true, how do I do that?

+4  A: 

The default behaviour of List.Contains is that it uses the default equality comparer. If your items are reference types this means that it will use an identity comparison unless your class provides another implementation via Equals.

If you are using .NET 3.5 then you can change your second line to this which will do what you want:

if (ListB.Any(x => x.Name == player.Name))

For .NET 2.0 you could implement Equals and GetHashCode for your class, but this might give undesirable behaviour in other situations where you don't want two player objects to compare equal if they have the same name but differ in other fields.

An alternative way is to adapt Jon Skeet's answer for .NET 2.0. Create a Dictionary<string, object> and fill it with the names of all players in listB. Then to test if a player with a certain name is in listB you can use dict.ContainsKey(name).

Mark Byers
.NET 2.0 unfortunately
Matt
@Matt: Now added possible solution for .NET 2.0.
Mark Byers
+2  A: 

An alternative to Mark's suggestion is to build a set of names and use that:

HashSet<string> namesB = new HashSet<string>(ListB.Select(x => x.Name));
foreach (CPlayer player in ListA)
{
    if (namesB.Contains(player.Name))
    {
        ...
    }
}
Jon Skeet
A: 

Assuming you are using the System.Collections.Generic.List class, if the CPlayer class does not implement IEquatable<T> it will use the Equals and GetHashCode functions of the CPlayer class to check if the List has a member that equals the argument of Contains. Assuming that implementation is OK for you, you could something like

CPlayer listBItem = ListB.First(p => p == player);

to get the instance from ListB

Peter
A: 

It sounds like this is what you need to accomplish:

For each player in list A, find each player in list B with the same name and bring both players into the same scope.

Here is an approach which joins the two lists in a query:

var playerPairs =
    from playerA in ListA
    join playerB in ListB on playerA.Name equals playerB.Name
    select new { playerA, playerB };

foreach(var playerPair in playerPairs)
{
    Console.Write(playerPair.playerA.Name);
    Console.Write(" -> ");
    Console.WriteLine(playerPair.playerB.Name);
}
Bryan Watts
A: 

If you want the .Contains method to match only on CPlayer.Name, then in the CPlayer class implement these methods:

public override bool Equals(object obj)
{
    if (!(obj is CPlayer)
        return false;
    return Name == (obj as CPlayer).Name;
}
public override int GetHashCode()
{
    return Name.GetHashCode();
}

If you want the Name comparison to be Case Insensitive, replace use this Equals method instead:

public override bool Equals(object obj)
{
    if (!(obj is CPlayer)
        return false;
    return Name.Equals((obj as CPlayer).Name, StringComparison.OrdinalIgnoreCase);
}

If you do this, your .Contains call will work just as you want it. Secondly, if you want to select this item in the list, do this:

var playerB = ListB[ListB.IndexOf(player)];

It uses the same .Equals and .GetHashCode methods.

UPD: This is probably a subjective statement, but you could also squeeze some performance out of it, if your .Equals method compared the Int hashes before doing the string comparison..

Looking at the .NET sources (Reflector FTW) I can see that seemingly only the HastTable class uses GetHashCode to improve it's performance, instead of using .Equals to compare objects every single time. In the case of a small class like this, the equality comparer is simple, a single string comparison.. If you were comparing all properties though, then comparing two integers would be much faster (esp if they were cached :) )

The List.Contains and List.IndexOf don't use the hash code, and use the .Equals method, hence I proposed checking the hash code inside. It probably won't be anything noticeable, but when you're itching to get every single ms of execution (not always a good thing, bug hey! :P ) this might help someone. just saying... :)

Artiom Chilaru
Can you provide some evidence or justification for "You could also squeeze some performance out of it, if your .Equals method compared the Int hashes before doing the string comparison.."?
Rob Fonseca-Ensor
added an explanation in the post. It's partially subjective.. but only partially )
Artiom Chilaru