前言

  泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能。基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用。同时,它减少了泛型类及泛型方法中的转型,确保了类型安全。委托本身是一种引用类型,它保存的也是托管堆中对象的引用,只不过这个引用比较特殊,它是对方法的引用。事件本身也是委托,它是委托组,C#中提供了关键字event来对事件进行特别区分。一旦我们开始编写稍微复杂的C#代码,就肯定离不开泛型、委托和事件。本章将针对这三个方面进行说明。

  这里也有一篇之前我对泛型的简单理解篇 http://www.cnblogs.com/aehyok/p/3384637.html C# 泛型的简单理解(安全、集合、方法、约束、继承)

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

  建议32、总是优先考虑泛型

  建议33、避免在泛型类型中声明静态成员

  建议34、为泛型参数设定约束

建议32、总是优先考虑泛型

  泛型的优点是多方面的,无论是泛型类还是泛型方法都同时具备可重用性、类型安全和高效率等特性,这都是非泛型类和非泛型方法无法具备的。本建议将从可重用性、类型安全和高效率三个方面来进行剖析在实际的编码过程中为何总是应该优先考虑泛型。

一、可重用性,比如简单的设计一个集合类

    public class MyList
{
int[] items;
public int this[int i]
{
get { return items[i]; }
set { this.items[i] = value; }
} public int Count
{
get { return items.Length; }
} ////省略一些其他方法
}

该类型只支持整型,如果要让类型支持字符串,有一种方法是重新设计一个类。但是这两个类型的属性和方法都是非常接近的,如果有一种方法可以让类型接收一个通用的数据类型,这样就可以进行代码复用了,同时类型也只要一个就够了。泛型完成的就是这样的功能。

    public class MyList<T>
{
T[] items; public T this[int i]
{
get { return items[i]; }
set { this.items[i] = value; }
} public int Count
{
get { return items.Length; }
} ///省略其他方法
}

  可以把T理解为一个占位符,在C#泛型编译生成的IL代码中,T就是一个占位符的角色。在运行时,即使编译器(JIT)会用实际代码中输入的T类型来代替T,也就是说,在由JIT生成的本地代码中,已经使用了实际的数据类型。我们可以把MyList<int>和MyList<string>视作两个完全不同的类型,但是,这仅是对本地代码而言的,对于实际的C#代码,它仅仅拥有一个类型,那就是泛型类型MyList<T>。

  以上从代码重用性的角度论证了泛型的优点。继续从类型MyList<T>的角度论述,如果不用泛型实现代码重用,另一种方法是让MyList的编码从object的角度去设计。在C#的世界中,所有类型(包括值类型和引用类型)都是继承自object,如果要让MyList足够通用,就需要让MyList针对object编码,代码如下:

    public class MyList
{
object[] items;
public object this[int i]
{
get { return items[i]; }
set { this.items[i] = value; }
} public int Count
{
get { return items.Length; }
} ////省略一些其他方法
}

这会让以下代码编译通过

            MyList list = new MyList();
list[] = ;
list[] = "123";

由上面两行代码带来的问题就是非”类型安全性“。该问题实际在建议20 http://www.cnblogs.com/aehyok/p/3641896.html 中已经详细论述过了。让类型支持类型安全,可以让程序在编译期间就过滤掉部分Bug,同时也能让代码规避掉”转型为object类型“或“从object转型为实际类型”所带来的效率损耗。尤其是涉及的操作类型是值类型时,还会带来装箱和拆箱的性能损耗。

例如,上文代码中的

list[] = ;

就会带来一次装箱操作,因为它首先倍转型为object,继而存储到items这个object数组中去了。

  泛型为C#带来的是革命性的变化,FCL之后的很多功能都是借助泛型才得到了很好的实现,如LINQ。LINQ借助于泛型和扩展方法,有效地丰富了集合的查询功能,同时避免了代码爆炸并提升了操作的性能。我们在设计自己的类型时,应充分考虑到泛型的优点,让自己的类型成为泛型类。

