与显式类型参数相比,使用 `object` 参数时 C# NumSharp 未定义的行为
C# NumSharp undefined behavior when using `object` parameter compared to explicit type parameter
我正在使用 NumSharp 并且对它有点陌生。我想设置多个数据值,主要关注两个函数:
NDArray.SetValue(object value, param int[] indices)
NDArray.SetData(object value, param int[] indices)
第一个 SetValue
需要单值元素的完整索引,例如,如果你有 np.zeros(3,3)
它需要完整索引,如果缺少索引它将使用 0
取而代之。
例如:
var arr = np.zeros(3,3);
arr.SetValue(10.0, 0);
arr.SetValue(10.0, 0, 0);
两行是等价的。
另一方面,SetData
会根据索引修改整个子数组,如果你给出完整的索引,它的工作方式类似于SetValue
。例如:
arr.SetData(10.0, 0); // sets all arr[0] values to 10
arr.SetData(10.0, 0, 1); // sets arr[0][1] to 10
现在,对于我的用例,我想重新定义一个接受多个值并将每个值视为子数组索引的函数。
arr.SetMultiData(10.0, 0, 1); /// sets arr[0] and arr[10] values to 10
当然,我选择了扩展方法:
public static class Extensions
{
public static void SetMultiData(this NDArray arr, object value, int[] indices)
{
foreach (var i in indices)
arr.SetData(value, i);
}
}
但这就是让我完全迷失的地方。此代码不使用扩展方法,有效:
static void Main(string[] args)
{
NDArray arr = np.zeros(3, 3);
int[] indices = new int[] { 0, 1 };
double value = 10;
foreach (var i in indices)
arr.SetData(value, i);
Console.WriteLine(arr.ToString());
}
输出:
[[10, 10, 10],[10, 10, 10],[0, 0, 0]]
此代码不会:
static void Main(string[] args)
{
NDArray arr = np.zeros(3, 3);
int[] indices = new int[] { 0, 1 };
double value = 10;
arr.SetMultiData(value, indices);
Console.WriteLine(arr.ToString());
}
输出:
[[10, 0, 0],[0, 0, 0],[0, 0, 0]]
这并不总是会打印,在大多数情况下它只是无法打印,我相信底层 arr
结构已相应损坏,这会导致当我 运行 它时出现未定义的行为多次。
现在,最后一点。当我将 Extension
方法更改为接受 double
而不是 object
时,它工作得很好。但是我需要扩展方法来处理不止一种类型,因为它只是 SetData
的包装器。鉴于此,我也尝试将方法更改为通用的:
public static class Extensions
{
public static void SetMultiData<T>(this NDArray arr, object value, int[] indices)
{
T val = (T)value;
foreach (var i in indices)
arr.SetData(val, i);
}
}
当这不起作用并产生相同的输出时,我感到很震惊。基本上,我进行了从对象到类型的显式转换。我这样调用函数:arr.SetMultiData<double>(value, indicies);
我使用了调试器并比较了使用它的代码和类型与传递给 arr.SetValue
给定 i = int
和val = double
,但一个有效,一个无效。
是什么导致了这种行为?为什么当我明确地将它设置为 double
时,当它使用通用/object
类型都失败时,为什么它可以工作?
供参考,这里是开源的 SetData
implementation.。
原因是 double
可以隐式转换为 NDArray
(通过 NDArray
中定义的隐式运算符)。所以当你这样做时:
arr.SetData(value, i);
和 value
是类型 double
- 被调用的重载是 SetData(NDArray, int[)
,因为它是最合适的,因为 double
可以转换为 NDArray
.
然而,当 value
是类型 object
甚至是未解析的泛型类型 (T
) - 然后被调用的重载是 SetData(object, int[])
,所以事情变得疯狂 (因为如果您作为对象传递的值不是 NDArray,那么这些重载会做完全不同的事情,而且它不是 - 它是双倍的)。重载是在编译时选择的,即使在泛型情况下,编译器也无法选择 NDArray
重载(因为选择的重载应该适合您可以传递的任何可能的泛型类型)。
在您的情况下,您可以通过在扩展方法中使用 NDArray value
而不是对象或泛型来“解决”此问题:
public static class Extensions
{
public static void SetMultiData(this NDArray arr, NDArray value, int[] indices)
{
foreach (var i in indices)
arr.SetData(value, i);
}
}
我正在使用 NumSharp 并且对它有点陌生。我想设置多个数据值,主要关注两个函数:
NDArray.SetValue(object value, param int[] indices)
NDArray.SetData(object value, param int[] indices)
第一个 SetValue
需要单值元素的完整索引,例如,如果你有 np.zeros(3,3)
它需要完整索引,如果缺少索引它将使用 0
取而代之。
例如:
var arr = np.zeros(3,3);
arr.SetValue(10.0, 0);
arr.SetValue(10.0, 0, 0);
两行是等价的。
另一方面,SetData
会根据索引修改整个子数组,如果你给出完整的索引,它的工作方式类似于SetValue
。例如:
arr.SetData(10.0, 0); // sets all arr[0] values to 10
arr.SetData(10.0, 0, 1); // sets arr[0][1] to 10
现在,对于我的用例,我想重新定义一个接受多个值并将每个值视为子数组索引的函数。
arr.SetMultiData(10.0, 0, 1); /// sets arr[0] and arr[10] values to 10
当然,我选择了扩展方法:
public static class Extensions
{
public static void SetMultiData(this NDArray arr, object value, int[] indices)
{
foreach (var i in indices)
arr.SetData(value, i);
}
}
但这就是让我完全迷失的地方。此代码不使用扩展方法,有效:
static void Main(string[] args)
{
NDArray arr = np.zeros(3, 3);
int[] indices = new int[] { 0, 1 };
double value = 10;
foreach (var i in indices)
arr.SetData(value, i);
Console.WriteLine(arr.ToString());
}
输出:
[[10, 10, 10],[10, 10, 10],[0, 0, 0]]
此代码不会:
static void Main(string[] args)
{
NDArray arr = np.zeros(3, 3);
int[] indices = new int[] { 0, 1 };
double value = 10;
arr.SetMultiData(value, indices);
Console.WriteLine(arr.ToString());
}
输出:
[[10, 0, 0],[0, 0, 0],[0, 0, 0]]
这并不总是会打印,在大多数情况下它只是无法打印,我相信底层 arr
结构已相应损坏,这会导致当我 运行 它时出现未定义的行为多次。
现在,最后一点。当我将 Extension
方法更改为接受 double
而不是 object
时,它工作得很好。但是我需要扩展方法来处理不止一种类型,因为它只是 SetData
的包装器。鉴于此,我也尝试将方法更改为通用的:
public static class Extensions
{
public static void SetMultiData<T>(this NDArray arr, object value, int[] indices)
{
T val = (T)value;
foreach (var i in indices)
arr.SetData(val, i);
}
}
当这不起作用并产生相同的输出时,我感到很震惊。基本上,我进行了从对象到类型的显式转换。我这样调用函数:arr.SetMultiData<double>(value, indicies);
我使用了调试器并比较了使用它的代码和类型与传递给 arr.SetValue
给定 i = int
和val = double
,但一个有效,一个无效。
是什么导致了这种行为?为什么当我明确地将它设置为 double
时,当它使用通用/object
类型都失败时,为什么它可以工作?
供参考,这里是开源的 SetData
implementation.。
原因是 double
可以隐式转换为 NDArray
(通过 NDArray
中定义的隐式运算符)。所以当你这样做时:
arr.SetData(value, i);
和 value
是类型 double
- 被调用的重载是 SetData(NDArray, int[)
,因为它是最合适的,因为 double
可以转换为 NDArray
.
然而,当 value
是类型 object
甚至是未解析的泛型类型 (T
) - 然后被调用的重载是 SetData(object, int[])
,所以事情变得疯狂 (因为如果您作为对象传递的值不是 NDArray,那么这些重载会做完全不同的事情,而且它不是 - 它是双倍的)。重载是在编译时选择的,即使在泛型情况下,编译器也无法选择 NDArray
重载(因为选择的重载应该适合您可以传递的任何可能的泛型类型)。
在您的情况下,您可以通过在扩展方法中使用 NDArray value
而不是对象或泛型来“解决”此问题:
public static class Extensions
{
public static void SetMultiData(this NDArray arr, NDArray value, int[] indices)
{
foreach (var i in indices)
arr.SetData(value, i);
}
}