I've been profiling my code and found that System.Array.IndexOf
is allocating a fair bit of memory. I've been trying to find out how come this happens.
public struct LRItem
{
public ProductionRule Rule { get; } // ProductionRule is a class
public int Position { get; }
}
// ...
public List<LRItem> Items { get; } = new List<LRItem>();
// ...
public bool Add(LRItem item)
{
if (Items.Contains(item)) return false;
Items.Add(item);
return true;
}
I'm assuming the IndexOf
is called by Items.Contains
because I don't think Items.Add
has any business checking indices. I've tried looking at the reference source and .NET Core source but to no avail. Is this a bug in the VS profiler? Is this function actually allocating memory? Could I optimize my code somehow?
I know this is probably a bit late, but in case anyone else has the same question...
When
List<T>.Contains(...)
is called, it uses theEqualityComparer<T>.Default
to compare the individual items to find what you've passed in[1]. The docs say this aboutEqualityComparer<T>.Default
:Since your
LRItem
does not implementIEquatable<T>
, then it falls back to usingObject.Equals(object, object)
. And becauseLRItem
is a struct, then it will end up being boxed as anobject
so it can be passed in toObject.Equals(...)
, which is where the allocations are coming from.The easy fix for this is to take a hint from the docs and implement the
IEquatable<T>
interface:This will now cause
EqualityComparer<T>.Default
to return a specialised comparer that does not need to box yourLRItem
structs and hence avoiding the allocation.[1] I'm not sure if something's changed since this question was asked (or maybe it's a .net framework vs core difference or something) but
List<T>.Contains()
doesn't callArray.IndexOf()
nowadays. Either way, both of them do defer toEqualityComparer<T>.Default
, which means that this should still be relevant in either case.