【问题标题】:C# local arrays IReadOnlyCollection/IReadOnlyList optimizing away object creationC# 本地数组 IReadOnlyCollection/IReadOnlyList 优化对象创建
【发布时间】:2016-10-29 23:35:42
【问题描述】:

以下三种方法是否具有等效的垃圾收集行为? #1 有点牵强,但是今天的 C# 编译器是否足够聪明,可以在每次调用方法时优化 #2 中的对象创建?我特别不想在方法之外提升数组初始化。

    public Boolean IsRgb1(string color)
    {
        string[] colors = { "red", "green", "blue" };
        return colors.Contains(color);
    }

    public Boolean IsRgb2(string color)
    {
        IReadOnlyCollection<string> colors = new[] { "red", "green", "blue" };
        return colors.Contains(color);
    }

    public Boolean IsRgb3(string color)
    {
        switch(color)
        {
            case "red":
            case "green":
            case "blue":
                return true;
            default:
                return false;
        }
    }

【问题讨论】:

    标签: c# readonly


    【解决方案1】:

    没有与这些类型相关的编译器魔法。每次调用时,总是在 Rgb1 和 Rgb2 中创建一个数组。

    数组声明简写语法

    string[] colors = { "red", "green", "blue" };
    

    与(显示“推导语法”)相同

    string[] colors = new string[3] { "red", "green", "blue" };
    

    基本规则是:new总是创建一个新的对象/实例。要只创建一次(数组)对象,只需创建一次。然后可以使用成员/字段共享单个数组实例。这种“提升”必须手动完成。

    // One array created and stored for later use ..
    private static string[] Colors = { "red", "green", "blue" };
    // .. independent of number of times this method is called
    public Boolean IsRgb(string color)
    {
        return Colors.Contains(color);
    }
    

    在这两种情况下,Contains 都来自 IEnumerable&lt;T&gt;,因为 T[]IReadOnlyList&lt;T&gt; 都是 IEnumerable1 的子类型,并且符合 LINQ Contains 扩展方法的条件。将使用相同的 IEnumerable Contains 实现(LINQ To Objects),并且应用于数组的任何特化都应适用于这两种情况。

    Rgb3 案例完全避免了数组的创建,它避免了一些方法调用,它避免了执行广义集合的开销包含“循环”逻辑。这将是最快的——如果/在什么地方很重要——仅仅是因为它要做的事情最少。

    一个简单的字符串switch语句可以被认为是编写一系列if..else if..比较相同值的替代方法。在这种情况下,每个方法调用没有创建新对象:字符串文字已被保留,显然没有新数组。

    或者,考虑只使用一个表达式:

     return color == "red" || color == "green" || color == "blue";
    

    1由于类型继承比较混乱,这里摘录一小段:

    T[] -> IEnumerable<T> (Contains as Extension Method)
        -> IList<T> -> ICollection<T> (Contains in Interface) -> IEnumerable<T>
        -> IReadOnlyList<T> -> IEnumerable<T>
                            -> IReadOnlyCollection<T> -> IEnumerable<T>
    

    由于T[]IReadOnlyList&lt;T&gt; 的子类型,因此 Rgb2 中的赋值导致了隐式向上转换 - 变量仍然命名新创建的数组对象。 IEnumerable&lt;T&gt;.Contains 的选择发生在编译时,因此 Rgb1 和 Rgb2 方法都将在原始创建的数组对象上使用扩展方法 IEnumerable&lt;T&gt;.Contains。要使用 ICollection&lt;T&gt;.Contains 需要 ((IList&lt;string&gt;)colors).Contains(..) 或类似名称。

    【讨论】:

    • 在这种情况下,我可能会选择new Hashset&lt;string&gt;(StringComparer.OrdinalIgnoreCase);通过不区分大小写的比较进行快速查找。
    • @MetroSmurf 由于常量开销要小得多,字符串比较会更快(甚至包含可能会更快)。
    • 同意少量数据。对于较大的集合,Hashset。无论如何,只是另一种选择。
    • @MetroSmurf 绝对;对于“正常大小”的颜色名称调色板,这将是一个值得考虑的问题!添加到集合中通常也更容易,该集合旨在允许任意大小。
    • @MetroSmurf 对于较大的集合,不,您可能仍然不需要为哈希集合而烦恼。如果 switch 语句有足够的 case 标签来提高性能,编译器就会在后台生成并调用哈希函数。自己试试吧。
    猜你喜欢
    • 2021-10-14
    • 2021-10-21
    • 1970-01-01
    • 2023-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多