C#小测试(二):嵌套子类带来的困惑
2008-08-04 11:53 Anders Cui 阅读(2758) 评论(20) 编辑 收藏 举报这里有个很有意思的题目,先别运行程序,猜猜它会输出什么?
public class A<T>
{
public class B : A<int>
{
public void M()
{
Console.WriteLine(typeof(T).ToString());
}
public class C : B { }
}
}
class Program
{
static void Main(string[] args)
{
A<string>.B.C c = new A<string>.B.C();
c.M();
}
}
这里的T是int、string还是其它的什么?还是程序压根儿就不能通过编译?
...
...
...
答案是System.Int32,你猜对了吗?很可能这跟你的期望值并不一致。我们来看看它背后的玄妙之处。
有人认为c.M方法输出Int32,因为B的声明B : A<int>告诉B,T将一直是int。这好像是有道理的,却不是真正的原因。我们先不管C,执行这一行代码(new A<string>.B()).M(),它会输出:String。B是A<int>的子类并不意味着B中的T总是int。
问题的根本在于声明class C : B会产生不明确的内容:即是class C : A<T>.B 还是class C : A<int>.B?我们得首先搞清楚这个问题。
先看继承和嵌套类的区别:
public class X
{
public void M() { }
}
public class Y
{
public void N() { }
public class Z : X { }
}
...
(new Y.Z()).M(); // 合法,M是继承自基类的方法
(new Y.Z()).N(); // 不合法,外部类的成员不是内部类的方法
在我们的测试题中,我们调用了A<string>.B.C类的方法M,它本质上则调用了基类的M方法。如果基类是A<T>.B,那么应调用A<T>.B.M,即输出T的当前值:String;如果基类是A<int>.B,那么应调用A<int>.B.M,即总是输出:Int32。
而程序的结果告诉我们C#选择了后者。是不是有些不可思议?
也许程序中的泛型扰乱我们的直觉。那就看一个不使用泛型的例子:
public class D
{
public class E { }
}
public class F
{
public class E { }
public class G
{
public E e; // 很明显这里是 F.E
}
}
public class H : D
{
public E e; // 很明显这里是 D.E
}
OK,目前为止,一切都是合法的,而且也没什么异议。当我们通过类型名称来引用类型时,类型可以来自于基类(H.e),也可以来自于外部类(G.e)。但是如果这两种情况同时出现会如何呢?
public class J
{
public class E { }
public class K : D
{
public E e; // 是J.E 还是D.E?
}
}
这种情况是合法的吗?答案是肯定的。此时C#认为基类要优先于外部类。这也合乎常理,派生类与基类的关系是“is-a”,内部类与外部类的关系是“包含于”,前者要比后者更为紧密。
还可以这么理解:从基类继承下来的成员都属于“当前的作用域”,因此从“外部的作用域”获得的成员的优先级要低一些。
一般地,对于一个类型S,在其上下文中对一个名称进行解析的算法是:
- S的类型参数(type parameter)
- S可以访问的内部类
- S的基类中可以访问的内部类(访问基类的顺序是由近及远)
- S的外部类
现在回到开始的问题。我们看看在解析C的基类的时候发生了什么。我们调用的是B.M(),所以问题可以转化为找到正确的B。首先C没有类型参数,也没有内部类。
A<T>.B的基类是A<int>,而外部类则是A<T>,两者都有一个名称为B的内部类。选哪一个呢?根据上面的算法,基类优先,即A<int>.B。
这个过程实在是非常的绕,虽然跟着文章得出了结论,还是有些模糊。。。
参考:
出处:http://anderslly.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端