# 集合

集合跟前面的数组有点类似,但是数组是一旦创建好,它的大小就是固定的,不能在数组的末尾添加新项。而集合可以添加删除元素。C#中的数组实现为System.Array类的实例,它们只是集合类(Collection Class)中的一种类型,集合类的功能大多实现System.Collections名称空间中的接口而获得的,集合的语法已经标准化了,这个名称空间中还包含其他以不同于System.Array的方式实现这些接口的类。集合类的功能可以通过接口来实现,所以不仅可以使用基本集合类,比如System.Array,还可以创建自己的定制集合类,这些集合可以专用于要枚举的对象,这么做的一个优点是该集合是强类型化的,从集合中提取项时,不需要把它们转换类型,因为我们知道这个集合只有这个类型的,还有一个优点是提供专用于这种类型的方法,比如从一个球类的集合,获取所有的篮球。

System.Collections名称空间中的以下几个接口提供了基本的功能:

  • IEnumerable——可以迭代集合中的项。
  • ICollection——继承于IEnumerable。可以获取集合中项的个数,并能把项复制到一个简单的数组类型中。
  • IList——继承于IEnumerable和ICollection。提供了集合的项列表,允许访问这些项,并提供其他一些与项列表相关的基本功能。
  • IDictionary——继承于IEnumerable和ICollection。类似于IList,但提供了可通过键值而不是索引访问的项列表。

System.Array类实现了IList,ICollection和IEnumerable,但不支持IList的一些更高级功能,它表示大小固定的项列表。

# ArrayList

System.Collections名称空间中的类System.Collections.ArrayList也实现了IList,ICollection和IEnumerable接口,它要比Array复杂些,System.Array的大小是固定不变的,而这个类可以用于表示大小可变的项列表。看下面实例:

class Animal
{
    string name = "Animal!";

    protected int id;
    public virtual void Run()
    {
        Console.WriteLine("Animal run!");
    }
}

