连载,LINQ兵法十四章,3(1of1)

hi20140509 2014-05-12 03:31:00
加精
上一篇

第三章 泛型

和Java、C++一样,区别于Python、Javascript、Ruby等语言,C#是强类型语言。所谓强类型,就是指编译器会在编译阶段检查类型和对象调用的合法性。

在第一章,我们举了一个例子:

int i = 1;
i.CompareTo(2);


这个代码是合法的,但是

object i = 1;
i.CompareTo(2);


这样的代码是非法的。因为编译器会检查i的类型,它是object的,而object类型并没有CompareTo这个方法。有人说了,为什么编译器不能“智能一点”,根据i=1推断出i就是int类型呢。

那你冤枉编译器了,有时候,这真不好推断:

static bool IsInteger(string s)
{
int n = 0;
return int.TryParse(s, out n);
}
static void Main(string[] args)
{
string s = Console.ReadLine();
object i;
if (IsInteger(s))
i = int.Parse(s);
else
i = s;
i.CompareTo(2);
}


我们的程序从控制台读入一个字符串,如果它表示一个整数,我们就让i以整数的方式接收它,如果是别的字符串,我们就以字符串的形式接收它。我们前面说了,i被定义为object类型,那么它天然地可以接受任意类型。

此时编译器怎么知道i的类型究竟是整数还是字符串——我们也不知道,除非程序运行,用户输入了以后才知道。

总而言之,编译器在编译的时候必须确定一个变量的类型,因为它会把它和调用它的代码硬编码到可执行文件中去,所以它不得不确认这一点。

编译器不但会检查一个变量是否能调用某个方法,如上面所述的那样,还会检查某个对象能不能传给这个变量,比如

int i = 0;
object o = 1;
i = o; // error
o = i;


编译器只允许object类型接收int类型的变量(int是object的派生类型),但是决不允许int类型接收object类型的变量。因为object类型不但可以表示int,也可以表示别的类型,它们和int类型并不兼容。而一个object类型究竟表示什么具体的类型,我们有时候还是得等运行的时候才知道。编译器不敢乱猜。

我们来编写一个函数,比较两个数是否相等:

同学甲不假思索编写了如下代码:

static bool IsEqual(int a, int b)
{
return a == b;
}


同学乙马上反驳道,你怎么知道是整数?如果是浮点数呢?
甲同学说,这个好办,我再写一个就是了:

static bool IsIntEqual(int a, int b)
{
return a == b;
}
static bool IsFloatEqual(float a, float b)
{
return a == b;
}


但是马上意识到,这样写很呆,不过他马上就想到一个“好办法”:

static bool IsEqual(object a, object b)
{
return (a as IComparable).CompareTo(b) == 0;
}


使用object类型代替了具体的类型,这下不管传入什么,编译器都统统放行。

乙同学说,那好,我来调用下。

int i = 3;
float j = 3.0f;
Console.WriteLine(IsEqual(i, j));


他故意传入了一个整数和一个浮点数,反正编译器不管,都是object,可以编译。但是一运行,就出错了。

甲同学说,不能这么玩的,传入的类型必须是两个相同的类型。

说着,加上一个判断:

static bool IsEqual(object a, object b)
{
if (a.GetType() == b.GetType())
return (a as IComparable).CompareTo(b) == 0;
else
return false;
}


连类型都不一样,那就肯定不等,这下你挑不出毛病了吧。

乙同学不依不挠:

object i = new object();
object j = new object();
Console.WriteLine(IsEqual(i, j));


类型相同,都是object,看你怎么比。

果不其然,程序又挂掉了。

甲同学调试了下,发现object在运行时没办法转换为IComparable,所以才导致了错误,于是他还得修改代码,判断下传入的类型是否实现了IComparable……

现在我们采访下甲同学:

甲同学说,“为了让程序尽可能通用,我似乎应该使用抽象的类型,比如object,这样让调用者尽可能传各种类型过来都可以调用我的代码。

“但是,为了让程序可靠,我又不得不学着编译器那样,在运行的时候对传入的类型做前置的审查,以免调用者传入不合理的类型的对象搞破坏。如果我不想检查,还是用具体的类型(比如int、float)比较好,那样编译器就代替我的检查,不合理的参数在编译阶段就拦截下来,我可以省多少事。”

似乎这两点需求是矛盾的,那么鱼和熊掌可以兼得么?那就要使用泛型。我们看下,使用泛型我们可以怎么写:

static bool IsEqual<T>(T a, T b) where T : IComparable
{
return a.CompareTo(b) == 0;
}


我们定义了一个泛型参数T。a和b的参数类型都用T表示,这意味着编译器将会检查,a和b的类型必须相同。你想一个传int一个传float那编译器就会拦截下来了。Where后面的叫泛型约束,它保证T类型必须实现IComparable,这样我们也不用担心a和b在转换的过程中因为没有实现这一接口而在运行时出错了。所以我们也无需再写前置审查的条件了,因为编译器为我们审查了。

我们可以归纳下泛型在其中起的作用:

(1)参数使用同一个泛型参数表示它们的类型相同——甭管它们是什么类型,但是它们必须是一个类型,比如

void foo<T1, T2>(T1 a, T2 b, T2 c)
{

}


那就是说,b和c必须是一个类型,a是另一个类型。a可以和b、c的类型相同么?当然可以。只要T1和T2声明成相同的类型即可。a和bc类型可以相同可以不同,但是bc类型必须相同。

