C# 接口(什么是接口)
什么是接口
接口是指定一组函数成员而不实现它们的引用类型。所以只能类和结构来实现接囗。这种描
述听起来有点抽象,因此先来看看接口能够帮助我们解决的问题,以及是如何解决的。
以下面的代码为例。观察program类中的Main方法,它创建并初始化了一个CA类的对象,
并将该对象传递给PrintInfo方法。PrintInfo需要一个CA类型的对象,并打印包含在该对象
内的信息。
class CA
{public string Name;public int Age;
}class CB
{public string First;public string Last;public double PersonsAge;
}class Program
{static void PrintInfo(CA item){Console.WriteLine($"Name:{item.Name},Age:{item.Age}");}static void Main(){CA a=new CA() {Name="John Doe",Age=35};PrintInfo(a);}
}
只要传入的是CA类型的对象,PrintInfo方法就能工作正常。但如果传入的是CB类型的对
象(同样见上面的代码),就不行了。假设PrintInfo方法中的算法非常有用,我们想用它操
作不同类的对象。
现在的代码不能满足上面的需求,原因有很多。首先,PrintInfo的形参指明了实参必须为
CA类型的对象,因此传人CB或其他类型的对象将导致编译错误。但即使能克服这个障碍,使其
接受CB类型的对象还是会有问题,因为CB的结构与CA的不同。其字段的名称和类型与不一
样,PrintInfo对这些字段一无所知。
能不能创建一个能够成功传人PrintInfo的类,并且不管该类是什么样的结构,PrintInfo
都能正常处理呢?接口使这种设想变为可能。
图16-1中的代码使用接口解决了这一问题。你现在不需要理解细节,但一般来说,注意以下几点。
- 首先,它声明了一个llnfo接口,其中包含两个方法一GetName和GetAge,每个方法
都返回string。 - 类CA和CB各自实现了llnfo接口(将其放到基类列表中),并实现了该接口所需的两个
- Main创建了CA和的实例,并传人PrintInfo。
- 由于类实例实现了接口,PrintInfo可以调用那两个方法,每个类实例执行各自的方法,
就好像是执行自己类声明中的方法。
interface IInfo //声明接口
{string GetName();string GetAge();
}class CA:IInfo //声明实现接口的CA类
{public string Name;public int Age;public string GetName(){return Name;} //在CA类中实现两个接口方法public string GetAge(){return Age.ToString();}
}class CB:IInfo //声明实现接口的CB类
{public string First;public string Last;public double PersonsAge;public string GetName(){return First+","+Last;} //在CB类中实现两个接口方法public string GetAge(){return PersonsAge.ToString();}
}class Program
{static void PrintInfo(IInfo item) //传入接口的引用{Console.WriteLine("Name:{0},Age {1}",item.GetName(),item.GetAge());}static void Main(){CA a=new CA(){Name="John Doe",Age=35};CB b=new CB(){First="Jane",Last="Doe",PersonsAge=33};PrintInfo(a); PrintInfo(b); //对象的引用能自动转换为他们的接口的引用}
}
使用IComparable接口的示例
我们已经了解了接口能够解决的问题,接下来看第二个示例并深入一些细节。先来看看如下
代码,它接受了一个没有排序的整数数组并且按升序进行排序。这段代码的功能如下:
- 第一行代码创建了包含5个无序整数的数组;
- 第二行代码使用了Array类的静态sort方法来对元素排序;
- 用foreach循环输出它们,显示以升序排序的数字。
Array类的sort方法在int数组上工作得很好,但是如果我们尝试在自己的类数组上使用会
发生什么呢?如下所示:
class MyClass //声明一个简单类
{public int TheValue;
}...
MyClass[] mc=new MyClass[5]; //创建一个有5个元素的数组//创建并初始化元素Array.Sort(mc); //尝试使用Sort时抛出异常
如果你尝试运行这段代码的话,不会进行排序而是会得到一个异常。sort不能针对Myclass
对象数组进行排序的原因是,它不知道如何比较用户定义的对象以及如何进行排序。Array类的
sort方法其实依赖于一个叫作1Comparable的接口,它声明在BCL中,包含唯一的方法CompareTo。
下面的代码展示了IComparable接口的声明。注意,接口主体内包含CompareTo方法的声明,
指定了它接受一个object类型的参数。尽管该方法具有名称、参数和返回类型,却没有实现。
它的实现用一个分号表示。
图16-2演示了IComparable接口。CompareTo方法用灰色框显示,以表明它不包含实现。
尽管在接口声明中没有为CompareTo方法提供实现,但IComparable接口的.NET文档中描述
了该方法应该做的事情,你可以在创建实现该接口的类或结构时参考。文档中写道,在调用
方法时,它应该返回以下几个值之一
- 负数值如果当前对象小于参数对象;
- 正数值如果当前对象大于参数对象;、
- 零如果两个对象在比较时相等。
Sort使用的算法依赖于使用元素的CompareTo方法来决定两个元素的次序。int类型实现了
lcomparable,但是myClass没有,因此当Sort尝试调用MyClass不存在的CompareTo方法时会
抛出异常。
我们可以通过让类实现IComparable,让Sort方法可以用于MyClass类型的对象。要实现一
个接口,类或结构必须做两件事情:
- 在基类列表中列出接口名称;
- 为接口的每一个成员提供实现。
例如,下面的代码更新了MyClass来实现IComparab1e接口。注意下面关于代码的内容。 - 接口名称列在类声明的基类列表中。
- 类实现了一个名为compareTo的方法,它的参数类型和返回类型与接口成员一致。
- 实现CompareTo方法以遵循接口文档的定义。也就是说,它将它的值与传人方法的对象值
进行比较,并据此返回一1、1或0。
class MyClass:IComparable //基类列表中的接口名称
{public int ThValue;public int CompareTo(Object obj) //接口方法的实现{MyClass mc=(MyClass)obj;if(this.ThValue<mc.ThValue) return -1;if(this.ThValue>mc.TheValue) return -1;return 0;}
}
图16-3演示了更新后的类。从有阴影的接口方法指向类方法的箭头表示接口方法不包含代
码,而是在类级别的方法中实现。
下面显示了更新后的完整代码,现在可以使用sort方法来排序MyClass对象数组了。Main
创建并初始化了MyClass对象的数组并且输出它们,然后调用了sort并重新输出,以显示它们
已经排好序了。
class MyClass:IComparable //类实现接口
{public int TheValue; public int CompareTo(object obj) //实现方法{MyClass mc=(MyClass)obj;if(this.TheValue<mc.TheValue)return -1;if(this.TheValue>mc.TheValue) return -1;return 0;}
}class Program{static void PrintOut(string s,MyClass[] mc){Console.Write(s);foreach(var m in mc)console.Write($"{m.TheValue}");Console.WriteLine("");}
}static void Main()
{var myInt=new []{20,4,16,9,2};MyClass[] mcArr=new MyClass[5]; //创建Myclass对象for(int i=0;i<5;i++){mcArr[i]=new MyClass();mcArr[i].TheValue=myInt[i];}PrintOut("Initial Order:",mcArr);//输出初始数组Array.Sort(mcArr); //数组排序PrintOut("Sorted Order: ",mcArr);//输出排序后的数组
}