前言

C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法。
另外C#2.0还提出了可空类型,匿名方法和迭代器3个优美的特性。

1,泛型
1.1 泛型是什么
泛型的英文表述是"generic", 这个单词意为通用的。从字面意思可知,泛型代表的就是"通用类型",它可以代替任意的数据类型,使类型参数化,
从而达到之实现一个方法就可以操作多种数据类型的目的。泛型是将方法实现行为与方法操作的数据类型分离,实现了代码重用。

 class Program
{
static void Main(string[] args)
{
//用int作为实际参数来促使花泛型类型
List<int> intList = new List<int>();
//从int列表添加元素3
intList.Add(); //用string作为实际参数来初始化泛型类型
List<string> stringList = new List<string>();
//从string列表添加元素
stringList.Add("wanmg-meng");
}
}

在以上的代码中,List<T> 是.Net 类库中实现的泛型类型,T是泛型参数(可理解为形参), 如果想实例化一个泛型类型,必须传入实际的参数类型。

泛型除了可以实现代码重用外, 还提供了更好的性能和类型安全特性. 前面关于拆箱装箱讲过. 应用类型和值类型间存在着相互转换,转换的过程称为装箱和拆箱. 这对过程会引起一定的性能损失. 而泛型是避免性能损失的有效方法.

1.2全面解析泛型
在前面的泛型代码中, T就是类型参数. 无论调用类型方法还是初始化泛型实例, 都需要用真实类型来替换T. 可以将T理解为类型的一个占位符, 即告诉编译器, 在调用泛型时必须为其指定一个实际类型.
1.2.1
已构造泛型又可分为开放类型和密封类型. 其中, 开放类型是指包含类型参数的泛型,所有未绑定的泛型类型都属于开放类型; 而封闭类型则是指那些已经为每一个类型参数都传递了司机数据类型的泛型.

 //声明开放泛型类型
public class DictionaryStringKey<T> : Dictionary<string, T>
{ }
class Program
{
static void Main(string[] args)
{
//Dictionary<,> 是一个开放类型, 它有两个类型参数
Type t = typeof(Dictionary<,>);
//DictionaryStringKey<int> 是一个封闭类型
t = typeof(DictionaryStringKey<int>);
}
}

1.2.2
泛型中的静态字段和静态函数问题
静态数据类型是属于类型的. 对于静态之端来说, 如果某个MyClass类中定义了一个静态字段X, 则不管之后创建了多少个该类的实例,也不管从该类派生出多少个实例,
都只存在一个MyClass.x字段. 但泛型类型却并非如此, 每个封闭的泛型类型中都有仅属于他自己的静态数据.

 //泛型类型, 具有一个类型参数
public static class TypeWithStaticField<T>
{
//静态字段
public static string field;
//静态构造函数
public static void OutField()
{
Console.WriteLine(field + ":" + typeof(T).Name);
}
} //非泛型类
public static class NoGenericTypeWithStaticField
{
public static string field;
public static void OutField()
{
Console.WriteLine(field);
}
} class Program
{
static void Main(string[] args)
{
//使用不同类型实参来实例化泛型实例
TypeWithStaticField<int>.field = "一";
TypeWithStaticField<string>.field = "二";
TypeWithStaticField<Guid>.field = "三"; //对于非泛型类型, 此时field只会有一个值, 每次赋值都改变了原来的值
NoGenericTypeWithStaticField.field = "非泛型类静态字段一";
NoGenericTypeWithStaticField.field = "非泛型类静态字段二";
NoGenericTypeWithStaticField.field = "非泛型类静态字段三"; NoGenericTypeWithStaticField.OutField(); //证明每个封闭类型都有一个静态字段
TypeWithStaticField<int>.OutField();
TypeWithStaticField<string>.OutField();
TypeWithStaticField<Guid>.OutField();
Console.ReadKey();
}
}

运行结果图:


从图中可以看出每个封闭的泛型类型都有属于它自己的静态字段. 泛型暂时就写这么多, 以后遇到这方面的内容还会继续补充.

2,可空类型

