c# - 从 2D 矩阵中切片 Span<T> 行 - 不确定为什么会这样

标签 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] 部分并不能说服我。

运行时如何获取对矩阵内该位置值的实际引用,因为二维数组中的 this[int x, int y] 方法只是返回一个值而不是引用资料?

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

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

enter image description here

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

If that's the case, is there a way to directly use that method from C# code?

And is there a way to discover methods like this one, when available?

我的意思是,我只是通过查看 IL 注意到,我不知道它存在,也不知道为什么之前的代码能正常工作,此时我想知道默认库中有多少很棒的东西,却没有任何提示对于普通开发人员。

谢谢!

最佳答案

标准一维 (SZ) 阵列具有三个操作码来处理它们 - ldelemstelemldelema。它们表示可以对变量 执行的操作 - 获取其值、设置其值以及获取对它的引用。 a[i] 语法只是被翻译成任何代表你对元素所做的事情。其他变量具有相似的操作码(ldlocSTLocldlocaldfldstfld , ldflda 等)

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

For one-dimensional arrays that aren’t zero-based and for multidimensional arrays, the array class provides a Get method.

For one-dimensional arrays that aren’t zero-based and for multidimensional arrays, the array class provides a StoreElement [sic] method

For one-dimensional arrays that aren’t zero-based and for multidimensional arrays, the array class provides an Address method.

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):

Array elements shall be laid out within the array object in row-major order (i.e., the elements associated with the rightmost array dimension shall be laid out contiguously from lowest to highest index). The actual storage allocated for each array element can include platform-specific padding.

关于c# - 从 2D 矩阵中切片 Span<T> 行 - 不确定为什么会这样,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48069646/

相关文章:

c# - 在 C# 中向 EventHandler 添加一些参数

c# - 通过脚本 C# 在特定点移动 unity3d 中的门

c# - 让 EntityFramework 尊重 mysql max_user_connections

.net - F#尾递归调用

c# - 如果异常不为空,则 Nlog 输出字符

c# - 如何使用 shell 的默认处理程序打开文件?

c# - 连接必须有效且打开。 MySql 5.2 错误

c# - 有没有办法在自定义 System.Security.IPermission 实现中修复此 FxCop CA2103 警告?

c# - 表达式主体语法与 IL 级别的 Getter 语法有什么区别?

c# - CompileToMethod 无法编译常量 '<some value>',因为它是一个不平凡的值,例如事件对象