上一篇
第一章 从类型到接口
先看一个简单的程序:
int x = 1;
object y = 2;
Console.WriteLine(x.GetType());
Console.WriteLine(y.GetType());
运行结果:
System.Int32
System.Int32
我们定义了两个变量,x和y,它们都是整数。
现在的问题是,int x和object x这两种定义究竟有什么不同。
我们利用VS的智能感知提示来观察下:
这是x的
这是y的
我们发现y只有4个方法,而x有6个。CompareTo和GetTypeCode对于y是没有的。
我们看下CompareTo方法的提示:
请注意,这个方法是属于int的(int.CompareTo)
同样是定义一个整数,int x和object y究竟有什么不同呢?
我们看object y,它是什么意思?
很多人不假思索地说,这还不简单?它表示定义了一个object类型的变量,y。请注意,这种说法是不准确的,object y的确切含义是,它定义了一个变量,这个变量可以是object类型,或者它的派生类型。我们知道,object是一切类型的基类(这一点和Java是一样的,在很多编程语言/类库中都是如此,比如CObject是MFC类型的基类,TObject是VCL的基类等等)。因此object y中的y可以是object,也可以是任意类型,比如string int double甚至是一个类的实例,比如User、Product 或者 ContactList。
int x呢,它只能是一个整数,因为整数类型没有派生的类型。它不能表示 string或者double了。所以,int x表示,x这个类型,编译器将其视作int,它可以调用int的方法,而object y编译器将其视作object,它可以是任意对象,但是编译器只能调用这写类型共同的基类,也就是object中定义的方法,而不能直接调用其某个派生类,比如int中的方法。
看这样一个程序:
class A
{
public void FooA() { }
}
class B : A
{
public void FooB() { }
}
class C : B
{
public void FooC() { }
}
class Program
{
static void Main(string[] args)
{
A a = new A();
A b = new B();
A c = new C();
}
}
C#的继承和定义方法的方式和C++、Java不同,但是读者应该不会觉得难以理解。
根据前面所述,A类型的变量a b c可以接受A类型和A类型的派生类型的对象实例,因此上面三行都是合法的。
但是
A d = new object();
这么写就是不合法的,A类型不能接受它的父类的类型。
同理,
B b = new A();
这么写也是不行的。
我们观察下我们定义的三个类型:
也许你认为b.敲出来以后应该能看到FooB,而c.敲出来能看到FooB和FooC,那么你就错了。我们之前说了,当你定义A类型的时候,无论接收的是什么类型的对象实例,编译器都视作是A类型,所以只能看到A类型的方法。
我们写
C d = new C();
此时就可以看到FooB和FooC了。
如果我们希望让c这个对象也能看到FooB和FooC,(它是A类型),我们需要让编译器将它“视作”C类型,因此用as运算符:
A c = new C();
(c as C).FooC();
此时可以看到FooC了。
看这样一个程序:
class A
{
public void SayHello()
{
Console.WriteLine("Hello A");
}
}
class B
{
public void SayHello()
{
Console.WriteLine("Hello B");
}
}
class Program
{
static void Main(string[] args)
{
A a = new A();
a.SayHello();
B b = new B();
b.SayHello();
}
}
A和B是两个无继承关系的类,但是巧合的是,它们都有一个相同方法签名(指一个方法的方法名、参数和返回值都一样)的方法:SayHello。
我们的主程序分别调用了这两个方法。
我们有没有办法“取最小公倍数”呢。接口可以帮我们做到这一点。
我们可以定义一个接口:
interface ISayHello
{
void SayHello();
}
接口中包含了那个一致的方法。然后我们让这两个类都实现这个接口,相当于告诉编译器,它们都具有了这个接口定义的SayHello方法:
class A : ISayHello
{
public void SayHello()
{
Console.WriteLine("Hello A");
}
}
class B : ISayHello
{
public void SayHello()
{
Console.WriteLine("Hello B");
}
}
最后我们用接口类型替代具体的类型,调用它:
class Program
{
static void Main(string[] args)
{
ISayHello a = new A();
a.SayHello();
ISayHello b = new B();
b.SayHello();
}
}
我们回顾下:
ISayHello a = new A();
这到底是什么意思呢?ISayHello这个类型告诉编译器,将a“视作”任意具有ISayHello这个接口定义的类型方法的任意类的实例,它可以是A,也可以是B,或者C,只要C也实现了ISayHello。
回到最初的例子,int具有一个CompareTo的方法,它的作用是对当前的整数和传入的整数比较,并且将结果返回:
如果这个整数比传入的大,返回1,如果相等,返回0,如果小于传入的整数,返回-1
看如下程序:
int x = 1;
Console.WriteLine(x.CompareTo(10) < 0 ? "小于" : "大于等于");
运行结果
小于
其实,拥有CompareTo的并非只有int,string也有。它比较的是字符串的ascii的大小,如果一个字符串有多个字符,那么优先比较第一个字符,如果相等,再比较第二个。通俗地说,你翻开一本英文字典,排在前面的单词比排在后面的更小。
string y = "b";
Console.WriteLine(y.CompareTo("a") < 0 ? "小于" : "大于等于");
因此这个程序输出
大于等于
我们怎么将这两个程序用接口来改写呢?很显然,只要找到一个接口,包含CompareTo这个方法的定义就可以。幸好int string都实现了IComparable接口,并且包含一个int CompareTo(object)的方法。我们来调用它:
IComparable x = 1;
Console.WriteLine(x.CompareTo(10) < 0 ? "小于" : "大于");
IComparable y = "b";
Console.WriteLine(y.CompareTo("a") < 0 ? "小于" : "大于");
程序的结果和刚才一样。
请注意,我们先前调用的int.CompareTo的方法签名是int CompareTo(int),并非int CompareTo(object),string的也一样,严格来说,这样的改写其实是不等价的。但是在这里,我们不深入探讨这一点。大体上说,我们使用接口代替了具体的类型完成了改写。
下一篇