C# 学习笔记(10)—— 可空类型、匿名方法、迭代器

2023-10-20 18:50:06 浏览数 (1)

泛型是 C# 2 众多特性的其中之一,C# 2 还提出了可空类型、匿名方法和迭代器三个重要特性

可空类型

当你把数据库表映射为 C# 中的对象时会发现,DateTime类型在 C# 语言中是不能为 null 的。为了完成映射,开发人员便有了这样的需求——值类型能不能是可空类型呢?

简介

可空类型也是值类型,但是它包含 Null 值的值类型

代码语言:javascript复制
int?o nullable = null;

在以上代码中,int? 就是可空的 int 类型。修饰符只是 C# 提供的一个语法糖,所谓语法糖,就是 C# 提供的一种方便的表现行是

C# 中肯定没有 int? 这个类型,对于编译器而言,int? 会被编译成Nullable<int>类型,即可空类型

代码语言:javascript复制
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 类型进行比较的,所以??运算符不能用于值类型

代码语言:javascript复制
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案例,它必须要实现IEnumerableIEnumerable<T>接口。因为foreach就需要一个迭代器,IEnumerable接口中定义了一个GetEnumerator方法用来返回迭代器,类型如果实现了IEnumerable接口,则也必须实现GetEnumerator方法,因此也就可以使用foreach语句了

在 C# 1.0 中,要获取迭代器,就必须要实现 IEnumerableGetEnumerator 方法;而要实现一个迭代器,就必须要实现IEnumerable接口中的MoveNextReset方法。然而在 C# 2.0 中,微软提供了yield关键字来简化迭代器的实现,这使得自定义迭代器变得容易了很多。我们先看 C# 1.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()
        {
            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# 中提供的另一个语法糖,简化了我们迭代器源代码,把具体而复杂的实现过程留给了编译器去完成

0 人点赞