(2)我们可以约束某个泛型参数的基类或者实现了什么接口,比如

T foo<T>() where T : A
{
...
}


这里,T必须是A的派生类,当然也包括了A
这样,我们无需转换,就可以直接调用A中的字段或者方法。

我们还可以增加构造函数约束,这对于我们需要在函数中直接创建T类型的对象很有用:

T foo<T>() where T : A, new()
{
return new T();
}


在这里,因为我们约束了T可以包含一个无参数的构造函数,所以我们可以直接在代码中用new T()创建一个T的实例。如果T代表的类型没有这样的构造函数,编译器同样会在编译的时候检查出来,比如A这样定义:

class A
{
private A() { }
}
class Program
{
static void Main(string[] args)
{
foo<A>();
}

static T foo<T>() where T : A, new()
{
return new T();
}
}


因为A的构造函数被封闭,所以这段代码会给出一个编译错误。

除了函数可以使用泛型,类和委托也可以。

class A<T>
{
private T value;
public void foo(T a)
{
value = a;
}
}


我们给一个类加上泛型参数,那么我们可以在定义字段、属性和方法的时候用到它。此时foo方法可以直接使用T作为a的类型。而不需要在方法名后面加上<T>来定义这一参数了。

关于委托使用泛型,这里简单说下3个预置的类型:Func<>、Action<>和Predicate<T>。

它们可以使得你不必定义大部分的委托。比如你想定义一个包含2个参数,一个返回值的委托:int MyDelegate(int a, int b),你可以直接使用Func<int, int, int>。Func<T1, T2, … Tn>委托泛型的第1~n-1个参数分别表示委托参数的类型,而第n个参数表示返回值的类型。Func<T>表示没有参数,只有返回值,且返回值为T类型的委托。Action则表示方法,Action<T1, T2,…, Tn>表示具有T1~Tn,n个参数,且没有返回值的方法。void MyDelegate(int a, int b)可以表示为Action<int, int>。Action表示既没有参数,也没有返回值的方法。Predicate<>可以视作Func<>的特例,它表示返回值为bool类型的委托,因此Predicate<T1, T2, …, Tn>相当于Func<T1, T2, … Tn, bool>。注意,这三个预置的委托的n不可以无限大,在.NET 4.0中,最多有16个参数。不过我们很少有机会定义多于16个参数的函数或者方法,因此绝大多数情况下,它们够用了。

最后看个例子以结束本章:
class Program
{
static void Main(string[] args)
{
Func<int, int, int> add = new Func<int, int, int>(Add);
Console.WriteLine(add(1, 2));
}

static int Add(int a, int b) { return a + b; }
}


下一篇
...全文
1202 23 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
haihang55 2014-05-17
  • 打赏
  • 举报
回复
very good . Thanks
shluochen 2014-05-16
  • 打赏
  • 举报
回复
看下撒,很详细的样子
config_man 2014-05-16
  • 打赏
  • 举报
回复
引用 15 楼 a13786733193 的回复:
楼主您好!Func<int, int, int>没什么通用性,直接定义调用也行,还请指导
21楼中的Func写法有一点点的小问题,因为Func函数在使用时,一般需要获取返回值,所以最后一个参数是用来获取返回值的。这点要说明下。
config_man 2014-05-16
  • 打赏
  • 举报
回复
引用 15 楼 a13786733193 的回复:
楼主您好!Func<int, int, int>没什么通用性,直接定义调用也行,还请指导
你的问题包含2个: 1、没有通用性 2、委托的用途 关于通用性,请看如下解释: Func为系统内置的委托类型,参数为T,所以有通用性。你可以这么写: Func<int,string,A> func = new Func<int,string,A>(); func.Invoke(1,"hello",new A()); 关于委托的用途,请参考楼主的另一篇帖子: 第二章 从函数到委托
引用
http://bbs.csdn.net/topics/390784442
关于Func,要谈到另一个委托类型:Action 他们的主要区别为:Func有返回值,Aciton没有。 关于他们的具体解释,请移步到MSDN查阅。
joyhen 2014-05-14
  • 打赏
  • 举报
回复
鞭辟入里,顶
a13786733193 2014-05-13
  • 打赏
  • 举报
回复
楼主您好!Func<int, int, int>没什么通用性,直接定义调用也行,还请指导
a13786733193 2014-05-13
  • 打赏
  • 举报
回复
楼主您好!我想问下Func<int, int, int>这个有什么用?貌似直接定义调用就行了 ,没什么通用性,还请指导
猴头 2014-05-13
  • 打赏
  • 举报
回复
laoer_2002 2014-05-13
  • 打赏
  • 举报
回复
谢谢楼主分享,支持
iceMung 2014-05-13
  • 打赏
  • 举报
回复
辛苦辛苦
没花鹿 2014-05-13
  • 打赏
  • 举报
回复
精彩之极,感谢分享
layxbjl 2014-05-12
  • 打赏
  • 举报
回复
as1316293630 2014-05-12
  • 打赏
  • 举报
回复
好多 啊 眼晕
yqb_last 2014-05-12
  • 打赏
  • 举报
回复
谢谢
漫天雪飞 2014-05-12
  • 打赏
  • 举报
回复
894121007 2014-05-12
  • 打赏
  • 举报
回复
欢乐的小猪 2014-05-12
  • 打赏
  • 举报
回复
感谢分享。。。

111,088

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