2.1可空类型也是值类型, 但它是包含null值得值类型.

int? nullable = null;
解析: C# 肯定没有int?这个类型, 对于编译器而言,int?会被编译成Nullable<int>类型, 即可空类型. C# 2.0 提供和的可空类型是Nullable<int>和Nullable. (可控类型的定义是public struct Nullable<T> where T:struct, T只能为值类型)

int? value = 1 等价于==> Nullable<int> value = 1;

2.2 空合并操作符
空合并操作符即??操作符, 他会对左右两个操作数进行判断: 如果左边的数不为null,就返回左边的数; 如果左边的数位null, 就返回右边的数.
这个操作符可以用于可空类型, 也可用于引用类型,但是不能用于值类型. 因为??运算符会将其左边的数与null进行比较, 但除了可空类型外,其他的值类型是不能与null进行比较的.

可空类型的优点就是可以很方便地设置默认值,避免了通过if和else语句来进行判断, 从而简化代码函数,提高了代码的可读性:
int? nullHasValue = 1;
int x = nullHasValue ?? 12;// ??和三目运算符功能差不多, 类似于: x = nullHasValue.HasValue ? b.value : 12;

2.3 可空类型与一元或二元运算符一起使用时,只要有一个操作数为null,结果都为null;
int? d = null;
int? dd = d = 5;
Console.WriteLine(dd); //null

同理: 比较可空类型时,只要一个操作数为null,比较结果就为false。

2.4可空类型的装箱与拆箱
既然值类型存在着装箱和拆箱, 而可空类型属于值类型, 那么它自然也就存在装箱和拆箱. 当把一个可空类型赋给引用类型变量时, CLR会对可空类型对象处理.
CLR首先会检测可空类型是否为null. 如果为null, CLR将不会进行实际的装箱操作, 如果不为null,CLR则会从可空类型对象中获取值,并对该值进行装箱操作.

 //定义一个可控类型对象nullable
Nullable<int> nullable = ;
int? nullableWithoutValue = null; //获得可空对象的类型, 此时返回的是System.Int32, 而不是System.Nullable<System.Int32>, 这一点需要特别注意
nullable.GetType();// System.Int32 //对一个为null的类型调用方法时将出现异常, 所以一般引用类型调用方法前, 最好先检查下它是否为null
nullableWithoutValue.GetType(); //装箱操作
object obj = nullable;
obj.GetType();// System.Int32 //拆箱后变成非可空变量
int value = (int)obj; //拆箱后变成可空类型
nullable = (int?)obj;

前面说了 对于没有值得可空类型调用函数时会抛出空引用异常, 但是仍然可以访问HasValue属性.
原因在于,可空类型是包含null值得可空类型, 对于向可空类型赋值这项操作来说, null是一个有效的值类型.而向引用类型赋值null值则表示空引用
表示不指向托管对中的任何对象, 所以可以访问HasValue属性.

3. 匿名方法
匿名方法就是没有名字的方法. 因为没有名字, 匿名方法只能在函数定义的时候被调用, 在其他任何情况下都不能被调用.
前面讲到委托的时候讲到 委托是后续诸多特性的基础, 匿名方法和委托有着莫大的关系. 下面用代码来说明二者之间的关系. 首先回顾委托的使用方法.

 class Program
{
//定义投票委托
delegate void VoteDelegate(string name);
static void Main(string[] args)
{
//使用Vote方法来实例化委托对象
VoteDelegate voteDelegate = new VoteDelegate(new Friend().Vote);
//下面的方式为隐式实例化委托方式,它把方法直接赋给了委托对象
//VoteDelegate voteDelegate = new Friend().Vote; //通过调用委托来回调Vote()方法, 这是隐式调用方式
voteDelegate("BarryWang");
Console.ReadKey();
} public class Friend
{
//朋友的投票方法
public void Vote(string nickName)
{
Console.WriteLine("昵称为: {0}来办Wang Meng投票了", nickName);
}
}
}

