[读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器
前言
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自定义迭代器
直接看code的实现形式吧:
//使用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迭代器的执行过程图解
PS: 这两天比较闲 便更新的比较频繁. 写完这个系列也等于把这本书又重新读了一遍, 仍有不少的收获. 勉励自己多读书, 多记录, 加油! 2016/01/20
[读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器的更多相关文章
- C#学习笔记三: C#2.0泛型 可控类型 匿名方法和迭代器
前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...
- [读书笔记]C#学习笔记一: .Net Framwork
前言: 一次偶然的机会 在园子里看到@Learning hard 出版的一本书: <<C#学习笔记>>, 然后买来 一直到现在读完, 感觉很不错, 适合入门, 书中内容是从C ...
- [读书笔记]C#学习笔记三: C#类型详解..
前言 这次分享的主要内容有五个, 分别是值类型和引用类型, 装箱与拆箱,常量与变量,运算符重载,static字段和static构造函数. 后期的分享会针对于C#2.0 3.0 4.0 等新特性进行. ...
- C#学习笔记(六):可空类型、匿名方法和迭代器
可空类型 为啥要引入可空类型? 在数据库中,字段是可以为null值的,那么在C#中为了方便的操作数据库的值,微软引入了可空类型. 声明可空类型 我们可以使用两种方法声明一个可空类型: Nullable ...
- [读书笔记]C#学习笔记八:StringBuilder与String详解及参数传递问题剖析
前言 上次在公司开会时有同事分享windebug的知识, 拿的是string字符串Concat拼接 然后用while(true){}死循环的Demo来讲解.其中有提及string操作大量字符串效率低下 ...
- Asp.net MVC4高级编程学习笔记-模型学习第四课基架与模型绑定20171027
MVC模型 一.构建基架. MVC中的基架可以为应用程序提供CURD各种功能生成所需要的样板代码.在添加控制器的时候可以选择相应的模板以及实体对象来生成相应的模板代码. 首先定义一个模型类如下所示: ...
- [读书笔记]C#学习笔记二: 委托和事件的用法及不同.
前言: C#委托是什么 c#中的委托可以理解为函数的一个包装, 它使得C#中的函数可以作为参数来被传递, 这在作用上相当于C++中的函数指针. C++用函数指针获取函数的入口地址, 然后通过这个指针 ...
- [读书笔记]C#学习笔记七: C#4.0中微小改动-可选参数,泛型的可变性
前言 下面就开始总结C#4.0的一些变化了, 也是这本书中最后的一点内容了, 这一部分终于要更新完了. 同时感觉再来读第二遍也有不一样的收获. 今天很嗨的是武汉下雪了,明天周六,一切都是这么美好.哈哈 ...
- [读书笔记]C#学习笔记六: C#3.0Lambda表达式及Linq解析
前言 最早使用到Lambda表达式是因为一个需求:如果一个数组是:int[] s = new int[]{1,3,5,9,14,16,22};例如只想要这个数组中小于15的元素然后重新组装成一个数组或 ...
随机推荐
- 安卓中adapter的应用
个人菜鸟总结,期待大神指点,悉心倾看! Adapter是ListView界面与数据之间的桥梁,Adapter提供对数据的访问,也负责为每一项数据产生一个对应的View(白话通俗点:先写adapter为 ...
- stm32 MDK5软件仿真之查看io口输出
软件MDK5 stm32的pack 打开MDK,添加工程 一.首先找到Project的Options选项,里面的Debug选为Use Simulator,也就是选择软件仿真. 然后再Logic ...
- 获取datagrid选择行
var rows = $('#dg').datagrid('getChecked'); var ids = ''; for (var i = 0; i < rows.length; i ...
- js 获取时间差
写这片博客 ,下面代码虽然简单,但却很实用...默默留下来... var minute = 1000 * 60;var hour = minute * 60;var day = hour * 24;v ...
- easyUI学习1
panel组件: <div id="p" class="easyui-panel" title="My Panel" style=&q ...
- noip2010-t2
题目大意:小明过生日的时候,爸爸送给他一副乌龟棋当作礼物.乌龟棋的棋盘是一行 N个格子,每个格子上一个分数(非负整数).棋盘第 1 格是唯一 的起点,第 N格是终点,游戏要求玩家控制一个乌龟棋子从起点 ...
- C/C++字符串函数之复制函数
突然发现对字符串函数缺乏系统的了解,所以花了一点时间专门整理下,在此记录之,以方便自己及有需要的人使用. C/C++字符串函数的头文件:string.h 复制函数主要有4个,如下: 1.char * ...
- CCF 201612-2 火车购票 (暴力)
问题描述 请实现一个铁路购票系统的简单座位分配算法,来处理一节车厢的座位分配. 假设一节车厢有20排.每一排5个座位.为方便起见,我们用1到100来给所有的座位编号,第一排是1到5号,第二排是6到10 ...
- Java里的if else语句例子
import java.util.Scanner;public class if_else2{ public static void main(String[] args) { Scanner s=n ...
- C# async await 学习笔记2
C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html) 提到了ThreadId是一样的,突然想到在WinForm中,非UI线程 ...