【问题标题】:Overload resolution on generic method doesn't work as expected泛型方法的重载解决方案无法按预期工作
【发布时间】:2018-06-14 13:22:09
【问题描述】:

去年我问过如何遍历和打印锯齿状数组,而不必为每个添加的维度编写重载函数。 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&lt;T&gt;(T[] array) 被调用而不是Print&lt;T&gt;(T[][] array)。当从PrintDynamic(dynamic array) 调用时,编译器如何知道要使用哪个Print&lt;T&gt;(),但从Print&lt;T&gt;() 内调用时却不知道?

【问题讨论】:

  • 重载分辨率是一个复杂的课题!
  • 简而言之-编译器不知道dynamic的情况。 DLR 在运行时使用反射解决它。
  • 因此即使Print&lt;T&gt;(T[][] array)接受超过2维的锯齿状数组作为参数,它仍然会认为array[i]始终是一维数组,因此编译器将始终调用Print&lt;T&gt;(T[] array) ?那么在运行时调用的 2 个方法中的哪一个并没有真正解决?
  • @AlexanderMoser:是的,我已经解释了第二个答案,它正确地描述了为什么你的代码会这样。

标签: c# generics dynamic overload-resolution


【解决方案1】:

回答你原来的问题。当你打电话时:

str += Print(sub)

并且该方法的原始对象是int[][][],那么该方法的&lt;T&gt;int[]。所以你用T[] 调用Print(sub),其中Tint[]

因此,PrintT[] 重载被选中,Tint[] - 一切都按预期进行。这是编译时解决的,是编译器可以利用它所拥有的信息做的最好的事情。

请记住,泛型方法仅编译为 IL 一次 - 您不会因碰巧调用它的不同方式而获得“不同版本”(与 C++ 模板不同)。泛型方法的行为必须对所有可能的输入都有效。所以如果方法接收到T[][],而你在内部提取子元素,则只能认为子对象类型为T[]。它无法在运行时检测到“哦,实际上,T 是一个 int[],所以我将调用一个不同的重载”。无论输入是什么,该方法都只会调用Print(sub)T[] 重载。

但是,在使用 dynamic 的情况下,您将忽略编译时烘焙的所有泛型类型信息,并说“现在在运行时使用反射实际上是什么类型。现在哪种方法最合适?使用那个!这种行为有很大的开销,因此必须使用 dynamic 关键字明确请求。

【讨论】:

    【解决方案2】:

    “当从 PrintDynamic(动态数组) 调用时,编译器如何知道使用哪个 Print()”

    答案是虚函数表。由于您使用的是 C#,因此所有类型都继承自“对象”类。对 Print() 的简单调用会尝试打印对象本身而不是其内容。为什么?因为没有重载更合适的方法,所以为对象调用了 ToString() 方法。每当您使用 C# 等强类型 OOP 语言时,每个对象都是指向其数据结构(通常在堆上)的指针,并且该数据结构的第一个条目是指向该对象的虚函数表的指针。虚函数表本质上是该类支持的每个函数的函数指针数组。因为通过调用 PrintDynamic 您实际上是在传递对象的指针,所以对象指针的分辨率映射回其类的虚函数表。然后,可以调用适当的重载函数。这是对该过程的高级描述。这个概念在 C++ 等语言中是相似的。我希望这可以帮助您更多地了解编译器在幕后实际执行的操作。我会推荐一些学术阅读,或者可能是以下链接以了解更多详细信息。

    https://en.wikipedia.org/wiki/Virtual_method_table

    【讨论】:

    • 我想我现在知道函数指针和虚函数表是如何工作的了,谢谢。但是当我从内部调用 Print(array) 时,我仍然不明白为什么选择函数 'Print(T[] array)' 而不是 'Print(T[][] array)',但不是如果我从 'PrintDynamic()' 调用它?
    【解决方案3】:

    如果我是你,因为这是一个在编译时无法真正解决任意维度的问题,我会完全避免使用泛型:

    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 ] ] ]

    【讨论】:

    • 感谢您的回答。虽然这是解决我以前的问题的好方法,但不幸的是,它并不能满足我对为什么程序没有按照我预期的方式运行的好奇心。
    猜你喜欢
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 2014-08-30
    • 1970-01-01
    • 1970-01-01
    • 2016-07-27
    • 2017-04-17
    相关资源
    最近更新 更多