How does System.Collection.Generic.List.IndexOf(item) work?

If I have this situation:

public List<string> plants = new List<string>();
plants.Add("Lily");
plants.Add("Orange tree");
plants.Add("Raspberry");
plants.Add("Bell pepper");
plants.Add("Orange tree");

And I want to find the index of “Orange tree” using:
print(plants.IndexOf("Orange tree")) it finds it as 1, because it stops at the first occurance, but if I do this: print(plants.IndexOf("Orange tree", 2)) it prints 4 because I started at index 2. What I’m trying to say is that both index 1 and index 4 are suitable as a search for “Orange tree”;

How ever if I have this situation:

public enum PlantType{
   Tree, Flower, Bush, Vegetable
}
public class Plant{
   public string name;
   public PlantType type;
   public string latinName;
   public int rarity;
  //constructor
   public Plant(string _name, PlantType _type, string _latinName, int _rarity){
      name=_name;type=_type;latinName=_latinName;rarity=_rarity;
   }
}

List<Plant> plants = new List<Plant>();
plants.Add(new Plant("Lily", PlantType.Flower,  "Lilium",  2));
plants.Add(new Plant("Orange tree", PlantType.Tree,  "Citrus sinensis",  4));
plants.Add(new Plant("Raspberry", PlantType.Bush,  "Rubus",  5));
plants.Add(new Plant("Bell pepper", PlantType.Vegetable,  "Capsicum annuum",  5));
plants.Add(new Plant("Orange tree", PlantType.Tree,  "Citrus sinensis",  4));

//When I search this, it gives me -1, so it seems it doesn't search for values, but for the memory address of the instance of the Plant class
plants.IndexOf(new Plant("Orange tree", PlantType.Tree,  "Citrus sinensis",  4));

I can do something like this:

public class Plant{
   //...fields, constructor, enum, etc.
   public static int FindIndexOfPlant(List<Plant> plantList, string name, PlantType type){
      int i = 0;
      foreach(Plant plant in plantList){
         if(plant.name.Equals(name) && plant.type == type) return i;
         i++;
      }
      return -1;
   }
}

print(Plant.FindIndexOfPlant(plants, "Orange tree", PlantType.Tree));

This seems messy

Nothing wrong with that at all.

But, there are several ways of doing just about anything in programming. A quick Google search.

Well, of course two classes are never equal to each other, even when their content is the same. However your class can override the Equals and GetHashCode method to provide it’s own comparison logic.

public class Plant{
    public string name;
    public PlantType type;
    public string latinName;
    public int rarity;
    public Plant(string _name, PlantType _type, string _latinName, int _rarity)
    {
        name=_name;type=_type;latinName=_latinName;rarity=_rarity;
    }
    public override bool Equals(object other)
    {
        if (other == null)
            return false;
        if (name != other.name)
            return false;
        if (type != other.type)
            return false;
        // add other conditions that have to match in order to test for equality

        return true;
    }
    public override int GetHashCode()
    {
        // also add things that should also be considered "different"
        return name.GetHashCode() ^ type.GetHashCode();
    }
}

Now List.IndexOf would work with a line like this:

int index = plants.IndexOf(new Plant("Orange tree", PlantType.Tree,  "",  0));

Though it’s usually not a good idea to just create a new object instance just to compare it. Instead you can use FindIndex of the List class. Here you can simply pass a predicate that is used on each item. something like this would work as well without the whole Equals and GetHashCode:

int index = plants.FindIndex(p=>p.name == "Orange tree");

FindIndex also supports a start index and a count parameter. Though those come before the predicate.

Like Brathnann said, your solution does work as well and would be even better in regards to memory allocations. While a predicate like I used would be no problem, when you use a variable to compare the name to, you would create a closure which would allocate a bit of memory. So just a method that does specifically what you need is usually the best solution. Though, I would not use a foreach loop. Use a for loop since you want to have the index available. Also you may want to implement the method as an extension method. Something like this:

public static class PlantList_Ext
{
    public static int FindIndexOfPlant(this List<Plant> plantList, string name, PlantType type)
    {
        for(int i = 0; i < plantList.Count; i++)
        {
            if(plantList[i].name == name && plantList[i].type == type)
                return i;
        }
        return -1;
    }
}

Extension methods must be defined in a static class. It doesn’t matter where it’s defined, could be in another file. The extension method would automatically show up in intellisense and allows you to do

int index = plantList.FindIndexOfPlant("Orange tree",PlantType.Tree);
3 Likes