泛型

可空值类型

对于一个值类型,他一定是有效的,但他的值不一定是有效的

举例来说,如果需要用户输入一个体重float,当用户没有输入时,float默认就会是0,但很明显,此时的体重是非法的,是应该报错的

解决办法有两个

  • 当没有输入值时,我们用一个默认值代替,系统默认是0,我们可以设置成其他的
  • 额外设置一个bool标志位,当为false时,代表数据非法

很明显这两种解决办法,多少都是有一些问题的,于是产生了可空值类型

Nullable<T>结构体

可空值类型是一种数据结构,早期核心代码如下

public struct Nullable<T> where T : struct
{
    private readonly T value;
    private readonly bool hasValue;
    
    public Nullable(T value)
    {
        this.value = value;
        this.hasValue = true;
    }
    
    public bool HasValue => hasValue;
    
    public T Value
    {
        get
        {
            if(!hasValue)
            {
                throw new Exception();
            }
            return value;
        }
    }
}

由于结构体的特性,默认还会有一个隐藏的构造函数

value会是T的默认值,注意,虽然value是有值的(值类型必定有一个默认值),但是此时我们认为这个value是无效的,逻辑上是!hasValue

public struct Nullable<T> where T : struct
{
    public Nullable()
    {
        this.value = default;
        this.hasValue = false;
    }
}

装箱操作

int x = 5;
object o = x;

此时o是对于装箱int的引用,但是在C#里,装箱intint没有显示区别,o.GetType(),和typeof(int),结果是一样的

而可空值类型则并非如此

  • hasValue时,结果是对于装箱T的引用
  • !hasValue时,结果是对NULL的引用

语法

?后缀

以下两种声明是等价的

Nullable<int> x;
int? y;

null字面值

  • 当null用于引用类型时,表示一个空的引用
  • 当null用于可空值类型时,表示一个!hasValue的可空值类型

以下二者等价

int? x;
if (x != null){}
if (x.HasValue){}

提升运算符

对于非可控类型T,我们可以重载很多运算符,诸如:+、++、-、–等等

当我们对T进行运算符重载之后,Nullable<T>也会进行相同的操作,此时Nullable<T>里的运算符,称为提升运算符

具体表格略

可空逻辑

Nullable<T>之间也是可以进行逻辑运算的

具体真值表略

as运算符

object o = 5;
int? x = o as int;

如果o可以转为int,那么x就hasValue,且Value=5

否则x就是!hasValue的

??合并运算符

var x = a ?? b;

如果a不是null,那么x等于a,否则x等于b

迭代器

简介

  • IEnumerable接口,代表可迭代序列
  • IEnumerator接口,代表访问可迭代序列的一个游标

实现IEnumerator的类,代表了一个迭代器,需要提供一个可以完整迭代的序列以及迭代规则

public class TestEnumerator : IEnumerator<int>
{
    private int index;

    object IEnumerator.Current => Current;

    public int Current
    {
        get
        {
            index++;
            return index - 1;
        }
    }
    
    public bool MoveNext()
    {
        return index <= 5;
    }
}

其中Current是迭代序列,会一直返回index,而MoveNext则是判断是否可以继续迭代,很明显当index>5时,迭代结束

实现IEnumerable的类,需要返回一个迭代器

public class TestEnumerable : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        return new TestEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

此时就可以使用foreach语法糖了

public static void Main(string[] args)
{
    var test = new TestEnumerable();
    foreach (var index in test)
    {
        Console.WriteLine(index);
    }
}

代码等价于

public static void Main(string[] args)
{
    var test = new TestEnumerable();
    var iterator = test.GetEnumerator();
    while (iterator.MoveNext())
    {
        Console.WriteLine(iterator.Current);
    }
}

延迟执行

迭代器重要的作用之一就是延迟执行,考虑实现一个斐波那契数列

public static void Main(string[] args)
{
    foreach (var value in Get())
    {
        if (value>1000)
            break;
        Console.WriteLine(value);
    }
}
    
public static IEnumerable<int> Get()
{
    var current = 0;
    var next = 1;
    while (true)
    {
        yield return current;
        var temp = current;
        current = next;
        next = next + temp;
    }
}