我们来看一下这个代码:
DayOfWeek.Friday.GetType()
DayOfWeek.Friday是一个枚举值,它的这个GetType()是哪里冒出来的?结合我们前面所学的知识,子类中找不到的成员,一定是来自于它的父类,是不是?然而,enum DayOfWeek又没有继承啊?
欲知详情,有一个简单粗暴的办法:直接在GetType()上F12,马上转到方法定义:
哈哈,GetType()就是
Object
的实例方法嘛!
实际上,Object是C#中所有类型的父类,可以说它是万物之祖。因此,任何类默认自动的继承自Object,不需要显式声明。
Object还有其他几个常用的方法:
1、Equals():注意这里有它是一个静态方法,用于比较作为参数传入的两个对象:
- 如果是值类型,比较两个对象的值内容(所有字段),相同为true,否则为false
- 如果是引用类型,
- 如果有一个值是null值,返回false,否则
- 当两个变量指向同一个对象,返回true;否则为false
struct Bed //值类型 //class Bed //引用类型 { internal int Id { get; set; } }
检测代码:
Bed a = new Bed(); a.Id = 1; Bed b = new Bed(); b.Id = 1; //或者=2 //如果Bed是struct,true //如果Bed是class,false Console.WriteLine(Equals(a, b));
演示:
关于==
- 值类型:
内置的简单值类型:比较值
自定义的值类型:默认不支持,需要自己进行运算符重载
enum:比较底层数值 - 引用类型:同Equals(),除非进行了运算符重载
- string:类似于值类型,且两个为null/empty的字符串相同
2、GetHashCode():获取对象的哈希值。哈希(Hash),有被称之为散列,是一种获取固定长度数据的算法。GetHashCode()可以获取将当前对象进行Hash预算之后的结果。
注意:GetHashCode()是virtual的,所以可以被子类重写(override)。
如果没有被重写,两个完全相同的对象GetHashCode()的Hash值必然相同。但是,相同的Hash值并不意味着生成他们的对象相同!所以,我们可以用GetHashCode()确定两个对象不相同,但不能用于确定他们相同。
//接上面代码 ///Bed为值类型,输出相等 ///Bed为引用类型,输出不同 Console.WriteLine(a.GetHashCode()); Console.WriteLine(b.GetHashCode());
演示:
_3、GetType():获取当前对象的类型信息。注意这里获取的是当前对象的类型信息,不是当前变量的类型信息。什么意思呢?看代码:
_
Person wx = new Student(); Console.WriteLine(wx.GetType().Name); //结果为:Student
_Person就是wx这个变量的类型,而Student就是wx(指向的)这个对象的类型。又因为wx究竟指向的是什么对象,只能是在程序运行时才能知道,所以GetType()又被称之为获取变量的“运行时类型”。经常与之相对比的是:typeof(),它获取的是类的类型信息:
_
Console.WriteLine(typeof(Student));
_
因为传入typeof的就是一个类,所以可以在编译时就知道这个类的类型信息,它又被称之为获取类型的“编译时类型”。
这是一个常见的面试题,同学们要注意。
4、ToString():将对象转换成字符串。注意这也是一个virtual方法,所以很多常用的.NET类库对象都重写了这个方法,比如DateTime。如果没有重写的话,默认返回对象的类的全名。
_
Console.WriteLine(DateTime.Now.ToString()); Console.WriteLine(new Student().ToString());
好了,终于到了我们可以系统的捋一捋.NET中所有类型关系的时候了,一图胜千言:
咳咳,……,嗯,这个图看起来可能还是有点晕,解释一下:
- 所有类型被分为值类型(Value Type)和引用类型(Reference Type)
- 但都继承自Object
- 所有class定义的都是引用类型(数组继承自Array,是引用类型;String是一个行为特征非常类似于值类型的引用类型),其他struct/enum定义的都是值类型
所以,在C#中,“万物皆对象”,就是一个事实:所有的类都继承自Object,所以,所有的对象都是Object对象——没毛病。
那么,这就涉及到下面一个问题:
装箱和拆箱
我们来看这样几行代码:
object wx = new Student(); //把new Student()的堆地址存到wx变量中 int i = 888; //把值888直接存放到变量i中 object o = 986; //把986存到哪里?
注意最后一行代码,986能不能直接放到变量o里面?这是不行的,因为o是object引用类型的,o里面只能存放一个堆地址。但是object o =986;又是符合语法的,这种把值类型数据直接赋值给object变量的行为,被称之为装箱(box)。具体来说,这一行代码做了两件事:
- 自动生成一个object对象(相当于new object())
- 将整数值986存入这个object对象(这个过程就是装箱的核心过程)
- 将object对象的堆地址赋值给o变量
写成伪代码就是:
object o = new object(); o.inbox = 986; //inbox是我随手写的,代指“箱子内部”
我们可以用&运算符查看:
int i = 986; object o = 986;
既然能够装箱,当然也能够拆箱(unbox)
int i = (int)o;
注意:装箱和类型的强制转换语法完全一致,但是不要将他们混为一谈:
- 装箱和拆箱仅适用于值类型和object之间;强制类型转换适用于任何有继承关系的类型
- 装箱和拆箱多了一个创建object实例的过程;类型转换没有这个过程,不会额外的生成一个对象
最后的最后,我们来学习C#4.0因为因为Office Automation APIs和Iron Python libraries而引入的一种新类型
动态类型(dynamic)
学习这个类型,完全是为了面试需要。^_^
很多时候,dynamic完全和object完全一致,任何类型对象都可以使用dynamic标记。使用dynamic标记的变量可以绕过C#的编译时(注意:仅仅是编译时)的类型检查。
object o = "986"; Console.WriteLine(o - 88); //编译时错误 dynamic d = "986"; Console.WriteLine(d.GetType()); //仍然是string类型 Console.WriteLine(d - 98); //绕过了类型检查
但是,dynamic并不意味这变量类型可变,通过上述d.GetType()可以清楚的看到d的类型;而且,当我们运行d-98时会报出因类型不匹配造成的错误: