泛型是 C# 2 众多特性的其中之一,C# 2 还提出了可空类型、匿名方法和迭代器三个重要特性
可空类型
当你把数据库表映射为 C# 中的对象时会发现,DateTime
类型在 C# 语言中是不能为 null 的。为了完成映射,开发人员便有了这样的需求——值类型能不能是可空类型呢?
简介
可空类型也是值类型,但是它包含 Null 值的值类型
代码语言:javascript复制int?o nullable = null;
在以上代码中,int? 就是可空的 int 类型。修饰符只是 C# 提供的一个语法糖,所谓语法糖,就是 C# 提供的一种方便的表现行是
C# 中肯定没有 int? 这个类型,对于编译器而言,int? 会被编译成Nullable<int>
类型,即可空类型
public struct Nullable<T> where T : struct
{
...
}
下面演示一下使用方法
代码语言:javascript复制static void Main(string[] args)
{
Nullable<int> a = 1;
int? b = 2;
Console.WriteLine(a);
Console.WriteLine(b);
Console.ReadKey();
}
空合并操作符
空合并操作符即??
操作符,它会对左右两个操作数进行判断:如果左边的数不为 null,就返回左边的数;如果左边的数为 null,就返回右边的数。这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型。因为??
运算符会将其左边的数与 null 进行比较,但除了可空类型外,其他的值类型都是不能与 null 类型进行比较的,所以??
运算符不能用于值类型
static void Main(string[] args)
{
int? b = null;
Console.WriteLine(b ?? 2); // 2
Console.ReadKey();
}
从以上代码可以看出,使用??
运算符可以很方便地设置默认值,避免了通过 if 和 else 语句来进行判断,从而简化了代码行数,提高了代码的可读性
可空类型的装箱和拆箱操作
既然值类型存在装箱和拆箱的过程,而可空类型属于值类型,那么自然也就存在装箱和拆箱操作了
当把一个可空类型赋给引用变量时,CRL
会对可空类型(Nullable<T>
)对象进行装箱处理。CLR
会首先检测可空类型是否为null。如果为 null,CLR
将不会进行实际的装箱操作(因为 null 可以直接赋给一个引用类型变量);如果不为 null,CLR
则从可空类型对象中获取值,并对该值进行装箱(即值类型的装箱过程)
当把一个已装箱的值类型赋给可空类型变量时,CLR
会对已装箱的值类型进行拆箱处理。如果已装箱值类型的引用为 null,则CLR
会把可空类型也设为 null
为了更好地理解可空类型的装箱和拆箱过程,请看下面这个示例
代码语言:javascript复制static void Main(string[] args)
{
Nullable<int> nullable = 5;
// 装箱
object obj = nullable;
// 拆箱
nullable = (int?)obj;
Console.ReadKey();
}
匿名方法
什么是匿名方法
匿名方法,顾名思义,就是没有名字的方法。因为没有名字,匿名方法只能再函数定义的时候被调用,再其他任何情况下都不能被调用。对于编译器来说,匿名方法不是没有名字的,编译器在编译匿名方法的时候会为其生成一个方法名
前面说到,委托是后续诸多特性的基础,匿名方法和委托有着莫大的关系。下面通过例子来对二者的关系进行说明,首先回顾委托的使用方法
代码语言:javascript复制class Program
{
delegate void Test();
static void Main(string[] args)
{
Test test = new Test(Speak);
test();
Console.ReadKey();
}
public static void Speak()
{
Console.WriteLine("123");
}
}
委托是用来包装方法的类类型,既然匿名方法也是方法,当然也可以被委托包装了
代码语言:javascript复制class Program
{
delegate void Test();
static void Main(string[] args)
{
Test test = () => Console.WriteLine("123");
test();
Console.ReadKey();
}
}
以上代码可以看出,若使用了匿名方法,就不再需要单独定义一个 Speak 方法了,减少了代码行数,更有利于程序阅读,我们也不会再被过多的回调方法弄糊涂了
那是不是所有的委托对象都该有匿名方法来实例化呢?当然不是,匿名方法也有缺点——不能在其他地方被调用,即不具有复用性。而且,匿名方法会自动形成”闭包“
迭代器
迭代器简介
迭代器记录了某个集合中的某个位置,它使程序只能向前移动。C# 1.0 使用foreach
语句来实现访问迭代器的内置支持,foreach
使便利集合变得更加容易,它比 for 语句更方便,也更容易理解,foreach
被编译器编译后,会调用GetEnumerator
来返回一个迭代器,也就是一个集合中的初始位置
C# 1.0 中如何实现迭代器
在 C# 1.0 中,一个类型要想使用foreach
关键字进修班i案例,它必须要实现IEnumerable
或IEnumerable<T>
接口。因为foreach
就需要一个迭代器,IEnumerable
接口中定义了一个GetEnumerator
方法用来返回迭代器,类型如果实现了IEnumerable
接口,则也必须实现GetEnumerator
方法,因此也就可以使用foreach
语句了
在 C# 1.0 中,要获取迭代器,就必须要实现 IEnumerable
或GetEnumerator
方法;而要实现一个迭代器,就必须要实现IEnumerable
接口中的MoveNext
和Reset
方法。然而在 C# 2.0 中,微软提供了yield
关键字来简化迭代器的实现,这使得自定义迭代器变得容易了很多。我们先看 C# 1.0 中实现迭代器的代码
class Program
{
static void Main(string[] args)
{
Friends friends = new Friends();
foreach (Friend item in friends)
{
Console.WriteLine(item.name);
}
Console.ReadKey();
}
public class Friend
{
public Friend(string name)
{
this.name = name;
}
public string name { get; set; }
}
public class Friends : IEnumerable
{
public Friends()
{
this.friends = new Friend[] { new Friend("张三"), new Friend("王二") };
}
private Friend[] friends;
public Friend this[int index]
{
get { return friends[index]; }
}
public int Count
{
get { return friends.Count(); }
}
public IEnumerator GetEnumerator()
{
return new FriendIterator(this);
}
}
public class FriendIterator : IEnumerator
{
private readonly Friends friends;
private int index;
private Friend current;
internal FriendIterator(Friends friends)
{
this.friends = friends;
index = 0;
}
public object Current => this.current;
public bool MoveNext()
{
if (index 1 > friends.Count)
{
return false;
}
else
{
this.current = friends[index];
index ;
return true;
}
}
public void Reset()
{
index = 0;
}
}
}
从以上代码可以看出,在 C# 1.0 中,要使用某个类型可以迭代时要写大量代码的。幸好 .Net 帮我们封装了这一切
C# 2.0 简化了迭代器的实现
C# 2.0 实现迭代器代码如下
代码语言:javascript复制class Program
{
static void Main(string[] args)
{
Friends friends = new Friends();
foreach (Friend item in friends)
{
Console.WriteLine(item.name);
}
Console.ReadKey();
}
public class Friend
{
public Friend(string name)
{
this.name = name;
}
public string name { get; set; }
}
public class Friends : IEnumerable
{
public Friends()
{
this.friends = new Friend[] { new Friend("张三"), new Friend("王二") };
}
private Friend[] friends;
public Friend this[int index]
{
get { return friends[index]; }
}
public int Count
{
get { return friends.Count(); }
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < friends.Length; i )
{
yield return friends[i];
}
}
}
}
这段代码只用了一个yield return
就实现了迭代器,它的作用就是告诉编译器,GetEnumerator
方法不是一个普通方法,而是实现迭代器的方法。当编译器看到yield return
语句的时候,会在中间代码中为我们生成了一个IEnumerator
接口的对象,这点可以用反射工具查看
yield return
语句其实是 C# 中提供的另一个语法糖,简化了我们迭代器源代码,把具体而复杂的实现过程留给了编译器去完成