委托是用来包装方法的类类型, 既然委托方法也是方法, 当然可以被委托类型包装了, 所以我们还可以用匿名方法的方式去实现前面的代码:

 class Program
{
//定义投票委托
delegate void VoteDelegate(string name);
static void Main(string[] args)
{
//使用Vote方法来实例化委托对象
VoteDelegate voteDelegate = delegate(string nickName)
{
Console.WriteLine("昵称为: {0}来办Wang Meng投票了", nickName);
}; //通过调用委托来回调Vote()方法, 这是隐式调用方式
voteDelegate("BarryWang");
Console.ReadKey();
}
}

从以上代码可以看出, 若使用了匿名方法, 就不再需要单独定义一个Vote方法了, 这减少了代码行数, 更有利于程序阅读. 
但是匿名方法也有缺点: 不能再其他地方被调用, 即不具有重复性. 所以如果委托包装的方法相对简单, 并且该方法在其他地方的调用频率较低, 我们就可以考虑用匿名方法来实例化委托对象了.

4, 迭代器
迭代器记录了集合中的某个位置, 它使程序只能向前移动. 
在C#1.0中, 一个类中要想使用foreach关键字进行遍历, 它必须实现IEnumerable或者IEnumerable<T>接口. 
然而在C#2.0中, 微软提供了yield关键字来简化迭代器的实现, 这使得自定义迭代器变得容易了很多.

4.1,首先我们来看看IEnumerable、IEnumerator的区别来帮助我们理解迭代器:
先来看一下IEnumerable接口,其实看过这个接口之后,发现它其实是非常的简单,只包含一个方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象,如下面代码所示:

 public interface IEnumerable
{
// Summary:
// Returns an enumerator that iterates through a collection.
//
// Returns:
// An System.Collections.IEnumerator object that can be used to iterate through
// the collection.
[DispId(-)]
IEnumerator GetEnumerator();
}

那么再来看看IEnumerator中的实现方法:
这里的IEnumerator对象,其实就是另外一个接口,这个接口对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接口又定义了什么东西。

 // Summary:
