【问题标题】:Puzzle from an Interview with Eric Lippert: Inheritance and Generic Type SettingEric Lippert 访谈中的谜题:继承和泛型类型设置
【发布时间】:2016-12-25 09:21:14
【问题描述】:

有人可以向我解释为什么下面的代码会输出它的作用吗? 为什么第一个是T是String,不是Int32,为什么下一个输出是相反的情况?

这个谜题来自interview with Eric Lippert

当我查看代码时,我真的不知道它是 Int32 还是 String:

public class A<T>
    {
        public class B : A<int>
        {
            public void M() { System.Console.WriteLine(typeof(T)); }
            public class C : B { }
        }
    }

    public class P
    {
        public static void Main()
        {            
            (new A<string>.B()).M(); //Outputs System.String
            (new A<string>.B.C()).M(); //Outputs System.Int32
            Console.Read();
        }
    }

【问题讨论】:

    标签: c# generics inheritance


    【解决方案1】:

    有人可以向我解释一下为什么下面的代码会输出它的作用吗?

    我在这里简单解释一下;更详细的解释可以在here找到。

    问题的关键在于确定Bclass C : B中的含义。考虑一个没有泛型的版本:(为简洁起见,我将省略 publics。)

    class D { class E {} }
    class J {
      class E {}
      class K : D {
        E e; // Fully qualify this type
      }
    }
    

    可能是J.ED.E;是哪一个?解析名称时,C# 中的规则是查看基类层次结构,并且只有在失败时才查看您的容器。 K 已经有一个继承的成员 E,所以它不需要查看它的容器来发现它的容器有一个包含的成员 E。

    但是我们看到这个谜题有同样的结构;它只是被泛型混淆了。我们可以将泛型视为模板,只需将 A-of-string 和 A-of-int 的构造写成类:

    class A_of_int 
    {
      class B : A_of_int
      {
        void M() { Write("int"); }
        class C : B { } // A_of_int.B
      }
    }
    class A_of_string
    {
      class B : A_of_int
      {
        void M() { Write("string"); }
        class C : B {} // still A_of_int.B
      }
    }
    

    现在应该清楚为什么A_of_string.B.M()stringA_of_string.B.C.M()int

    【讨论】:

      【解决方案2】:

      B 内部的方法 M 打印 A&lt;T&gt;typeof(T)AB 的父类。

      所以无论B 是否派生自任何东西,M 都会打印出typeof(T)String

      所以A&lt;T&gt;.B.M 打印最接近AT

      所以A&lt;string&gt;.B.M 将打印string

      现在,让我们扩展表达式A&lt;string&gt;.B.C,它相当于A&lt;string&gt;.B.A&lt;int&gt;.B(因为CA&lt;int&gt;.B),所以方法A&lt;string&gt;.B.A&lt;int&gt;.B.M 将打印最近的T

      A&lt;string&gt;.B.A&lt;int&gt;.B.M 将打印int

      【讨论】:

      • 为什么 C 等于 A.B?更确切的说,为什么输入的值 T 是 String 会丢失,而使用 Int 的默认值?
      • 不等于,是类型解析替代类型来解析类型。
      【解决方案3】:

      Introduction to GenericsT 也可用于嵌套类。这是嵌套在A 中的类B 的情况。另一方面,C 嵌套在 B 中,B 的 T 可用于 C。如您所见,B 的 T 是 int,在 C 上调用的方法将使用 int 作为通用参数。

      【讨论】:

        【解决方案4】:

        方法M()总是打印其类的父类的泛型参数类型:

        所以(new A&lt;string&gt;.B.C()).M(); 应该打印B 的泛型类型,它始终是int。 (你可以看到B总是A&lt;int&gt;

        同样(new A&lt;string&gt;.B()).M(); 应该打印string 因为B 的父级是A&lt;string&gt;

        【讨论】:

        • 我不确定我是否没有看到输出,乍一看我可以正确猜测输出,但是在看到结果之后,检测结果的打印方式并不难。楚谜很容易解决巡逻;)
        【解决方案5】:

        稍微修改一下代码:

        public class A<T>
        {
            public class B : A<int>
            {
                public void M() { System.Console.WriteLine(typeof(T)); }
                public class C : A<T>.B { }
            }
        }
        
        public class P
        {
            public static void Main()
            {            
                (new A<string>.B.C()).M(); //Outputs System.String
            }
        }
        

        注意我如何将C 的基类从B 更改为A&lt;T&gt;.B。这会将输出从 System.Int32 更改为 System.String

        否则,A&lt;string&gt;.B.C 不是源自A&lt;string&gt;.B,而是源自A&lt;int&gt;.B,导致您看到的行为。这是因为一般情况下,基类中定义的名称可以通过非限定查找获得,而名称B 是在基类A&lt;int&gt; 中定义的。

        【讨论】:

        • 如果一个类与输入的 T 值相距不超过一个“级别”,是否可以访问它?例如。 (新 A.B()).M();输出 String 是因为 B 在 A 之后直接访问,这意味着提供的 T 会覆盖 B 默认继承的值?而 (new A.B.C()).M();输出 Int32 因为曾经在 C 中,它现在距离输入的 String 的 T 值 2 级,因此它使用 Int32 的默认继承值。
        • @Backwards_Dave 不,这不是真的,将M 的定义放入C 会表明它不是真的。 A&lt;T&gt;.B.M 将打印T,无论你在调用它时离它还有多少层。棘手的部分只是因为误导性基类,A&lt;int&gt;.B.M 被调用而不是A&lt;string&gt;.B.M
        • 让我再解释一下。如果 T 通过继承向下流动,它将覆盖继承中定义的任何默认 T 值。但是,一旦有“向上”的流,在这种情况下,当 C 调用在 C 的父类中定义的 M() 时,指定的 T 值就会丢失,并使用 Int32 的默认 T 值。而如果 M() 是在 C 而不是 B 中声明的,那么就不会有向上的流动。不完全确定在这里用什么词,也许“流”不是很好,但我希望你知道我的意思。
        • @Backwards_Dave 这不是发生的事情。 T 没有被覆盖。在A&lt;X&gt;.B.C 内部,T 将是X,而不考虑B 的基类。 A&lt;string&gt;.B.C.MA&lt;int&gt;.B.M,不是因为T 没有您可能期望的值,而是因为基类B 不是您可能期望的类型。对于A&lt;int&gt;.B,很明显T应该是int,而且确实是。
        • 为什么基类 B 不是我所期望的类型?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-27
        • 2015-09-15
        • 1970-01-01
        • 2018-08-01
        相关资源
        最近更新 更多