System.Array.IndexOf 分配内存

System.Array.IndexOf allocates memory

我一直在分析我的代码,发现 System.Array.IndexOf 正在分配相当多的内存。我一直在试图找出为什么会这样。

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;
}

我假设 IndexOf 是由 Items.Contains 调用的,因为我认为 Items.Add 没有任何业务检查索引。我试过查看 reference source and .NET Core source 但无济于事。这是 VS 分析器中的错误吗?这个函数实际上是在分配内存吗?我能以某种方式优化我的代码吗?

您的对象不是文本或数字等简单数据类型,所以 检查复杂对象的每个项目可能是内存杀手。

例如,如果您的对象包含图像、文本、数字和...每个数据都应该进行相似性处理。

我建议您不要使用 IndexOf 或 Contains 函数,因为它们 可能比较对象中的每一个数据。

只需手动执行,使用单个 foreach 循环,只需 比较您的密钥 数据(用户 ID、对象 ID、名称、家庭、日期、时间或 ...)。

我知道这可能有点晚了,但万一其他人有同样的问题...

调用 List<T>.Contains(...) 时,它使用 EqualityComparer<T>.Default 比较各个项目以查找您传入的内容 [1]。 docs 关于 EqualityComparer<T>.Default 的说法:

The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.

由于您的 LRItem 实现 IEquatable<T>,因此它会退回到使用 Object.Equals(object, object)。因为 LRItem 是一个结构,所以它最终会 被装箱 作为 object,所以它可以传递给 Object.Equals(...),这是分配的来源。

解决这个问题的简单方法是从文档中获取提示并实现 IEquatable<T> 接口:

public struct LRItem : IEquatable<LRItem>
{
    // ...

    public bool Equals(LRItem other)
    {
        // Implement this
        return true;
    }
}

现在这将导致 EqualityComparer<T>.Default 到 return 一个不需要装箱您的 LRItem 结构的专门比较器,从而避免分配。

[1] 我不确定自问这个问题以来是否发生了某些变化(或者可能是 .net 框架与核心差异之类的)但是 List<T>.Contains() 没有调用 Array.IndexOf()如今。无论哪种方式,它们都 do 遵从 EqualityComparer<T>.Default,这意味着这在任何一种情况下都应该仍然相关。