// Supports a simple iteration over a nongeneric collection.
[ComVisible(true)]
[Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerator
{
// Summary:
// Gets the current element in the collection.
//
// Returns:
// The current element in the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The enumerator is positioned before the first element of the collection or
// after the last element.
object Current { get; } // Summary:
// Advances the enumerator to the next element of the collection.
//
// Returns:
// true if the enumerator was successfully advanced to the next element; false
// if the enumerator has passed the end of the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// Summary:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}

那么我们再来看一个真实的例子:

 public class Person
{
public string Name { get; set; }
public int Age { get; set; }
} public class People : IEnumerable
{
Person[] personList = new Person[];
public People()
{
personList[] = new Person() { Name = "aehyok", Age = };
personList[] = new Person() { Name = "Kris", Age = };
personList[] = new Person() { Name = "Leo", Age = };
personList[] = new Person() { Name = "Niki", Age = };
} public IEnumerator GetEnumerator()
{
return this.personList.GetEnumerator();
}
} class Program
{
static void Main(string[] args)
{
People p = new People(); //第一种遍历Person的方式
foreach (Person person in p)
{
Console.WriteLine("Name {0} : Age {1}", person.Name, person.Age);
} //第二种遍历方式
IEnumerator i = p.GetEnumerator();
while (i.MoveNext())
{
Person person = (Person)i.Current;
Console.WriteLine("Name {0} : Age {1}", person.Name, person.Age);
} Console.ReadKey();
}
}

从上面我们知道IEnumerator接口定义了一个Current属性,MoveNext和Reset两个方法,这是多么的简约。既然IEnumerator对象是一个访问器。那至少应该有一个Current属性,来获取当前集合中的项吧。MoveNext方法只是将游标的内部位置向前移动(就是移到一下个元素而已),要想进行循环遍历,不向前移动一下怎么行呢?

通过注释也可以明确的发现他们的用处。

4.2

 //使用yield自定义实现迭代器
class Program
{
static void Main(string[] args)
{
//创建一个对象
Friends friendCollection = new Friends();
//Friends实现了IEnumerable,所以可以使用foreach语句进行遍历
foreach (Friend f in friendCollection)
{
Console.WriteLine(f.Name);
}
Console.ReadKey();
}
} //朋友类
public class Friend
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Friend(string name)
{
this.name = name;
}
} //朋友集合
public class Friends : IEnumerable
{
private Friend[] friendArray; public Friends()
{
friendArray = new Friend[]
{
new Friend("张三"),
new Friend("李四"),
new Friend("王五")
};
} //索引器
public Friend this[int index]
{
get { return friendArray[index]; }
} public int Count
{
get { return friendArray.Length; }
} //C#2.0简化了迭代器的实现
public IEnumerator GetEnumerator()
{
for (int i = ; i < friendArray.Length; i++)
{
//在C#2.0中, 只需要使用下面的语句就可以实现一个迭代器
yield return friendArray[i]; //这里使用yield 简化了IEnumerator GetEnumerator中的Current() MoveNext() Reset() 方法的实现
}
}
}

4.3迭代器的执行过程图解

使用yield自定义迭代器
直接看code的实现形式吧:

C#学习笔记三: C#2.0泛型 可控类型 匿名方法和迭代器的更多相关文章

  1. [读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器

    前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...

  2. Dynamic CRM 2013学习笔记(十七)JS读写各种类型字段方法及技巧

    我们经常要对表单里各种类型的字段进行读取或赋值,下面列出各种类型的读写方法及注意事项: 1. lookup 类型 清空值 var state = Xrm.Page.getAttribute(" ...

  3. 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记

    回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...

  4. Oracle学习笔记三 SQL命令

    SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)  

  5. [Firefly引擎][学习笔记三][已完结]所需模块封装

    原地址:http://www.9miao.com/question-15-54671.html 学习笔记一传送门学习笔记二传送门 学习笔记三导读:        笔记三主要就是各个模块的封装了,这里贴 ...

  6. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  7. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  8. Typescript 学习笔记三:函数

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  9. muduo网络库学习笔记(三)TimerQueue定时器队列

    目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...

随机推荐

  1. php部分--数组(包含指针思想遍历数组);

    1.创建并输出数组 (1)相同数据类型的数组$attr=array(1,2,3,4,5); print_r($attr); echo "<br>"; $sttr1=ar ...

  2. js部分---函数与递归;

    function (){}//匿名函数 1.function hanshu () { alert("这是我第一个函数"); } hanshu();//调用函数 2.//有参数的函数 ...

  3. CString用法总结

    概述:CString是MFC中提供的用于处理字符串的类,是一种很有用的数据类型. 它很大程度上简化了MFC中的许多操作,使得MFC在做字符串操作时方便了很多. 不管怎样,使用CString有很多的特殊 ...

  4. URAL 2034 Caravans(变态最短路)

    Caravans Time limit: 1.0 secondMemory limit: 64 MB Student Ilya often skips his classes at the unive ...

  5. 配置DNS

    1.将DNS解析服务器添加到:/etc/resolv.conf 2.打开DNS相关配置:sed -i 's/^hosts:[ \t]*files[ \t]*$/& dns/' /etc/nss ...

  6. c++时间处理

    struct tm;这是一个结构体,包括了时间的各个属性年月日,时分秒 time(time_t * t);获取从1900年到现在经过的毫秒数,或者也可以这么用time_t t=time(NULL); ...

  7. mysql提权笔记

    最近小菜遇到mysql提权,总是会搞错,就记记笔记吧!以后方便用 先说手工吧! mysql<5.0,导出路径随意:5.0<=mysql<5.1,则需要导出至目标服务器的系统目录(如: ...

  8. IE下必须点击一下页面空白的地方才可以激活onchange事件

    checkbox在IE下必须点击一下页面空白的地方才可以激活onchange事件. 解决办法把onchange换成onclick

  9. com.sun.crypto.provider.SunJCE

    Could not instantiate bean class [com.lz.monitor.alert.service.ServiceImp]: Constructor threw except ...

  10. 019. Asp.net将SqlServer中的数据保存到xls/txt中

    using System; using System.Collections; using System.Configuration; using System.Data; using System. ...