何时对引用类型的数组使用包含引用类型的值类型的数组
本文关键字:引用类型 数组 类型 包含 何时 | 更新日期: 2023-09-27 18:31:14
假设我有以下内容:
public class MyElement
{
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
struct ArrayElement
{
internal MyElement Element;
}
public class MyClass
{
internal MyElement ComputeElement(int index)
{
// This method does a lengthy computation.
// The actual return value is not so simple.
return new MyElement();
}
internal MyElement GetChild(ref MyElement element, int index)
{
if (element != null)
{
return element;
}
var elem = ComputeElement(index);
if (Interlocked.CompareExchange(ref element, elem, null) != null)
{
elem = element;
}
return elem;
}
}
public class MyClassA : MyClass
{
readonly MyElement[] children = new MyElement[10];
public MyElement GetChild(int index)
{
return GetChild(ref children[index], index);
}
}
public class MyClassB : MyClass
{
readonly ArrayElement[] children = new ArrayElement[10];
public MyElement GetChild(int index)
{
return GetChild(ref children[index].Element, index);
}
}
在什么情况下使用MyClassB
比MyClassA
更有优势?
澄清 usr 的正确但有些稀疏的答案:
C# 支持一个功能——我的候选"C# 中最差的功能"——称为数组类型协方差。也就是说,如果你有一个海龟数组,你可以把它分配给一个"动物数组"类型的变量:
class Animal {}
class Turtle : Animal {}
...
Animal[] animals = new Turtle[10];
这是"协方差",因为数组的赋值兼容性规则是与其元素的赋值兼容性规则方向相同的箭头:
Turtle --> Animal
Turtle[] --> Animal[]
此功能不是类型安全的,因为...
animals[0] = new Giraffe();
我们只是把一只长颈鹿放进一个阵列中,这个阵列实际上是一群海龟。编译器无法确定此处是否违反了类型安全(长颈鹿是一种动物),因此必须由运行时执行检查。
为了防止这种情况在运行时发生,每次将长颈鹿放入动物数组时,运行时都会插入一个检查,以检查它是否真的是海龟数组。 它几乎从来都不是。但是此检查需要时间,因此该功能实际上会减慢每次成功的阵列访问速度。
不安全数组协方差仅适用于元素类型为引用类型的数组。 它不适用于值类型。(这是一个小谎言;CLR 将允许您将int[]
投射到object
,然后object
投向uint[]
。但通常,协方差不适用于值类型。
因此,您可以通过使数组实际上成为值类型的数组来节省检查费用,其中值类型只是引用的包装器。数组的大小将不受影响,但对它的访问速度会稍快一些。
你不应该使用这种疯狂的技巧,除非你有经验证据表明这样做实际上解决了实际的性能问题。需要这种优化的情况非常少,但是在少数地方,这种事情可以有所作为。
我注意到,您还可以通过密封海龟类型然后使用海龟阵列来避免检查费用。运行时会推断数组类型实际上不能派生更多,因为这样它的元素类型将派生自密封类型,这是不可能的。
ArrayElement
是一个包装器,允许 JIT 生成更好的代码。 .NET 数组对引用存储进行了运行时类型检查,因为它们并非在所有方面都静态类型安全。
var array = new Stream[10];
((object[])array)[0] = "somestring"; //runtime exception
使用包装器,不再需要类型检查。