C#的变迁史 - C# 2.0篇
在此重申一下,本文仅代表个人观点,如有不妥之处,还请自己辨别。
第一代的值类型装箱与拆箱的效率极其低下,特别是在集合中的表现,所以第二代C#重点解决了装箱的问题,加入了泛型。
1. 泛型 - 珍惜生命,远离装箱
集合作为通用的容器,为了兼容各种类型,不得已使用根类Object作为成员类型,这在C#1.0中带来了很大的装箱拆箱问题。为了C#光明的前途,这个问题必须要解决,而且要解决好。
C++模板是一个有用的启迪,虽然C++模板的运行机制不一样,但是思路确实是正确的。
带有形参的类型,也就是C#中的泛型,作为一种方案,解决了装箱拆箱,类型安全,重用集合的功能,防止具有相似功能的类泛滥等问题。泛型最大的战场就是在集合中,以List<T>,Queue<T>,Stack<T>等泛型版本的集合基本取代了第一代中非泛型版本集合的使用场合。当然除了在集合中,泛型在其他的地方也有广泛的用途,因为程序员都是懒的,重用和应对变化是计算机编程技术向前发展最根本的动力。
为了达到类型安全(比如调用的方法要存在),就必须有约定。本着早发现,早解决的思路,在编译阶段能发现的问题最好还是在编译阶段就发现,所以泛型就有了约束条件。泛型的约束常见的是下面几种:
a. 构造函数约束(使用new关键字),这个约束要求实例化泛型参数的时候要求传入的类必须有公开的无参构造函数。
b. 值类型约束(使用struct关键字),这个约束要求实例化泛型参数的类型必须是值类型。
c. 引用类型约束(使用class关键字),这个约束要求实例化泛型参数的类型必须是引用类型。
d. 继承关系约束(使用具体制定的类或接口),这个约束要求实例化泛型参数的类型必须是指定类型或是其子类。
当然了,泛型参数的约束是可以同时存在多个的,参看下面的例子:
public class Employee
{ }
class MyList<T, V> where T: Employee, IComparable<T>
where V: new()
{
}
如果不指定约束条件,那么默认的约束条件是Object,这个就不多讲了。
当使用泛型方法的时候,需要注意,在同一个对象中,泛型版本与非泛型版本的方法如果编译时能明确关联到不同的定义是构成重载的。例如:
public void Function1<T>(T a);
public void Function1<U>(U a);
这样是不能构成泛型方法的重载。因为编译器无法确定泛型类型T和U是否不同,也就无法确定这两个方法是否不同 public void Function1<T>(int x);
public void Function1(int x);
这样可以构成重载 public void Function1<T>(T t) where T:A;
public void Function1<T>(T t) where T:B;
这样不能构成泛型方法的重载。因为编译器无法确定约束条件中的A和B是否不同,也就无法确定这两个方法是否不同
使用泛型就简单了,直接把类型塞给形参,然后当普通的类型使用就可以了。例如:
List<int> ages = new List<int>();
ages.Add();
ages.Add();
ages.Remove();
2. 匿名函数delegate
在C# 2.0中,终于实例化一个delegate不再需要使用通用的new方式了。使用delegate关键字就可以直接去实例化一个delegate。这种没有名字的函数就是匿名函数。这个不知道是不是语法糖的玩意儿使用起来确实比先定义一个函数,然后new实例的方式要方便。不过最方便的使用方式将在下一版中将会到来。
delegate void TestDelegate(string s);
static void M(string s)
{
Console.WriteLine(s);
} //C# 1.0的方式
TestDelegate testDelA = new TestDelegate(M); //C# 2.0 匿名方法
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };
谈到匿名函数,不得不说说闭包的概念。
如果把函数的工作范围比作一个监狱的话,函数内定义的变量就都是监狱中的囚犯,它们只能在这个范围内工作。一旦方法调用结束了,CLR就要回收线程堆栈空间,恢复函数调用前的现场;这些在函数中定义的变量就全部被销毁或者待销毁。但是有一种情况是不一样的,那就是某个变量的工作范围被人为的延长了,通俗的讲就像是某囚犯越狱了,它的工作范围超过了划定的监狱范围,这个时候它的生命周期就延长了。
闭包就是使用函数作为手段延长外层函数中定义的变量的作用域和生命周期的现象,作为手段的这个函数就是闭包函数。看一个例子:
class Program
{
static void Main(string[] args)
{
List<Action> actions = getActions();
foreach (var item in actions)
{
item.Invoke();
}
} static List<Action> getActions()
{
List<Action> actions = new List<Action>(); for (int i = ; i < ; i++)
{
Action item = delegate() { Console.WriteLine(i); };
actions.Add(item);
} return actions;
}
}
你可以试试运行这个例子,结果和你预想的一致吗?这个例子会输出5个5,而不是0到4,出现这个现象的原因就是闭包。getActions函数中的变量i被匿名函数引用了,它在getActions调用结束后还会一直存活到匿名函数执行结束。但是匿名函数是后面才调用的,执行它们的时候,i早就循环完毕,值是5,所以最终所有的匿名函数执行结果都是输出5,这是由闭包现象导致的一个bug。
要想修复这个由闭包导致的问题,方法基本上是破坏闭包引用,方式多种多样,下面是简单的利用值类型的深拷贝实现目的。
第一个方法:让闭包引用不再指向同一个变量
for (int i = ; i < ; i++)
{
int j = i;
Action item = delegate() { Console.WriteLine(j); };
actions.Add(item);
}
第二个方法:包上一层函数来构造新的作用域
static List<Action> getActions()
{
List<Action> actions = new List<Action>(); for (int i = ; i < ; i++)
{
Action item = ActionMethod(i);
actions.Add(item);
} return actions;
} static Action ActionMethod(int p)
{
return delegate()
{
Console.WriteLine(p);
};
}
闭包现象提醒我们使用匿名函数和3.0中的Lambda表达式时都要时刻注意变量的来源。
3. 迭代器
在C# 1.0中,集合实现迭代器模式是需要实现IEnumerable的,这个大家还记得吧,这个接口的核心就是GetEnumerator方法。实现这个接口主要是为了得到Enumerator对象,然后通过其提供的方法遍历集合(主要是Current属性和MoveNext方法)。自己去实现这些还是比较麻烦的,先需要定义一个Enumerator对象,然后在自定义的集合对象中还需要实现IEnumerable接口返回定义的Enumerator对象,于是一个新的语法糖就出现了: yield关键字。
在C# 2.0中,只需要在自定义的集合对象中还需要实现IEnumerable接口返回一个Enumerator对象就行了,这个创建Enumerator对象的工作就由编译器自己完成了。看一个简单的小例子:
public class Stack<T>:IEnumerable<T>
{
T[] items;
int count;
public void Push(T data){...}
public T Pop(){...}
public IEnumerator<T> GetEnumerator()
{
for(int i=count-;i>=;--i)
{
yield return items[i];
}
}
}
使用yield return创建一个Enumerator对象是不是很方便?编译器遇到yield return会创建一个Enumerator对象并自动维护这个对象。
当然了,多数时候foreach必要遍历集合中的每一个元素,这个时候使用yield return配合for循环枚举每个元素就可以了,但是有时候只需要返回满足条件的部分元素,这个时候就要结合yield break中断枚举了,看一下:
//使用yield break中断迭代:
for(int i=count-;i>=;--i)
{
yield return items[i]; if(items[i]>)
{
yield break;
}
}
4. 可空类型
这个特性我觉得又是把值类型设计成引用类型Object类子类后,微软生产的怪语法。空值是引用类型的默认值,0值是值类型的默认值。那么在某些场合,比如从数据库中的记录取到内存中以后,没有值代表的是空值,但是字段的类型却是值类型,怎么搞呢?于是整出了可空类型。当然了,这个问题可以通过在设计表的时候给字段设计一个默认值来解决,但是有的时候某些字段的设置默认值是没有意义的,比如年龄,0有意义吗?
可空类型的概念很简单,没什么可说的,不过一个相似的语法却让我感到很舒服:那就是"??"操作符。这是一个二元操作符,如果第一个操作数是空值,则执行第二个操作数代表的操作,并返回其结果。例如:
static void Main(string[] args)
{
int? age = null;
age = age ?? ;
Console.WriteLine(age);
}
5. 部分类与部分方法
这一特性还是比较好用的,终于不用把所有的内容挤到一起了,终于可以申明和实现相分离了,虽然好像以前也可以做到,但是现在这项权利也下放给人民群众了。partial关键字带来了这一切,也带来了一定的扩展性。这个特性也比较简单,就是使用partial关键字。编译的时候,这些文件中定义的部分类会被合并。部分方法是3.0的特性,不过没什么新意,就放到2.0一起说吧。
使用这个特性的时候需要注意:
a. 部分方法只能在部分类中定义。
b. 部分类和部分方法的签名应该是一致的。
c. partial用在类型上的时候只能出现在紧靠关键字 class、struct 或 interface 前面的位置。
d. public等访问修饰符必须出现在partial前面。
f. partial定义的东西必须是在同一程序集和模块中。
看一个简单的例子:
// File1.cs
namespace PC
{
partial class A
{
int num = ;
void MethodA() { }
partial void MethodC();
}
} // File2.cs
namespace PC
{
partial class A
{
void MethodB() { }
partial void MethodC() { }
}
}
这里需要注意一下MethodC的申明和定义是分开的就可以了。
还有一点,很多人认为partial破坏了类的封装性,实际上谈不上。因为一个类能分部,就说明类的设计者认为是需要保留这个扩展性的,所以后面的人才可以给这个类添加一些新的东西。
6. 静态类
这个特性也是比较符合实际情况的,很多情况下,某些对象只需要实例化一次,然后到处使用,单件模式是可以实现这个目的,现在静态类也是一个新的选择。
静态类只能含有静态成员,所以构造函数也是静态的,既然是静态的,那么它与继承就没什么关系了。静态类从首次调用的时候创建,一直到程序结束时销毁。
简单看一个小例子:
public static class A
{
static string message = "Message";
static A()
{
Console.WriteLine("Initialize!");
}
public static void M()
{
Console.WriteLine(message);
}
}
不过据经验讲,有没有静态构造函数对静态类的构造时间是有影响的,一个是出现在首次使用对象成员的时候,一个是程序集加载的时候,不过分清这个实在没什么意义,有兴趣的同学自己研究吧。
C#2.0的新特性绝不止这几个,但是对程序猿们影响比较大的都在这了,更多的就参看微软的MSDN吧。
C#的变迁史 - C# 2.0篇的更多相关文章
- C#的变迁史 - C# 4.0篇
C# 4.0 (.NET 4.0, VS2010) 第四代C#借鉴了动态语言的特性,搞出了动态语言运行时,真的是全面向“高大上”靠齐啊. 1. DLR动态语言运行时 C#作为静态语言,它需要编译以后运 ...
- C#的变迁史 - C# 3.0篇
C# 3.0 (.NET 3.5, VS2008) 第三代C#在语法元素基本完备的基础上提供了全新的开发工具和集合数据查询方式,极大的方便了开发. 1. WPF,WCF,WF 这3个工程类型奠定了新一 ...
- C#的变迁史 - C# 1.0篇
C#与.NET平台诞生已有10数年了,在每次重大的版本升级中,微软都为这门年轻的语言添加了许多实用的特性,下面我们就来看看每个版本都有些什么.老实说,分清这些并没什么太大的实际意义,但是很多老资格的. ...
- C#的变迁史 - C# 4.0 之多线程篇
在.NET 4.0中,并行计算与多线程得到了一定程度的加强,这主要体现在并行对象Parallel,多线程Task,与PLinq.这里对这些相关的特性一起总结一下. 使用Thread方式的线程无疑是比较 ...
- C#的变迁史 - C# 4.0 之线程安全集合篇
作为多线程和并行计算不得不考虑的问题就是临界资源的访问问题,解决临界资源的访问通常是加锁或者是使用信号量,这个大家应该很熟悉了. 而集合作为一种重要的临界资源,通用性更广,为了让大家更安全的使用它们, ...
- C#的变迁史 - C# 5.0 之调用信息增强篇
Caller Information CallerInformation是一个简单的新特性,包括三个新引入的Attribute,使用它们可以用来获取方法调用者的信息, 这三个Attribute在Sys ...
- C#的变迁史 - C# 5.0 之并行编程总结篇
C# 5.0 搭载于.NET 4.5和VS2012之上. 同步操作既简单又方便,我们平时都用它.但是对于某些情况,使用同步代码会严重影响程序的可响应性,通常来说就是影响程序性能.这些情况下,我们通常是 ...
- C#的变迁史 - C# 4.0 之并行处理篇
前面看完了Task对象,这里再看一下另一个息息相关的对象Parallel. Parallel对象 Parallel对象封装了能够利用多核并行执行的多线程操作,其内部使用Task来分装多线程的任务并试图 ...
- C#的变迁史 - C# 5.0 之其他增强篇
1. 内置zip压缩与解压 Zip是最为常用的文件压缩格式之一,也被几乎所有操作系统支持.在之前,使用程序去进行zip压缩和解压要靠第三方组件去支持,这一点在.NET4.5中已有所改观,Zip压缩和解 ...
随机推荐
- 浅析Windows安全相关的一些概念
Session 我们平常所说的Session是指一次终端登录, 这里的终端登录是指要有自己的显示器和鼠标键盘等, 它包括本地登录和远程登录.在XP时代每次终端登录才会创建一个Session,但是在Vi ...
- [专业名词·硬件] 2、DC\DC、LDO电源稳压基本常识(包含基本原理、高效率模块设计、常见问题、基于nRF51822电源管理模块分析等)·长文
综述先看这里 第一节的1.1简单介绍了DC/DC是什么: 第二节是关于DC/DC的常见的疑问答疑,非常实用: 第三节是针对nRF51822这款芯片电源管理部分的DC/DC.LDO.1.8的详细分析,对 ...
- Git 远程仓库搭建
大名鼎鼎的git就不多做介绍了,总之.我们使用git来作为项目的一个版本控制工具,多人开发的项目的时候会轻松很多. 安装git whthomas@whthomas:~/workplace/gitOne ...
- git rm–r folder fatal:pathspec "" did not match any files
问题描述: 某年某月某日,在查看git库的时候,发现文件的分布和文件夹的名字是极其不合理的,所以移动和重命名了某些文件. 在删除(git rm –r folder)一个空文件夹的时候,出现错误:fat ...
- 谈谈设计模式~原型模式(Prototype)
返回目录 原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例(clone),而不是新建(new)实例.被复制的实例就是我们所称的“原型”,这个原型是可定制的. 原型模式 ...
- 知方可补不足~sqlserver中的几把锁~续
回到目录 之前写过相关的文章,对脏读,不可重复读,幻读都做了相当的研究,而今天在程序中又出现了这个问题,即当一条数据被update时,另一个线程同时发起了读的操作,这对于序列化级别的事务是不被允许的, ...
- Java程序员的日常 —— Java类加载中的顺序
之前说过Java中类的加载顺序,这次看完继承部分,就结合继承再来说说类的加载顺序. 继承的加载顺序 由于static块会在首次加载类的时候执行,因此下面的例子就是用static块来测试类的加载顺序. ...
- Atiti qq空间破解(3)------------gui图形化通用cli执行器atiuse
Atiti qq空间破解(3)------------gui图形化通用cli执行器atiuse 结构:::命令行+以及反馈log框1 cli_guiUI/index.htm1 /AtiPlatf_c ...
- .NET实现Office Excel自定义公式 广泛应用于报表与数据分析
在管理软件开发的功能点中,有相当一部分功能是与Excel做数据交互,产生Excel 数据报表.如果Excel报表的数据计算方法很有规律可循,则可以通过自定义公式来解决.比如常见的资产负债表,利润表,取 ...
- 图(C描述)
一.概念 图是由顶点的非空有限集合V(由N>0个顶点组成)与边的集合E(顶点之间的关系)构成.边没有方向的图成为无向图,反之为有向图 无向图: