C# 本地数组 IReadOnlyCollection/IReadOnlyList 优化对象创建
C# local arrays IReadOnlyCollection/IReadOnlyList optimizing away object creation
以下三种方法是否具有等效的垃圾回收行为? #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;
}
}
没有与这些类型相关的编译器魔术。每次调用时,总是 在 Rgb1 和 Rgb2 中创建一个数组。
数组声明shorthand语法
string[] colors = { "red", "green", "blue" };
与(显示'deduced syntax')相同
string[] colors = new string[3] { "red", "green", "blue" };
基本规则是:new
总是 创建一个新的 object/instance。要只创建一次(数组)对象,只创建一次。然后可以使用 member/field 共享单个数组实例。此 'hoisting' 必须 手动 完成。
// 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<T>
,因为 T[]
和 IReadOnlyList<T>
都是 IEnumerable1 的子类型并且符合 LINQ 的条件包含扩展方法。将使用相同的 IEnumerable Contains 实现 (LINQ To Objects),并且应用于数组的任何专业化都应适用于这两种情况。
Rgb3 案例完全避免了数组创建,它避免了一些方法调用,并且避免了执行广义集合包含 'looping' 逻辑的开销。它将是最快的 - if/where 这样的事情 - 仅仅是因为它要做的事情最少。
一个简单的字符串 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<T>
的子类型,因此 Rgb2 中的赋值导致隐式向上转换 - 变量仍然命名新创建的数组对象。 IEnumerable<T>.Contains
的选择发生在编译时,因此 Rgb1 和 Rgb2 方法都将在原始创建的数组对象上使用扩展方法 IEnumerable<T>.Contains
。要使用 ICollection<T>.Contains
需要 ((IList<string>)colors).Contains(..)
或类似的。
以下三种方法是否具有等效的垃圾回收行为? #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;
}
}
没有与这些类型相关的编译器魔术。每次调用时,总是 在 Rgb1 和 Rgb2 中创建一个数组。
数组声明shorthand语法
string[] colors = { "red", "green", "blue" };
与(显示'deduced syntax')相同
string[] colors = new string[3] { "red", "green", "blue" };
基本规则是:new
总是 创建一个新的 object/instance。要只创建一次(数组)对象,只创建一次。然后可以使用 member/field 共享单个数组实例。此 'hoisting' 必须 手动 完成。
// 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<T>
,因为 T[]
和 IReadOnlyList<T>
都是 IEnumerable1 的子类型并且符合 LINQ 的条件包含扩展方法。将使用相同的 IEnumerable Contains 实现 (LINQ To Objects),并且应用于数组的任何专业化都应适用于这两种情况。
Rgb3 案例完全避免了数组创建,它避免了一些方法调用,并且避免了执行广义集合包含 'looping' 逻辑的开销。它将是最快的 - if/where 这样的事情 - 仅仅是因为它要做的事情最少。
一个简单的字符串 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<T>
的子类型,因此 Rgb2 中的赋值导致隐式向上转换 - 变量仍然命名新创建的数组对象。 IEnumerable<T>.Contains
的选择发生在编译时,因此 Rgb1 和 Rgb2 方法都将在原始创建的数组对象上使用扩展方法 IEnumerable<T>.Contains
。要使用 ICollection<T>.Contains
需要 ((IList<string>)colors).Contains(..)
或类似的。