去年我问过如何遍历和打印锯齿状数组,而不必为每个添加的维度编写重载函数。 Generic printing of jagged arrays。
我再次接受了这个问题并且能够像这样解决它。它类似于我得到的答案之一,但不完全相同。
static string Print<T>(T[] array)
{
string str = "[ ";
for (int i = 0; i < array.Length; i++)
{
str += array[i];
if (i < array.Length - 1)
str += ", ";
}
return str + " ]\n";
}
static string Print<T>(T[][] array)
{
string str = "";
for (int i = 0; i < array.Length; i++)
{
var sub = array[i];
if (sub.Length != 0 && sub[0] is Array)
str += PrintDynamic(sub);
else
str += Print(sub);
}
return str + "\n";
}
private static string PrintDynamic(dynamic array)
{
return Print(array);
}
它工作正常,我得到了正确的输出:
var twoDim = new int[][]
{
new int[] { 0, 1, 2, 3 },
new int[] { 0, 1, 2 },
new int[] { 0 }
};
var threeDim = new int[][][] { twoDim, twoDim }
Console.WriteLine(Print(threeDim));
// Output:
// [ 0, 1, 2, 3]
// [ 0, 1, 2]
// [ 0 ]
//
// [ 0, 1, 2, 3]
// [ 0, 1, 2]
// [ 0 ]
但是我仍然不满意,因为如果我不需要PrintDynamic()
并且如果我可以写
str += Print(sub);
而不是
str += PrintDynamic(sub);
这就是我的问题所在。如果我改变那一行,我没有得到任何错误,但输出变为
// [ System.Int32[], System.Int32[], System.Int32[], System.Int32[]]
// [ System.Int32[], System.Int32[], System.Int32[]]
// [ System.Int32[] ]
//
// [ System.Int32[], System.Int32[], System.Int32[], System.Int32[]]
// [ System.Int32[], System.Int32[], System.Int32[]]
// [ System.Int32[] ]
因为Print<T>(T[] array)
被调用而不是Print<T>(T[][] array)
。当编译器从Print<T>()
调用时,编译器如何知道要使用哪个PrintDynamic(dynamic array)
,而在Print<T>()
内调用时却不知道?
答案 0 :(得分:1)
回答您的原始问题。致电时:
str += Print(sub)
,该方法的原始对象是int[][][]
,则该方法的<T>
是int[]
。因此,您用Print(sub)
呼叫T[]
,其中T
是int[]
。
因此,选择了T[]
的{{1}}重载,其中Print
为T
-一切都按预期进行。这是解决编译时间的问题,并且是编译器可以利用其所拥有信息的最佳方法。
请记住,泛型方法只能编译到IL一次-您不会因为碰巧调用它的方式不同而获得“不同版本”(与C ++模板不同)。通用方法的行为必须对所有可能的输入均有效。因此,如果该方法接收到int[]
,并且您在内部提取了子元素,则它只能将子对象类型视为T[][]
。它在运行时无法检测到“哦,实际上T是一个int [],所以我将调用另一个重载”。无论输入什么,该方法都只会调用T[]
的{{1}}重载。
但是,在使用T[]
的情况下,您将忽略编译时产生的所有通用类型信息,而是说“在运行时使用反射现在实际上是什么类型”。现在哪种方法最合适?使用那个!此行为的开销很大,因此必须使用Print(sub)
关键字来明确请求。
答案 1 :(得分:0)
“当从PrintDynamic(动态数组)调用时,编译器如何知道要使用哪个Print()”
答案是虚函数表。由于您使用的是C#,因此所有类型都继承自“object”类。对Print()的简单调用会尝试打印对象本身而不是其内容。为什么?因为为对象调用ToString()方法,因为没有重载更合适的方法。每当您使用强类型OOP语言(如C#)时,每个对象都是指向其数据结构的指针(通常在堆上),此数据结构的第一个条目是指向该对象的虚函数表的指针。虚函数表本质上是类支持的每个相应函数的函数指针数组。因为通过调用PrintDynamic实际上是传递对象的指针,对象指针的分辨率会映射回其类的虚函数表。然后,可以调用适当的重载函数。这是该过程的高级描述。这个概念在C ++等语言中是类似的。我希望这有助于您更多地了解编译器在幕后实际执行的操作。我会推荐一些学术性阅读或者以下链接以获取更多细节。
答案 2 :(得分:0)
如果我是你,因为这是一个在编译时无法在任意维度上解决的问题,我完全避免使用泛型:
public static string Print(Array array)
{
string str = "[ ";
for (int i = 0; i < array.Length; i++)
{
var element = array.GetValue(i);
if (element is Array)
str += Print(element as Array);
else
{
str += element;
if (i < array.Length - 1)
str += ", ";
}
}
return str + " ]";
}
这会产生嵌套输出,我认为它更好,随着深度的增加,它会随意嵌套。
[[[0,1,2,3] [0,1,2] [0]] [[0,1,2,3] [0,1,2] [0]]]