class Tiger : Animal
{
    public Tiger(int idT)
    {
        id = idT;
    }
    public override void Run()
    {
        Console.WriteLine("Tiger Run!");
    }
    public override string ToString()
    {
        return "tiger id:"+id;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Animal[] animalArray = new Animal[2];

        animalArray[0] = new Tiger(3);
        animalArray[1] = new Tiger(4);
        ArrayList animalArrayList = new ArrayList();
        Tiger t1=new Tiger(1);
        animalArrayList.Add(t1);
        animalArrayList.Add(new Tiger(2));
        animalArrayList.AddRange(animalArray);
        animalArrayList.RemoveAt(2);
        animalArrayList.Remove(t1);
        foreach(Animal one in animalArrayList)
        {
            Console.WriteLine(one);
        }
        Console.WriteLine(animalArrayList.IndexOf(t1));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# CollectionBase

System.Collections.CollectionBase类,这个抽象类提供了集合类的大量实现代码,它的接口有IEnumerable,ICollection和IList,但只提供了一些必要的实现代码,主要是IList的Clear()和RemoveAt()方法,以及ICollection的Count属性。该类还提供了两个受保护的属性,List和InnerList,使用它们可以访问所存储的对象本身,List可以通过IList接口访问,InnerList用于存储项的ArrayList对象。

# 索引符

索引符(indexer)是一种特殊的属性,可以把它添加到一个类中,以提供类似于数组的访问形式。看下面实例:

class Book
{
    public string bookName;
    public Book(string name)
    {
        bookName = name;
    }
    public override string ToString()
    {
        return "书名:" + bookName;
    }
}
class Library:CollectionBase
{
    public void Add(Book book) => List.Add(book);
    public void Remove(Book removeBook) => List.Remove(removeBook);
    public Book this[int bookId]
    {
        get
        {
            return (Book)List[bookId];
        }
        set
        {
            List[bookId] = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Book b1 = new Book("历史");
        Book b2 = new Book("数学");
        Book b3 = new Book("英语");

        Library lib = new Library();
        lib.Add(b1);
        lib.Add(b2);
        lib.Add(b3);
        lib.Remove(b2);

        foreach(Book oneBook in lib)
        {
            Console.WriteLine(oneBook);
        }

        Console.WriteLine(lib[1]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# DictionaryBase

除实现IList接口外,还可以实现类似IDictionary接口,允许项通过键值进行索引,而不是根据顺序索引。这是通过IDictionary接口的实现,这个基类就是DictionaryBase,它也实现了IEnumerable和ICollection,提供了对任何集合都相同的基本集合处理功能。看实例:

class Book
{
    public string bookName;
    public Book(string name)
    {
        bookName = name;
    }
    public override string ToString()
    {
        return "书名:" + bookName;
    }
}
class Library:DictionaryBase
{
    public void Add(string bookName,Book book) => Dictionary.Add(bookName,book);
    public void Remove(string bookName) => Dictionary.Remove(bookName);
    public Book this[string bookId]
    {
        get
        {
            return (Book)Dictionary[bookId];
        }
        set
        {
            Dictionary[bookId] = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Book b1 = new Book("历史");
        Book b2 = new Book("数学");
        Book b3 = new Book("英语");

        Library lib = new Library();
        lib.Add("history",b1);
        lib.Add("math",b2);
        lib.Add("english",b3);

        foreach(DictionaryEntry one in lib)
        {
            Console.WriteLine(one.Key+":"+one.Value);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 迭代器

上面两个例子使用foreach来遍历元素,这是因为DictionaryBase和CollectionBase实现了IEnumerable接口,现在说明下在foreach循环中,迭代一个集合Library的过程:

  1. 调用Library.GetEnumerator(),返回一个IEnumerator引用,这个方法可通过实现IEnumerable接口的代码来获得,但它是可选的。
  2. 调用所返回的IEnumerator引用的MoveNext()方法。
  3. 如果MoveNext()方法返回true,就使用IEnumerator接口的Current属性获取对象的一个引用,用于foreach循环。
  4. 重复2,直到MoveNext()方法返回false为止。

为了让类能使用foreach,必须重写上面的几个方法,这有些麻烦,但是可以使用迭代器,它能有效地自动生成许多代码,简化流程。

迭代器的定义:它是一个代码块,按顺序提供了要在foreach块中使用的所有值,一般情况下,它是一个方法。无论代码块是什么,其返回类型都是有限制的,这个返回值与所枚举对象类型不同,它是IEnumerable和IEnumerator,它们的应用场合是:

  • 如果要迭代一个类,使用方法GetEnumerator(),其返回类型是IEnumerator。
  • 如果要迭代一个类成员,例如一个方法,则使用IEnumerable。

下面来看迭代一个方法的例子:

class Program
{
    public static IEnumerable SimpleEnumerator()
    {
        yield return "hello1";
        yield return "hello2";
        yield break;
        yield return "hello3";
    }
    static void Main(string[] args)
    {
        foreach (string item in SimpleEnumerator())
        {
            Console.WriteLine(item);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

下面是修改上面DictionaryBase的例子:

class Book
{
    public string bookName;
    public Book(string name)
    {
        bookName = name;
    }
    public override string ToString()
    {
        return "书名:" + bookName;
    }
}
class Library:DictionaryBase
{
    public void Add(string bookName,Book book) => Dictionary.Add(bookName,book);
    public void Remove(string bookName) => Dictionary.Remove(bookName);

    public new IEnumerator GetEnumerator()
    {
        foreach(object one in Dictionary.Values)
        {
            yield return one;
        }

    }
    public Book this[string bookId]
    {
        get
        {
            return (Book)Dictionary[bookId];
        }
        set
        {
            Dictionary[bookId] = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Book b1 = new Book("历史");
        Book b2 = new Book("数学");
        Book b3 = new Book("英语");

        Library lib = new Library();
        lib.Add("history",b1);
        lib.Add("math",b2);
        lib.Add("english",b3);

        foreach(Book one in lib)
        {
            Console.WriteLine(one);
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

使用迭代器来查找某个区间的所有素数:

class PrimeNumber
{
    private int min;
    private int max;
    public PrimeNumber()
    {
    }
    public PrimeNumber(int min, int max)
    {
        if(min < 2)
        {
            throw (new ArgumentOutOfRangeException("max over 2,actual value is:", min,"change it!"));
        }
        if(min >= max)
        {
            throw (new ArgumentOutOfRangeException("min is bigger than max","change it!"));
        }
        this.min = min;
        this.max = max;
    }

    public IEnumerator GetEnumerator()
    {
        for(int i = min; i <= max; i++)
        {
            bool isPrime = true;
            for(int j = 2; j <= Math.Floor(Math.Sqrt(i)); j++)
            {
                if (i % j == 0) { isPrime = false; break; }
            }
            if (isPrime)
            {
                yield return i;
            }
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        PrimeNumber primeNum = new PrimeNumber(2, 100);
        foreach(int i in primeNum)
        {
            Console.WriteLine(i);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 比较

# is运算符

在比较对象时常需要确定它们的类型,我们之前用GetType()和typeof联合一块使用,但是还有很多其他复杂的情况,比如判断从基类派生的子类和孙类是否属于这个基类,这里就涉及到is运算符。

is运算符并不是用来说明对象是某种类型,而是用来检查对象是不是给定类型,或者是否可以转换为给定类型,如果是就返回true。

它的语法如下:

sourceVariable is <targetType>
1

如果<targetType>是一个类类型,而sourceVariable也是该类型,或者它继承了该类型,或者它可以封箱到该类型中,返回True。 如果<targetType>是一个接口类型,而sourceVariable也是该类型,或者它是实现该接口的类型,则返回True。 如果<targetType>是一个值类型,而sourceVariable也是该类型,或者它可以拆箱到该类型中,则返回True。

interface IMyInterface{}
struct MyStruct : IMyInterface
{
    public int val;
}
class MyClass1 : IMyInterface{}
class MyClass2 : MyClass1{}
class Program
{
    static void Main()
    {
        object m1 = new MyStruct();
        MyClass1 c1 = new MyClass1();
        MyClass2 c2 = new MyClass2();
        if(c2 is MyClass1)
        {
            Console.WriteLine("c2 is MyClass1!");
        }

        if(c2 is IMyInterface)
        {
            Console.WriteLine("c2 is IMyInterface");
        }
        if(m1 is MyStruct)
        {
            Console.WriteLine("m1 is MyStruct!");
        }

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# is模式匹配

is运算符是进行模式匹配和过滤数据集的一种非常有效的技术,在下面的语法下,如果mc1是MyClass就创建一个MyClass的新变量mc11:

class MyClass
{
    public string name;
    public MyClass(string n)
    {
        name = n;
    }
}
class Program
{
    static void Main()
    {
        object mc1 = new MyClass("hello");
        if(mc1 is MyClass mc11)
        {
            Console.WriteLine(mc11.name);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 比较值

要比较自定义的类有两种方法,一种是使用运算符重载,另一种是使用IComparable和IComparer接口,.Net Framework的各种集合类也支持这种方式,使得他们成为对集合中的对象进行排序的一种极佳的方式。

# 运算符重载

通过运算符重载(operator overloading),可以对我们自己定义的类使用标准的运算符,比如>,<。看下面实例:

public class MyClass1
{
    public int val;
    public MyClass1(int value)
    {
        val = value;
    }

    public static MyClass3 operator +(MyClass1 c1,MyClass2 c2)
    {
        return new MyClass3(c1.val + c2.val);
    }
    public static bool operator >(MyClass1 c1,MyClass2 c2)
    {
        return c1.val > c2.val;
    }
    public static bool operator < (MyClass1 c1,MyClass2 c2)
    {
        return c1.val < c2.val;
    }

}
public class MyClass2
{
    public int val;
    public MyClass2(int value)
    {
        val = value;
    }
}
public class MyClass3
{
    public int val;
    public MyClass3(int value)
    {
        val = value;
    }
}


class Program
{
    static void Main(string[] args)
    {
        MyClass1 c1 = new MyClass1(10);
        MyClass2 c2 = new MyClass2(12);
        MyClass3 c3 = c1 + c2;
        //MyClass3 c3 = c2 + c1;
        //MyClass2 c2 = c1 + c2;
        Console.WriteLine(c1 < c2);
        Console.WriteLine(c3.val);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

注意可重载的运算符有:

  • 一元运算符:+,-,!,~,++,--,true,false
  • 二元运算符:+,-,*,/,%,&,|,^,<<,>>
  • 比较运算符:==,!=,<,>,<=,>=

如果重载true和false运算符,就可以在布尔表达式中用,比如if(MyClass1){}。

不能重载的运算符有:+=,&&和||。但是有相应的+,&,|替代。重载==或!=时通常也要重写Object.Equals()和Object.GetHashCode(),这保证了在所有情景都能有相同的表现。

public class MyClass1
{
    public int val;
    public MyClass1(int value)
    {
        val = value;
    }

    public static bool operator ==(MyClass1 c1, MyClass1 c2)
    {
        return c1.val == c2.val;
    }

    public static bool operator !=(MyClass1 c1, MyClass1 c2)
    {
        return c1.val != c2.val;
    }

    public override bool Equals(object obj)
    {
        Console.WriteLine($"use Equals,self value:{val};compare value:{((MyClass1)obj).val}");
        if (obj is MyClass1)
        {
            return val == ((MyClass1)obj).val + 2;
        }
        return false;
    }

    public override int GetHashCode()
    {
        Console.WriteLine($"Use GetHashCode function,object value is:{val}");
         return val;
    }

}
class Program
{
    static void Main(string[] args)
    {
        MyClass1 c1 = new MyClass1(10);
        MyClass1 c2 = new MyClass1(12);
        ArrayList arrayList = new ArrayList();
        Console.WriteLine(c1 == c2);
        arrayList.Add(c1);
        arrayList.Add(c2);
        Console.WriteLine(arrayList.IndexOf(c1));
        Console.WriteLine("array 0 element is :"+((MyClass1)arrayList[0]).val);
        Hashtable ht = new Hashtable();
        ht.Add(c1,"1");
        ht.Add(c2,"2");
        Console.WriteLine(ht.Contains(c1));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

从上面的实例是否看出为什么要重写Equals了,因为很多容器类中就是使用Equals方法来判断自定义类的。最好就是在重载运算符==和!=中调用Equals做二次封装。

# IComparable接口

IComparable和IComparer接口是 .Net Framework中比较对象的标准方式。它们的区别如下:

  • IComparable在要比较的对象的类中实现CompareTo方法,可以比较该对象和另一个对象。
  • IComparer在一个单独的类中实现Compare方法,可以比较任意两个对象。

一般是使用IComparable给出类的默认比较代码,使用其他类给出非默认的比较代码。一些集合类对其中对象进行排序时就需要这两种实现方法。下面看实例:

class Person:IComparable
{
    public int age;
    public string name;

    public Person(int ageT,string nameT)
    {
        age = ageT;
        name = nameT;
    }

    public int CompareTo(object obj)
    {
        if(obj is Person tg)
        {
            int ageResult = age - tg.age;
            if (ageResult == 0)
            {
                return Comparer.Default.Compare(name,tg.name);
            }
            else
            {
                return ageResult;
            }
        }
        else
        {
            throw new ArgumentException("Can not compare not Person class!");
        }
        return 0;
    }
    public override string ToString()
    {
        return $"age:{age},name:{name}";
    }
}

public class PersonComparer : IComparer
{
    public static IComparer Default = new PersonComparer();

    public int Compare(object x, object y)
    {
        if(x is Person source && y is Person target)
        {
            int ageResult = target.age - source.age;
            if (ageResult == 0)
            {
                return Comparer.Default.Compare(target.name, source.name);
            }
            else
            {
                return ageResult;
            }

        }
        else
        {
            throw new ArgumentException("compare objects need be Person objects");
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person(15, "x1");
        Person p2 = new Person(17, "x2");
        Person p3 = new Person(15, "x3");
        Person p4 = new Person(24, "x4");

        Console.WriteLine(p1.CompareTo(p2));
        Console.WriteLine(PersonComparer.Default.Compare(p1,p3));

        ArrayList list = new ArrayList();
        list.Add(p1);
        list.Add(p2);
        list.Add(p3);
        list.Add(p4);

        list.Sort();
        foreach(Person onePerson in list)
        {
            Console.WriteLine(onePerson);
        }
        Console.WriteLine("----------Use PersonComparer!---------");
        list.Sort(PersonComparer.Default);
        foreach (Person onePerson in list)
        {
            Console.WriteLine(onePerson);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

.Net Framework在类Comparer上提供了IComparer接口的默认代码,这个类位于System.Collection名称空间中,可以对简单类型以及支持IComparable接口的任意类型进行特定文化的比较。

static void Main(string[] args)
{
    string str1 = "abc";
    string str2 = "bcd";
    Console.WriteLine(Comparer.Default.Compare(str1,str2));
}
1
2
3
4
5
6

注意:

  • 检查传递给Comparer.Default.Compare()的对象,看看它们是否支持IComparable,如果支持就使用该实现代码。
  • 允许使用null值,它被解释为"小于"其他任意对象。
  • 字符串根据当前本地化规则来处理,要使用不同的本地化规则处理字符串,Comparer类必须使用其构造函数进行实例化,以便传送用于指定本地化规则的System.Globalization.CultureInfo对象。
  • 字符串处理时会区分大小,如果要忽略大小写,需要使用CaseInsensitiveComparer类。

# 封箱拆箱

# 封箱

封箱是把值类型转换为System.Object类型,或者转换为由值类型实现的接口类型。它的内部机制如下:

  1. 在堆中开辟内存空间。
  2. 将值类型的数据复制到堆中。
  3. 返回堆中新分配对象的地址。

# 拆箱

从object类型到值类型或从接口类型到实现该接口的值类型的显示转换。它的内部机制:

  1. 判断给定类型是否是装箱时的类型。
  2. 返回已装箱实例中属于原值类型字段的地址。

# 实例说明

struct MyStruct
{
  public int value;
}
class MyClass
{
  public int value;
}
class Program
{
  static void Main()
  {
      MyStruct myStruct = new MyStruct();
      myStruct.value = 100;
      object myStructObj = myStruct;
      myStruct.value = 1000;
      Console.WriteLine(((MyStruct)myStructObj).value);

      MyClass myClass = new MyClass();
      myClass.value = 100;
      object myClassObj = myClass;
      myClass.value = 1000;
      Console.WriteLine(((MyClass)myClassObj).value);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

装箱到一个接口中:

interface IMyInterface
{

}
struct MyStruct : IMyInterface
{
  public int val;
}
class Program
{
  static void Main()
  {
      MyStruct myStruct = new MyStruct();
      myStruct.val = 100;
      IMyInterface refType = myStruct;
      myStruct.val = 1000;
      Console.WriteLine(((MyStruct)refType).val);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

注意:

  • 以装箱方式创建的对象,包含值类型变量的一个副本引用,而不包含原值类型变量的引用。相对比来说引用类型做同样的操作却是一直引用同一对象。
  • 拆箱后的类型必须与装箱时的类型相同。
  • 伴随拆箱的字段复制步骤不属于拆箱过程。
  • 装箱和拆箱不是互逆过程,装箱的性能开销远大于拆箱。

# 意义

封箱有两个非常重要的原因:

  • 允许项的类型是object的集合(比如ArrayList)中使用值类型。
  • 有一个内部机制允许在值类型(比如int和结构)上调用object方法。

# 转换

在将我们自定义的类转换为其他类时,可重载转换运算符,类类型之间有显式和隐式转换。

class ConvertClass1
{
    public int val;
    public ConvertClass1(int value)
    {
        val = value;
    }
    public static implicit operator ConvertClass2(ConvertClass1 op1)
    {
        Console.WriteLine("Convert ConvertClass1 to ConvertClass2!value:"+op1.val);
        return new ConvertClass2(op1.val);
    }
}
class ConvertClass2
{
    public int val;
    public ConvertClass2(int value)
    {
        val = value;
    }
    public static explicit operator ConvertClass1(ConvertClass2 op2)
    {
        Console.WriteLine("Convert ConvertClass2 to ConvertClass1!value:"+op2.val);
        return new ConvertClass1(op2.val);
    }
}
class Program
{
    static void Main(string[] args)
    {
        ConvertClass1 c1 = new ConvertClass1(10);
        ConvertClass2 c2 = new ConvertClass2(20);
        //ConvertClass1 c3 = c2;
        ConvertClass1 c3 = (ConvertClass1)c2;
        Console.WriteLine(c3.val);
        //显式转换也可以做隐式转换。
        ConvertClass2 c4 = c1;
        ConvertClass2 c5 = (ConvertClass2)c1;
        Console.WriteLine(c4.val);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# as运算符

as运算符能把一种类型转换为指定的引用类型:

operObj as 目标类型
1

它只适用于如下情况:

  • operObj的类型是目标类型
  • operObj可以隐式转换为目标类型
  • operObj可以封箱到目标类型

如果不能将operObj转换为目标类型,则表达式的结果就是null。

interface IMyInterface
{
}
class ClassOne : IMyInterface
{
}
class ClassTwo : ClassOne
{
}
static void Main(string[] args)
{
    ClassOne obj1 = new ClassOne();
    ClassTwo obj2 = new ClassTwo();

    ClassTwo obj3 = obj1 as ClassTwo;
    Console.WriteLine($"派生类引用=基类对象as派生类,obj3:{obj3}");
    //ClassTwo obj6 = obj1 as ClassOne;
    //Console.WriteLine($"派生类引用=基类对象as基类,obj3:{obj3}");
    //提示缺乏ClassOne To ClassTwo的显式转换。
    ClassOne obj4 = obj2 as ClassTwo;
    Console.WriteLine($"基类引用=派生类对象as派生类,obj4:{obj4}");
    ClassOne obj5 = obj2 as ClassOne;
    Console.WriteLine($"基类引用=派生类对象as基类,obj5:{obj5}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

像上面的obj3,如果要是简单类型转换的话会抛出一个异常,但是如果使用as的话,就可以将一个null值赋给obj3,判断这个值是否为null要比捕捉异常简单。