建议33、避免在泛型类型中声明静态成员

在上一个建议中,已经解释了应该将MyList<int> 和MyList<string> 视作两个完全不同的类型,所以,不应将MyList<T>中的静态成员理解成为MyList<int>和MyList<string>共有的成员。

对于一个非泛型类型,以下的代码很好理解:

    public class MyList
{
public static int Count { get; set; } public MyList()
{
Count++;
}
}
class Program
{
static void Main(string[] args)
{
MyList myList1 = new MyList();
MyList mylist2 = new MyList();
Console.WriteLine(MyList.Count);
Console.ReadLine();
}
}

结果返回为2.

如果将MyList换成泛型类型,看看下面的代码会输出什么呢?

    public class MyList<T>
{
public static int Count { get; set; } public MyList()
{
Count++;
}
}
class Program
{
static void Main(string[] args)
{
MyList<int> myList1 = new MyList<int>();
MyList<int> mylist2 = new MyList<int>();
MyList<string> mylist3 = new MyList<string>();
Console.WriteLine(MyList<int>.Count);
Console.WriteLine(MyList<string>.Count);
Console.ReadLine();
}
}

代码输出为

实际上,随着你为T指定不同的数据类型,MyList<T>相应的也变成了不同的数据类型,在它们之间是不共享静态成员的。

不过,从上文我们也觉察到了,若T所指定的数据类型是一致的,那么两个泛型对象间还是可以共享静态成员的,如上文的myList1和myList2。但是,为了规避因此而引起的混淆,仍旧建议在实际的编码工作中,尽量避免声明泛型类型的静态成员。

上面举的例子是基于泛型类型的,非泛型类型中静态泛型方法看起来很接近该例子,但是应该始终这样来理解:

非泛型类型中的泛型方法并不会在运行时的本地代码中生成不同的类型。

    public class MyList
{
public static int Count { get; set; }
public static int Func<T>()
{
return Count++;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(MyList.Func<int>());
Console.WriteLine(MyList.Func<int>());
Console.WriteLine(MyList.Func<string>());
Console.ReadLine();
}
}

输出结果为

建议34、为泛型参数设定约束

”约束“这个词可能会引起歧义,有些人可能认为对泛型参数设定约束是限制参数的使用,实际情况正好相反。没有约束的泛型参数作用很有限,倒是”约束“让泛型参数具有了更多的行为和属性。

    public class Salary
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; } /// <summary>
/// 基本工资
/// </summary>
public int BaseSalary { get; set; } /// <summary>
/// 奖金
/// </summary>
public int Bouns { get; set; } } public class SalaryComputer
{
public int Compare<T>(T t1, T t2)
{
return ;
}
}

查看上面定义实体类可以发现,Compare<T>方法的参数t1或参数t2仅仅具有object的属性和行为,所以几乎不能在方法中对它们做任何的操作。但是,在加了约束之后,我们会发现参数t1或参数t2变成了一个有用的对象。由于为其指定了对应的类型,t1和t2现在就是一个Salary了,在方法的内部,它拥有了属性BaseSalary和Bonus,代码如下:

    public class SalaryComputer
{
public int Compare<T>(T t1, T t2) where T:Salary
{
if (t1.BaseSalary > t2.BaseSalary)
{
return ;
}
else if (t1.BaseSalary == t2.BaseSalary)
{
return ;
}
else
{
return -;
}
}
}

那么可以为泛型参数指定那些约束呢?

1、指定参数是值类型(除Nullable外),可以有如下形式:

        public void Method1<T>(T t) where T : struct
{ }

2、指定参数是引用类型,可以有如下形式:

        public void Method1<T>(T t) where T : class
{ } public void Method1<T>(T t) where T : Salary
{ }

注意object不能用来作为约束。

3、指定参数具有无参数的公共构造函数,可以有如下形式:

        public void Method2<T>(T t) where T : new()
{ }

注意CLR目前只支持无参构造方法约束。

4、指定参数必须是指定的基类、或者派生自指定的基类。

