从2D矩阵中切割Span <t>行 - 不确定为什么会这样做

时间:2018-01-03 00:24:58

标签: c# .net cil c#-7.2

我一直在寻找一种从2D矩阵中提取切片的方法,而无需实际重新分配复制内容,

public static Span<float> Slice([NotNull] this float[,] m, int row)
{
    if (row < 0 || row > m.GetLength(0) - 1) throw new ArgumentOutOfRangeException(nameof(row), "The row index isn't valid");
    return Span<float>.DangerousCreate(m, ref m[row, 0], m.GetLength(1));
}

我已经使用这个简单的单元测试检查了这个方法,显然它可以工作:

[TestMethod]
public void Foo()
{
    float[,] m =
    {
        { 1, 2, 3, 4 },
        { 5, 6, 7, 8 },
        { 9, 9.5f, 10, 11 },
        { 12, 13, 14.3f, 15 }
    };
    Span<float> s = m.Slice(2);
    var copy = s.ToArray();
    var check = new[] { 9, 9.5f, 10, 11 };
    Assert.IsTrue(copy.Select((n, i) => Math.Abs(n - check[i]) < 1e-6f).All(b => b));
}

但这对我来说似乎并不合适。我的意思是,我想了解幕后发生的事情,因为ref m[x, y]部分并不能说服我。

运行时如何获得矩阵内该位置的值的实际引用,因为2D数组中的this[int x, int y]方法只是返回值而不是引用?

ref修饰符不应只获取对返回给方法的float值的本地副本的引用,而不是对存储的实际值的引用矩阵内?我的意思是,否则使用ref返回的方法/参数将毫无意义,而事实并非如此。

我看了IL的测试方法并注意到了这一点:

enter image description here

现在,我不是百分百肯定,因为我不太擅长阅读IL,但ref m[x, y]调用不是被转换为对其他Address方法的调用,我想只是自己返回一个ref值?

  

如果是这种情况,有没有办法从C#直接使用该方法   码?

     

有没有办法在可用时发现像这样的方法?

我的意思是,我只是注意到通过查看IL并且我不知道它存在或为什么代码工作之前,在这一点上我想知道有多少伟大的东西在默认的库中没有提示它在那里对于平均开发者。

谢谢!

2 个答案:

答案 0 :(得分:6)

标准1D(SZ)阵列有三个操作码可用于它们 - ldelemstelemldelema。它们表示可以对变量执行的操作 - 获取其值,设置其值以及获取对它的引用。 a[i]语法只是转换为表示您对元素执行的操作。其他变量具有相似的操作码(ldlocstlocldloca; ldfldstfldldflda等。)

但是,这些操作码不能与多维数组一起使用。引用ECMA-335:

  

对于非零基础的一维数组和多维数组,数组类提供Get方法。

     

对于非零基础的一维数组和多维数组,数组类提供StoreElement [sic] 方法

     

对于非零基础的一维数组和多维数组,数组类提供Address方法。

StoreElement 方法已被重命名为 Set ,但这仍然有效。访问多维数组的元素将转换为您对它们执行的任何操作。

这三种方法都有这些签名:

instance int32 int32[0...,0...]::Get(int32, int32)
instance void int32[0...,0...]::Set(int32, int32, int32)
instance int32& int32[0...,0...]::Address(int32, int32)

这些内在方法由CLR实现。注意最后一个方法返回的引用。尽管最近已将返回引用的功能添加到C#,但CLI从一开始就支持它。

另请注意,决不涉及索引器。事实上,数组甚至没有索引器,因为这是一个C#的东西,并不足以实现变量的所有操作,因为缺少 get reference 访问器。

总而言之,数组上的a[x]和非数组上的a[x](带有索引器的任何对象)大量不同的东西。

顺便说一句, DangerousCreate 也可以通过这个声明(ECMA-335再次):

  

数组元素应按行主要顺序排列在数组对象中(即元素   与最右边的阵列尺寸相关联的应从最低到最小连续布局   最高指数)。为每个数组元素分配的实际存储可以包括特定于平台的存储   填充。

答案 1 :(得分:3)

在我看来,你困惑的关键在于:

  

ref修饰符不应只获取对返回给方法的浮点值的本地副本的引用,而不是对矩阵中存储的实际值的引用吗?

您似乎错误地认为数组的索引器语法与其他类型的索引器语法完全相同。但事实并非如此。数组的索引器是.NET中的一种特殊情况,被视为变量,而不是属性或方法对。

例如:

void M1()
{
    int[] a = { 1, 2, 3 };

    M2(ref a[1]);
    Console.WriteLine(string.Join(", ", a);
}

void M2(ref int i)
{
    i = 17;
}

的产率:

1, 17, 3

这是有效的,因为表达式a[1] 是对某个索引器getter的调用,而是描述了一个物理上位于第二个元素中的变量给定的数组。

同样,当您致电DangerousCreate()并传递ref m[row, 0]时,您正在将引用传递给m[row, 0]数组元素的变量。< / p>

由于对实际内存位置的引用是通过的,其余的应该不足为奇。也就是说,Span<T>类能够使用该地址来包装原始数组的特定子集,而无需分配任何额外的内存。