6

假设我有以下内容:

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在什么情况下使用over会有优势MyClassA

4

2 回答 2

12

为了澄清 usr 的正确但有些稀疏的答案:

C# 支持一个特性——我的候选“C# 中最差特性”——称为数组类型协方差。也就是说,如果你有一个海龟数组,你可以将它分配给一个“动物数组”类型的变量:

class Animal {}
class Turtle : Animal {}
...
Animal[] animals = new Turtle[10];

这是“协方差”,因为数组的赋值兼容性规则是与元素的赋值兼容性规则方向相同的箭头:

Turtle --> Animal
Turtle[] --> Animal[]

此功能不是类型安全的,因为...

animals[0] = new Giraffe();

我们只是将一只长颈鹿放入一个实际上是海龟数组的数组中。编译器无法确定此处违反了类型安全——长颈鹿是一种动物——因此必须由运行时执行检查。

为了防止在运行时发生这种情况,每次将 Giraffe 放入 Animals 数组时,运行时都会插入一个检查,以检查它是否真的是 Turtles 数组。它几乎从来没有。但是此检查需要时间,因此该功能有效地减慢了每次成功的阵列访问

不安全的数组协方差仅适用于元素类型为引用类型的数组。它不适用于值类型。(这是一个小小的谎言;CLR 将允许您先转换int[]object,然后再object转换为uint[]。但一般来说,协方差不适用于值类型。)

因此,您可以通过使您的数组实际上是一个值类型的数组来节省检查费用,其中值类型只是引用的包装器。数组的大小不会受到影响,但访问它会稍微快一些。

除非您有经验证据表明这样做实际上可以解决实际的性能问题,否则您不应该使用这些疯狂的技巧。需要进行这种优化的情况非常少,但是在某些地方这种事情可以有所作为。

我注意到您还可以通过密封Turtle 类型然后使用 Turtles 数组来避免检查成本。运行时会推断数组类型不能真正派生更多,因为它的元素类型将派生自密封类型,这是不可能的。

于 2013-09-14T15:28:42.643 回答
3

ArrayElement是一个允许 JIT 生成更好代码的包装器。.NET 数组对引用存储进行运行时类型检查,因为它们在所有方面都不是静态类型安全的。

var array = new Stream[10];
((object[])array)[0] = "somestring"; //runtime exception

使用包装器不再需要类型检查。

于 2013-09-14T15:14:54.703 回答