假设我有以下内容:
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
会有什么好处?
答案 0 :(得分:12)
澄清usr的正确但有点稀疏的答案:
C#支持一项功能 - 我的“C#中最糟糕的功能”的候选者 - 称为数组类型协方差。也就是说,如果你有一个海龟数组,你可以将它分配给“动物数组”类型的变量:
class Animal {}
class Turtle : Animal {}
...
Animal[] animals = new Turtle[10];
这是“协方差”,因为数组的赋值兼容性规则是与其元素的赋值兼容性规则在同一方向上的箭头:
Turtle --> Animal
Turtle[] --> Animal[]
这个功能不是类型安全,因为......好吧......
animals[0] = new Giraffe();
我们只是将一只长颈鹿放入一个实际上是一系列海龟的阵列中。编译器无法确定此处是否违反了类型安全 - 长颈鹿是一种动物 - 因此检查必须由运行时执行。
为了防止在运行时发生这种情况,每次将Giraffe放入动物数组时,运行时都会插入一个检查,以检查它是否真的 一组海龟。这几乎从来都不是。但是这种检查需要时间,因此该功能有效地减慢了每次成功的阵列访问。
不安全数组协方差仅适用于元素类型为引用类型的数组。它不适用于值类型。 (这是一个小谎言; CLR将允许您将int[]
转换为object
,然后object
转换为uint[]
。但一般来说,协方差不适用于值类型。)
因此,您可以通过使数组实际上是值类型数组来节省检查费用,其中值类型只是引用的包装器。数组的大小不受影响,但对它的访问速度会稍快一些。
除非你有经验证据这样做,否则你不应该采取这些疯狂的伎俩解决实际的性能问题。保证这种优化的情况非常少,但有一些地方可以发挥作用。
我注意到你也可以通过密封 Turtle类型然后使用海龟数组来避免检查的成本。运行时将推断数组类型不能真正更多派生,因为它的元素类型将从密封类型派生,这是不可能的。
答案 1 :(得分:3)
ArrayElement
是一个包装器,允许JIT生成更好的代码。 .NET数组对引用存储具有运行时类型检查,因为它们在所有方面都不是静态类型安全的。
var array = new Stream[10];
((object[])array)[0] = "somestring"; //runtime exception
使用包装器不再需要进行类型检查。