5、指定参数必须是指定的接口、或者实现指定的接口。

6、指定T提供的类型参数必须是为U提供的参数,或者派生自为U提供的参数。

    public class Sample<U>
{
public void Method1<T>(T t) where T : U
{ }
}

7、可以对同一类型的参数设置多个约束,并且约束自身可以是泛型类型。

在编程的过程中应该始终考虑为泛型参数设定约束,正像本建议开始的时候所说,约束使泛型成为一个实实在在的“对象”,让它具有了我们想要的行为和属性,而不仅仅是一个object。

英语小贴士

1、Where is the tourist information?——旅游咨询中心在那里?

2、Can you recommend a hotel which is not too expensive?——是否可建议一间较为廉价的旅馆?

3、Is there an airport bus to the city?——是否有机场巴士可到市区?

4、Is there a hotel which costs under 50 dollars a night?——是否有每晚花费在50美元以下的饭店?

5、Where is the bus stop(taxi stand)?——巴士站牌(出租车招呼站)在那里?

6、Could you recommend a hotel in the city center?——是否可建议一家位于市中心的旅馆?

7、Where can I get the limousine for Hilton Hotel?——我在何处可搭乘希尔顿饭店的接泊巴士?

8、I'd like to stay at a hotel near the station (beach).——我想要住在靠近车站(海滩)的饭店。

作者:aehyok

出处:http://www.cnblogs.com/aehyok/

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,那不妨点个推荐吧,谢谢支持:-O。

编写高质量代码改善C#程序的157个建议[优先考虑泛型、避免在泛型中声明静态成员、为泛型参数设定约束]的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

随机推荐

  1. [部署]CentOS安装apache

    环境 虚拟机:VMWare10.0.1 build-1379776 操作系统:CentOS7 64位 步骤 1.使用yum安装 yum install httpd httpd-devel 2.启动 a ...

  2. HADOOP namenode HA

    参考的文章:http://www.cnblogs.com/smartloli/p/4298430.html 当然,在操作的过程中,发现与上述文章中描述的还是有一些小小的区别. 配置好后,start-d ...

  3. 探索 OpenStack 之(11):cinder-api Service 启动过程分析 以及 WSGI / Paste deploy / Router 等介绍

    OpenStack 中的每一个提供 REST API Service 的组件,比如 cinder-api,nova-api 等,其实是一个 WSGI App,其主要功能是接受客户端发来的 HTTP R ...

  4. JVM相关问答

    大部分内容来源网络,整理一下,留个底. 问:堆和栈有什么区别? 答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的. 栈是跟随线程的,有线程就有 ...

  5. ZooKeeper系列1:ZooKeeper的配置

    问题导读:1.zookeeper有哪些配置文件?2.zookeeper最低配置需要哪些配置项?3.zookeeper高级配置需要配置哪些项? ZooKeeper 的功能特性通过 ZooKeeper 配 ...

  6. 『转载』使用TortoiseSVN客户端

    原文地址:https://www.sinacloud.com/doc/sae/tutorial/code-deploy.html#shi-yong-git-ke-hu-duan TortoiseSVN ...

  7. 在Android Studio中使用shareSDK进行社会化分享(图文教程)

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  8. [转] 国外程序员整理的 C++ 资源大全

    关于 C++ 框架.库和资源的一些汇总列表,由 fffaraz 发起和维护. 内容包括:标准库.Web应用框架.人工智能.数据库.图片处理.机器学习.日志.代码分析等. 标准库 C++标准库,包括了S ...

  9. 对window的认识

    首先要明确: 不管是全局的函数还是全局的变量,都是属于window的,例如: a = 12; //全局变量 alert(a) === alert(window.a) function show(){ ...

  10. 12Spring_AOP编程(AspectJ)_前置通知

    接下里的博客会一篇一篇的讲解每一个通知.其实AOP_AspectJ的编程与传统的AOP的编程的最大的区别就是写一个Aspect 支持多个Advice和多个PointCut .而且我们写AOP_Aspc ...