C#进阶篇
C#进阶篇

简单数据结构类
ArrayList
Object类型的数组
申明
//ArrayList的申明
ArrayList arr1 = new ArrayList();
增删查改
//增删查改
// 增
//尾插法
//可以增任何类型
arr1.Add("张三");
arr1.Add(1);
arr1.Add(true);
arr1.Add(new object());
//批量增加,把另一个list容器里的所有元素都添加到当前容器的后面
arr1.AddRange(new ArrayList() { "张三", "李四", "王五" });
//在中间指定位置插入
arr1.Insert(2, "111111");
//批量插入
arr1.InsertRange(3, new ArrayList() { "123", "234", "345" });
// 删
//从前往后遍历,删除首个匹配的元素
arr1.Remove("张三");
//删除指定位置的元素
arr1.RemoveAt(0);
//清空
// arr1.Clear();
// 查
//获取指定位置的元素
Console.WriteLine(arr1[0]);
//查看元素是否存在
if (arr1.Contains("1")) Console.WriteLine("存在");
//正向查找元素位置,找不到返回-1
int index = arr1.IndexOf(true);
Console.WriteLine(index);
//反向查找元素位置,返回的索引还是从前开始计数的,找不到返回-1
index = arr1.LastIndexOf(true);
Console.WriteLine(index);
// 改
arr1[0] = "999";
Console.WriteLine(arr1[0]);
Console.WriteLine();
//长度,数组的元素个数
Console.WriteLine(arr1.Count);
//容量
//用来避免每次改动数组都产生垃圾,有了容量的存在,只有扩容的时候才产生垃圾
Console.WriteLine(arr1.Capacity);
//遍历
//一般的遍历
for (int i = 0; i < arr1.Count; i++)
{
Console.WriteLine(arr1[i]);
}
//迭代器遍历
foreach (object obj in arr1)
{
Console.WriteLine(obj);
}
排序和反转
和数组一样
arr1.Sort();
arr1.Reverse();
装箱拆箱
#region 装箱拆箱
//ArrayList本质是一个可以自动扩容的object数组
//装箱:进行值类型的储存
//拆箱:进行值类型的取出
//所以尽量选择其他的数据容器
int num = 1;
arr1[0] = num; //装箱
num = (int)arr1[0]; //拆箱
#endregion
ArrayList和数组的区别
ArrayList本质是object数组
| 功能 | 数组 | ArrayList |
|---|---|---|
| 获取长度 | 数组名.Length |
数组名.Count |
| 访问元素 | 数组名[index](直接访问) |
数组名[index](需拆箱) |
| 修改元素 | 数组名[index] = value |
数组名[index] = value |
| 排序 | Array.Sort(数组名) |
数组名.Sort() |
| 反转 | Array.Reverse(数组名) |
数组名.Reverse() |
| 查找索引 | Array.IndexOf(数组名, value) |
数组名.IndexOf(value) |
| 元素是否存在 | ❌ | 数组名.Contains(value) |
| 清空 | Array.Clear(数组名, startIndex, count) |
数组名.Clear() |
| 增删方法 | 增删方法需要自己写 | 内置 |
| 添加元素 | ❌ 固定大小,不能动态添加 | 数组名.Add(value) |
| 插入元素 | ❌ 不支持 | 数组名.Insert(index, value)数组名.InsertRange(index, 一个集合); |
| 批量添加 | ❌ 不支持 | 数组名.AddRange(一个集合) |
| 删除元素 | ❌ 不支持 | 数组名.Remove(value)RemoveAt(index) |
| 自动扩容 | ❌ 定长 | ✅ 是 |
| 类型安全 | ✅ 是 | ❌ 否(需手动强制转换) |
| 性能 | 高(只要数组不是object数组就不存在装箱拆箱) | 相对较低(存在装箱/拆箱) |
习题

using System.Collections;
class Bag
{
private ArrayList items;
private int money;
public Bag(int money)
{
this.money = money;
items = new ArrayList();
}
public void BuyItem(Item item)
{
//物品信息错误
if (item.num <= 0 || item.price <= 0)
{
Console.WriteLine("物品信息有误");
return;
}
//金钱变化
if (money < item.price * item.num)
{
Console.WriteLine("钱不够");
return;
}
money -= item.price * item.num;
//添加物品
foreach (Item i in items)
{
//如果已经在背包里面,必须要比较UID
// 因为传入的item不会和items里的item是同一个对象,所以要判断UID
if (i.UID == item.UID)
{
//叠加数量
i.num += item.num;
return;
}
}
items.Add(item);
Console.WriteLine("买了{0}个{1},共花费{2}元", item.num, item.name, item.price * item.num);
ShowItems();
}
//根据item卖
public void SellItem(Item item)
{
//遍历物品
foreach (Item i in items)
{
//要卖的东西在背包里有,必须要比较UID
if (i.UID == item.UID)
{
string name = i.name;
int price = i.price;
int sellNum = item.num;
//买的数量判定
if (i.num < item.num)
{
sellNum = i.num; //要卖出的数量超出已有,只能卖出已有的全部
}
i.num -= sellNum;
money += (int)(sellNum * price * 0.8f);
Console.WriteLine("卖了{0}个{1},共获得{2}元", sellNum, name, (int)(sellNum * price * 0.8f));
if (i.num <= 0) items.Remove(i); //卖完了就要移除当前遍历的该项
ShowItems();
return;
}
}
Console.WriteLine("没有这个物品");
return;
}
//根据UID卖
public void SellItem(int UID, int num = 1)
{
Item item = new Item();
item.UID = UID;
item.num = num;
SellItem(item);
}
public void ShowItems()
{
foreach (Item item in items)
{
Console.Write("有{0}个{1},", item.num, item.name);
}
Console.WriteLine("现在手里有{0}元",money);
}
}
class Item
{
//单价
public int price;
public int UID;
public string name;
public int num;
public Item()
{
}
public Item(int UID, int price, string name, int num)
{
this.UID = UID;
this.price = price;
this.name = name;
this.num = num;
}
}
class Program
{
static void Main()
{
Bag bag = new Bag(10000);
Item i1 = new Item(1, 10, "sb", 10);
Item i2 = new Item(2, 20, "sb2", 20);
Item i3 = new Item(3, 999, "sb3", 3);
bag.BuyItem(i1);
bag.BuyItem(i2);
bag.BuyItem(i3);
bag.SellItem(2);
bag.SellItem(i1);
bag.SellItem(i1);
}
}
Stack
本质是object[]数组
栈储存容器,后进先出
栈的使用
using System.Collections;
//栈的申明
Stack stack = new Stack();
//增删查改
//压栈
stack.Push("1");
stack.Push(true);
stack.Push(1);
stack.Push(new Test());
//出栈
object o1 = stack.Pop();
Console.WriteLine(o1);
//查看栈顶
object o2 = stack.Peek();
Console.WriteLine(o2);
//是否在栈中
if (stack.Contains(1)) Console.WriteLine("存在1");
//清空
// stack.Clear();
//遍历
//1.长度
Console.WriteLine(stack.Count);
//2.用foreach遍历
foreach (object o in stack)
{
Console.WriteLine(o);
}
//3.转成object数组遍历,顺序也是从栈顶到栈底
object[] arr = stack.ToArray();
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
Console.WriteLine();
Console.WriteLine(stack.Count);
//4.循环弹栈
while (stack.Count > 0)
{
Console.WriteLine(stack.Pop());
}
Console.WriteLine(stack.Count);
class Test
{
}
栈的装箱拆箱
因为栈的本质还是object[]数组
所以当进行值类型存储(入栈值类型对象)的时候就是装箱,把值类型对象取出来(出栈值类型对象)转换使用就是拆箱。
//装箱
stack.Push(1); // int 值类型 → object 引用类型(装箱)
stack.Push(true); // bool 值类型 → object 引用类型(装箱)
//拆箱
object o = stack.Pop();
int num = (int)o; // 拆箱:object → int
如何避免装箱拆箱?
泛型集合:栈泛型 Stack <Type>
相当于一个只有栈的存储特性(后进先出)的集合,缺点就是只能存指定类型的元素
入栈出栈都是直接存取,不存在装箱拆箱
Stack<int> stack = new Stack<int>();
stack.Push(100);
int value = stack.Pop(); // 直接获取 int,无需拆箱
栈的应用
UI的显示逻辑(每次点击的面板总是显示在最前面)
高进制转低进制
习题

对于取出一个后进先出的数组,可以用栈来解决
using System.Collections;
Console.WriteLine("请输入一个整数");
uint num = (uint.Parse)(Console.ReadLine());
DecToBinary(num);
static void DecToBinary(uint num)
{
Stack stack = new Stack();
while (num != 0)
{
stack.Push(num % 2);
num /= 2;
}
Console.Write("二进制为:");
while (stack.Count != 0)
{
Console.Write(stack.Pop());
}
Console.WriteLine();
}
Queue
本质是object[]数组,先进先出,类似管道
队列的使用
using System.Collections;
Queue queue = new Queue();
//增删查改
//入队
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(new Test());
//出队
object o = queue.Dequeue();
Console.WriteLine(o);
//查看队头
o = queue.Peek();
Console.WriteLine(o);
//是否在队列中
if (queue.Contains(3)) Console.WriteLine("在队列中");
//清空
// queue.Clear();
//遍历
//1.长度
Console.WriteLine(queue.Count);
//2.用foreach遍历
foreach (object item in queue)
{
Console.WriteLine(item);
}
//3.转成object[]数组遍历
object[] arr = queue.ToArray();
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
//4.循环出队列
while (queue.Count > 0)
{
Console.WriteLine(queue.Dequeue());
}
class Test
{
}
队列的装箱拆箱
//装箱
queue.Enqueue(123);
//拆箱
int num = (int)queue.Dequeue();
和栈一样,用泛型 Queue<Type> 就可以避免装箱拆箱
习题

using System.Collections;
Queue queue = new Queue();
for (int i = 0; i < 10; i++)
{
queue.Enqueue(i);
}
while (queue.Count > 0)
{
Console.WriteLine(queue.Dequeue());
//隔停100毫秒
Task.Delay(100).Wait();
}
Hashtable
哈希表/散列表,本质是一个字典
是基于键的哈希代码组织起来的键值对 <key,value>
用键来访问集合中的元素
哈希表的使用
using System.Collections;
Hashtable hashtable = new Hashtable();
//增删查改
//增
//可以有相同value,但是不能有相同key
hashtable.Add(1, 123);
hashtable.Add("123", 321);
hashtable.Add(true, false);
//或者直接用索引器加,用索引器加相同key的时候相当于改了对应value
hashtable[1] = 123;
//删
//1.只能通过key来删
hashtable.Remove(1);
//2.删除不存在的键,不会报错
hashtable.Remove("1");
//3.清空
// hashtable.Clear();
//查
//1.通过key来查,找不到返回空
Console.WriteLine(hashtable[1]);
//2.通过key查是否存在键值对
if (hashtable.Contains("123")) Console.WriteLine("存在");
if (hashtable.ContainsKey("123")) Console.WriteLine("存在");
//3.通过value查是否存在键值对
if (hashtable.ContainsValue(321)) Console.WriteLine("存在");
//改
//只能改key对应的value,不能改key
hashtable[1] = 321;
Console.WriteLine(hashtable[1]);
//遍历
//键值对数
Console.WriteLine(hashtable.Count);
//通过key遍历:可以遍历key和value
foreach (var key in hashtable.Keys)
{
Console.WriteLine("key:{0},value:{1}",key ,hashtable[key]);
}
//通过value遍历:只能遍历value
foreach (var value in hashtable.Values)
{
Console.WriteLine("value:{0}",value);
}
//迭代器遍历键值对
foreach (var item in hashtable)
{
Console.WriteLine(item);
}
//迭代器遍历
IDictionaryEnumerator enumerator = hashtable.GetEnumerator();
bool flag = enumerator.MoveNext();
while (flag)
{
Console.WriteLine("key:{0},value:{1}",enumerator.Key,enumerator.Value);
flag = enumerator.MoveNext();
}
注意:哈希表的键值对排列顺序,取决于 key 的哈希码和冲突处理机制,并不是按照插入顺序排列的
关于迭代器:
foreach底层调用的就是
GetEnumerator()
哈希表的装箱拆箱
本质是object容器,字典,所以必然存在装箱拆箱
Hashtable table = new Hashtable();
// 装箱:int → object
table.Add(1, 100); // key 和 value 都是值类型,会装箱
// 拆箱:object → int
int key = 1;
int value = (int)table[key]; // 拆箱操作
用字典泛型Dictionary
<Type,Type>来避免装箱拆箱:
Dictionary<int, int> dict = new Dictionary<int, int>();
dict.Add(1, 100);//直接加
int value = dict[1]; // 直接取用
习题

using System.Collections;
for (int i = 0; i < 10; i++)
{
MonsterManager.Instance.AddMonster();
}
MonsterManager.Instance.RemoveMonster(1);
MonsterManager.Instance.RemoveMonster(5);
class MonsterManager
{
//要让管理器是唯一的 所以用单例模式来实现
private static MonsterManager _instance = new MonsterManager();
public static MonsterManager Instance
{
get
{
return _instance;
}
}
private Hashtable monsterTable = new Hashtable();
//不让在外面new
private MonsterManager()
{
}
private int monsterID = 0;
public void AddMonster()
{
Monster monster = new Monster(monsterID);
monsterTable.Add(monster.id, monster);
(monsterTable[monsterID] as Monster).Generate();
monsterID++;
}
public void RemoveMonster(int id)
{
if (monsterTable.ContainsKey(id))
{
(monsterTable[id] as Monster).Dead();
monsterTable.Remove(id);
}
}
}
class Monster
{
public int id;
public Monster(int id)
{
this.id = id;
}
public void Generate()
{
Console.WriteLine("生成怪物{0}", id);
}
public void Dead()
{
Console.WriteLine("怪物{0}死亡", id);
}
}
关于单例模式,这个在C# 核心里面提到过

这就是一个标准的单例模式书写,在外部不能实例化,只有类名.单例属性名.成员方法()才能调用

泛型
泛型的基本概念
- 泛型实现了类型参数化,用于代码复用
- 通过类型参数化来实现在同一份代码上操作多种类型
- 相当于类型占位符
- 定义类/方法的时候使用替代符来来代表变量类型
- 当真正使用类和方法时再具体制定类型
- 泛型占位符一般用大写字母
泛型的作用
- 不同类型对象的相同逻辑处理,可以选择泛型,提升代码的复用
- 使用泛型,可以一定程度避免装箱拆箱
- eg:自己写泛型类ArrayList
<T>来解决ArrayList存在的装箱拆箱问题、Stack<Type>、Queue<Type>、用字典Dictionary<T1,T2>实现Hashtable
泛型分类
语法
泛型类: class 类名<泛型占位字母>
泛型接口: interface 接口名<泛型占位字母>
泛型函数: 函数名<泛型占位字母>
泛型占位字母可以有多个,用逗号隔开
泛型类
class TestClass<T>
{
public T value;
}
//重载——多个泛型占位字母
class TestClass<T1,T2>
{
public T1 value;
public T2 value2;
}
class Program
{
static void Main(string[] args)
{
//类型占位符T可以用任意数据类型代替,这样就实现了类型的参数化
TestClass<int> t = new TestClass<int>();
t.value = 10;
TestClass<string> t2 = new TestClass<string>();
t2.value = "hello world";
TestClass<int, string> t3 = new TestClass<int, string>();
t3.value = 10;
t3.value2 = "111";
}
}
泛型接口
#region 泛型接口
interface TestInterface<T>
{
//接口只能有属性、方法、事件、索引器
T value { get; set; }
}
//在类中实现接口,因为是实现,所以必须在<>内注明数据类型
class Test : TestInterface<int>
{
public int value { get; set; }
}
#endregion
泛型方法(函数)
不确定泛型类型的时候可以用default(T)来获取默认值,然后在后面写函数逻辑
#region 普通类中的泛型方法
class Test2
{
public void TestFunc<T>(T value)
{
Console.WriteLine(value);
}
//无参
public void TestFunc<T>()
{
T t = default(T);
Console.WriteLine("{0}类型的默认值是{1}", typeof(T), t);
}
//占位符作为返回值类型
public T TestFunc<T>(string v)
{
return default(T);
}
//多个占位符
public void TestFunc<T, T2>(T v1, T2 v2)
{
}
}
#endregion
class Program
{
static void Main(string[] args)
{
//泛型方法
Test2 t4 = new Test2();
t4.TestFunc<int>(10);
t4.TestFunc<string>("hello world");
t4.TestFunc<double>();
Console.WriteLine(t4.TestFunc<int>("1"));
}
}
泛型类中的泛型方法
#region 泛型类中的泛型方法
class Test2<T>
{
public T value;
//函数名后没有<>,不是泛型方法
// 调用函数的时候,参数类型T已经被类的T定死,无法重新指定其数据类型
public void TestFunc(T v)
{
}
//函数名后有<>,才是泛型方法
// 括号里的参数类型T只与该函数的<T>一致,和类的T无关
public void TestFunc<T>(T v)
{
}
}
#endregion
class Program
{
static void Main(string[] args)
{
//泛型类中的泛型方法
Test2<int> t5 = new Test2<int>();
t5.TestFunc<int>(10);
t5.TestFunc<string>("hello world");
t5.TestFunc("111"); //编译器会自动推算出T的类型为string,但最好写上,不然可读性不高
}
}
习题

namespace 泛型习题;
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Test<int>());
}
static string Test<T>()
{
if (typeof(T) == typeof(int))
{
return String.Format("{0},{1}字节", typeof(T), sizeof(int));
}
else if (typeof(T) == typeof(double))
{
return String.Format("{0},{1}字节", typeof(T), sizeof(double));
}
else if (typeof(T) == typeof(float))
{
return String.Format("{0},{1}字节", typeof(T), sizeof(float));
}
else if (typeof(T) == typeof(char))
{
return String.Format("{0},{1}字节", typeof(T),sizeof(char));
}
else if (typeof(T) == typeof(string))
{
return String.Format("{0}", typeof(T));
}
else
{
return String.Format("其他类型");
}
}
}
泛型约束
泛型约束的基本概念
where 泛型字母:(约束的类型)
- 让泛型的类型有一定限制
- 关键字:
where - 泛型约束一共有6种
各泛型约束
| 值类型 | where 泛型字母:struct |
|---|---|
| 引用类型 | where 泛型字母:class |
| 存在无参公共构造函数 | where 泛型字母:new() |
| 类本身/子类 | where 泛型字母:类名 |
| 接口本身/接口的子类 | where 泛型字母:接口名 |
| 另一个泛型类型本身/其派生类型 | where 泛型字母:另一个泛型字母 |
namespace 泛型约束;
#region 各个泛型类型约束
//值类型约束
class Test1<T> where T : struct
{
public T value;
public void TestFunc<K>(K v) where K : struct
{
}
}
//引用类型约束
class Test2<T> where T : class
{
public T value;
public void TestFunc<K>(K v) where K : class
{
}
}
//公共无参构造约束
class Test3<T> where T : new()
{
public T value;
public void TestFunc<K>(K v) where K : new()
{
}
}
class Test1
{
}
class Test2
{
public Test2(int a)
{
}
}
class Test3
{
private Test3()
{
}
}
abstract class Test4
{
}
//类约束:某个类本身或其子类
class Test4<T> where T : Test1
{
public T value;
public void TestFunc<K>(K v) where K : Test1
{
}
}
class Test1_ : Test1
{
}
//接口约束:某个接口或者其子接口或其子类
interface IFly
{
}
interface IMove : IFly
{
}
class Test6 : IFly
{
}
class Test5<T> where T : IFly
{
public T value;
}
//另一个泛型约束
//前者必须是后者本身或其派生类型
class Test7<T, U> where T : U
{
public T value;
public void TestFunc<K, V>(K k) where K : V
{
}
}
#endregion
class Program
{
static void Main(string[] args)
{
//值类型
Test1<int> t = new Test1<int>();
t.TestFunc<bool>(true);
// Test1<object> t2 = new Test1<object>(); 错误
//引用类型
Test2<string> t2 = new Test2<string>();
t2.TestFunc<object>(new object());
//无参公共构造函数
Test3<Test1> t3 = new Test3<Test1>();
// Test3<Test2> t3 = new Test3<Test2>(); 错误,必须要有无参公共构造函数
// Test3<Test3> t3 = new Test3<Test3>(); 错误,必须要有无参公共构造函数
// Test3<Test4> t3 = new Test3<Test4>(); 错误,抽象类不行,因为抽象类不能new对象,只能在子类继承
Test3<int> t4 = new Test3<int>(); //正确,所有的值类型实际上都默认有一个无参构造
//类约束:某个类本身或其子类
Test4<Test1> t5 = new Test4<Test1>();
t5.TestFunc<Test1>(new Test1());
//Test1_是Test1的子类
Test4<Test1_> t6 = new Test4<Test1_>();
//接口约束
//接口本身
Test5<IFly> t7 = new Test5<IFly>();
t7.value = new Test6();
//接口的实现类(子类)
Test5<Test6> t8 = new Test5<Test6>();
//接口的子接口
Test5<IMove> t9 = new Test5<IMove>();
//另一个泛型约束
//同一类型
Test7<int, int> t10 = new Test7<int, int>();
Test7<Test1, Test1> t11 = new Test7<Test1, Test1>();
//前是后的派生类型
Test7<Test1_, Test1> t12 = new Test7<Test1_, Test1>();
Test7<Test6, IFly> t13 = new Test7<Test6, IFly>();
}
}
约束的组合使用
用 逗号连接两个约束,相当于多个约束条件
注意:
- 但不是每个都能组合起来使用,看报错
- new()一般写在最后
#region 约束的组合使用
//同时是引用类型且必须有无参构造函数
class Test8<T> where T : class, new()
{
}
class Test8_
{
}
#endregion
#region 约束的组合使用
Test8<Test8_> t14 = new Test8<Test8_>();
#endregion
多个泛型有约束
每个泛型字母都要对应一个 where
#region 多个泛型有约束
class Test9<T,U> where T : class, new() where U : struct
{
}
#endregion
习题

namespace 泛型约束习题;
//1. 泛型实现单例模式
class SingleBase<T> where T : new()
{
private static T _instance = new T();
public static T Instance
{
get
{
return _instance;
}
}
}
class Test : SingleBase<Test>
{
}
//2. 泛型实现一个不确定类型的ArrayList
class ArrayList<T>
{
private T[] array;
public void Add(T value)
{
//...
}
public void RemoveAt(int index)
{
//...
}
public void Remove(T value)
{
//...
}
public T this[int index]
{
get
{
return array[index];
}
set
{
array[index] = value;
}
}
}
常用泛型数据结构类型
List——列表,泛型ArrayList
本质:一个可变类型的泛型数组,也就是泛型实现的ArrayList
类型在申明时就确定好,所以不存在装箱拆箱
List的申明
using System.Collections.Generic;
//申明
List<int> list = new List<int>();
List的增删查改遍历
和ArrayList一样
using System.Collections.Generic;
//申明
List<int> list = new List<int>();
//增删查改
#region 增
//单个加
list.Add(1);
list.Add(2);
List<int> list2 = new List<int>();
list2.Add(1);
//范围加
list.AddRange(list2);
//在指定位置插入
list.Insert(0, 999);
#endregion
#region 删
//移除指定元素
list.Remove(1);
//移除指定位置元素
list.RemoveAt(0);
//清空
list.Clear();
#endregion
list.Add(1);
list.Add(2);
list.Add(3);
#region 查
//得到指定位置元素
Console.WriteLine(list[0]);
//元素是否存在
Console.WriteLine(list.Contains(1));
//正向查找元素位置
//找不到返回-1
Console.WriteLine(list.IndexOf(1));
Console.WriteLine(list.IndexOf(0));
//反向查找元素位置,返回的也是从左往右数的位置,只是从末尾开始遍历
//找不到返回-1
Console.WriteLine(list.LastIndexOf(1));
Console.WriteLine(list.LastIndexOf(0));
#endregion
#region 改
list[0] = 999;
#endregion
Console.WriteLine();
#region 遍历
Console.WriteLine(list.Count);
Console.WriteLine(list.Capacity);
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
foreach (var item in list)
{
Console.WriteLine(item);
}
#endregion
List和ArrayList的区别
List就是在申明时就确定好类型的ArrayList
| List | ArrayList | |
|---|---|---|
| 内部封装 | 泛型数组 不存在装箱拆箱 |
object数组 |
Dictionary——字典,泛型哈希表
本质:泛型实现的Hashtable,也是基于键的哈希代码组织起来的键值对
键值对的类型在申明时就确定好,所以不存在装箱拆箱
Dictionary的申明
using System.Collections.Generic;
//申明
Dictionary<int, string> dictionary = new Dictionary<int, string>();
Dictionary的增删查改遍历
using System.Collections.Generic;
//申明
Dictionary<int, string> dictionary = new Dictionary<int, string>();
//增删查改
#region 增
dictionary.Add(1, "111");
dictionary[2] = "222";
#endregion
#region 删
//1.通过键删除
//删除不存在的键,不报错
dictionary.Remove(1);
//2.清空
dictionary.Clear();
#endregion
dictionary.Add(1, "111");
dictionary.Add(2, "222");
dictionary.Add(3, "333");
#region 查
//1.通过键查询
//找不到键就报错,不返回空
Console.WriteLine(dictionary[1]);
//2.查看是否存在
//根据key
Console.WriteLine(dictionary.ContainsKey(1));
//根据value
Console.WriteLine(dictionary.ContainsValue("222"));
#endregion
#region 改
dictionary[2] = "9999";
#endregion
//遍历
Console.WriteLine(dictionary.Count);
//一起遍历
foreach (var item in dictionary)
{
Console.WriteLine(item.Key + ":" + item.Value);
}
foreach (KeyValuePair<int,string> item in dictionary)
{
Console.WriteLine(item);
}
//遍历key
foreach (var item in dictionary.Keys)
{
Console.WriteLine(item);
}
//遍历value
foreach (var item in dictionary.Values)
{
Console.WriteLine(item);
}
顺序存储和链式存储
顺序结构:数组、ArrayList、Stack、Queue、List
链式结构:链表(单向、双向、循环)
LinkedList——泛型双向链表
本质:一个可变类型的泛型双向链表
链表的节点类LinkedListNode <T>

LinkedList申明
//申明
LinkedList<int> linkedList = new LinkedList<int>();
LinkedList<string> linkedList2 = new LinkedList<string>();
LinkedList的增删查改和遍历
//申明
LinkedList<int> linkedList = new LinkedList<int>();
LinkedList<string> linkedList2 = new LinkedList<string>();
//增删查改
#region 增
//头插
linkedList.AddFirst(1);
//尾插
linkedList.AddLast(99);
//在指定节点后插入
LinkedListNode<int> n = linkedList.Find(1);
linkedList.AddAfter(n, 2);
//在指定节点前插入
linkedList.AddBefore(n, 0);
#endregion
#region 删
//删除头节点
linkedList.RemoveFirst();
//删除尾节点
linkedList.RemoveLast();
//删除指定值的节点
//无法通过位置删除,因为链表没有办法直接获取索引
linkedList.Remove(99);
//清空
linkedList.Clear();
#endregion
linkedList.AddLast(1);
linkedList.AddLast(2);
#region 查
//获取头节点
LinkedListNode<int> first = linkedList.First;
//获取尾节点
LinkedListNode<int> last = linkedList.Last;
//获取指定值的节点
LinkedListNode<int> node = linkedList.Find(1);
Console.WriteLine(node.Value);
//判断是否存在
Console.WriteLine(linkedList.Contains(1));
#endregion
#region 改
Console.WriteLine(node.Value);
node.Value = 3;
Console.WriteLine(node.Value);
#endregion
#region 遍历
//迭代器
foreach (var item in linkedList)
{
Console.WriteLine(item);
}
//通过节点遍历:因为本质是双向链表,所以存在正序和倒序遍历
//1.正序遍历
LinkedListNode<int> nowNode = linkedList.First;
while (nowNode != null)
{
Console.WriteLine(nowNode.Value);
nowNode = nowNode.Next;
}
//2.倒序遍历
nowNode = linkedList.Last;
while (nowNode != null)
{
Console.WriteLine(nowNode.Value);
nowNode = nowNode.Previous;
}
#endregion
泛型栈和队列
前面介绍栈和队列的时候有装箱拆箱的问题,在其解决方法中我已提过引入泛型来解决
泛型栈
Stack
Stack<int> stack = new Stack<int>();
泛型队列
Queue <T> queue
Queue<int> queue = new Queue<int>();
其内置方法和之前的栈和队列完全一样
总结:上述各种数据容器的适用场景
数组、List、Dictionary, Stack, Queue, LinkedList
| 数据结构 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
数组 |
固定长度 | 连续内存存储,支持下标访问,性能高 | 数据量固定、频繁通过下标访问的场景 |
List<T> |
动态数组 | 可变长度,支持下标访问,插入/删除效率较低 | 需要频繁修改内容但又需要通过索引快速查找的场景 |
LinkedList<T> |
双向链表 | 插入/删除效率高,不支持下标访问 | 不确定长度,频繁在中间插入或删除元素的场景 |
Stack<T> |
后进先出 | 入栈(Push)、出栈(Pop)、查看栈顶(Peek) | 实现递归算法、撤销/重做机制、UI面板显隐规则等 |
Queue<T> |
先进先出 | 入队(Enqueue)、出队(Dequeue) | 消息队列、任务调度、事件处理等需按顺序处理的场景 |
Dictionary<K,V> |
键值对集合 | 快速通过键查找值,不允许重复键 | 存储具有唯一标识的数据,如ID-对象映射、配置项、资源管理等 |
委托和事件
委托
委托是专门装载函数的容器,也就是函数的变量类型
用来存储、传递函数
本质:是一个类,用来定义函数的类型(返回值和参数的类型)
不同的函数对应和各自“格式"一致的委托
委托的申明和使用
关键字:delegate
位置:nameplace、class语句块中,一般写在nameplace中
访问修饰符:一般用public,默认不写就是public
语法:访问修饰符 delegate 返回值 委托名(参数列表)
namespace 委托;
//委托的申明,统一语句块中不能重名
delegate void MyFunc();
public delegate int MyFunc2(int a);
class Program
{
static void Main(string[] args)
{
//委托是专门装载函数的容器
//把格式一样(无参无返回值)的方法Fun装进了MyFunc的对象f里面
//两种存放写法
MyFunc f = new MyFunc(Fun);
MyFunc f2 = Fun;
//调用委托对象f存放的方法
//两种调用写法
f.Invoke();
f2();
//注意:格式必须一样才能装载
MyFunc2 f3 = new MyFunc2(Fun2);
Console.WriteLine(f3(1));
}
static void Fun()
{
Console.WriteLine("Fun");
}
static int Fun2(int value)
{
return value;
}
}
泛型委托
//泛型委托
delegate T MyFunc3<T, K>(T t, K k);
使用定义好的委托——观察者设计模式
#region 使用定义好的委托
//委托常用在:
//1.作为类的成员
//2.作为函数的参数
class Test
{
public MyFunc func;
public MyFunc2 func2;
public void TestFunc(MyFunc func, MyFunc2 func2)
{
//观察者设计模式
//先处理一些逻辑,后 存放/延迟执行 传入的函数
int i = 0;
i++;
//延迟执行传入的函数
//func();
//func2(i);
//存放传入的函数
this.func = func;
this.func2 = func2;
}
}
#endregion
class Program
{
static void Main(string[] args)
{
#region 使用定义好的委托
Test t = new Test();
t.TestFunc(Fun, Fun2);
#endregion
}
static void Fun()
{
Console.WriteLine("Fun");
}
static int Fun2(int value)
{
return value;
}
}
委托变量存储多个函数——加、减、清空
class Test
{
public MyFunc func;
public MyFunc2 func2;
public void TestFunc(MyFunc func, MyFunc2 func2)
{
//观察者设计模式
//先处理一些逻辑,后 存放/延迟执行 传入的函数
int i = 0;
i++;
//延迟执行传入的函数
//func();
//func2(i);
//存放传入的函数
this.func = func;
this.func2 = func2;
}
#region 委托变量存储多个函数
//同样,需要格式一致才能装载
//增 +=
public void AddFunc(MyFunc func, MyFunc2 func2)
{
this.func += func;
this.func2 += func2;
}
//删 -=
public void RemoveFunc(MyFunc func, MyFunc2 func2)
{
this.func -= func;
this.func2 -= func2;
}
#endregion
}
#region 委托变量存储多个函数
//同样,需要格式一致才能装载
//增 +=
// MyFunc ff = Fun;
// ff += Fun3;
// ff();
//或者:先赋值为null,再+=
MyFunc ff = null;
ff += Fun;
ff += Fun3;
ff();
t.AddFunc(Fun, Fun2);
t.func();
//删 -=
ff -= Fun;
//多删不会报错
ff -= Fun;
ff();
ff -= Fun3;
//删完会报错
// ff(); 删完,ff为null,调用会报错
//清空委托容器
// ff = null;
if (ff != null) ff();
#endregion
系统定义好的委托
#region 系统定义好的委托容器
//无参无返回 —— Action
Action action = Fun;
action += Fun3;
action();
//n个参数无返回,最多支持传入16个参数 —— Action<T1,T2,T3...T16>
Action<int, string> actions = Fun6;
actions(1, "111");
//无参有返回的泛型委托 —— Func<T>
Func<string> funcString = Fun4;
Func<int> funcInt = Fun5;
//n个参数有返回,最多支持传入16个参数 —— Func<T1,T2,T3...T16,TResult>
//注意:参数的类型写前面,返回值的类型写后面
Func<int, string> funcs = Fun7; //参数是int,返回值是string
#endregion
static void Fun()
{
Console.WriteLine("这是Fun方法");
}
static void Fun3()
{
Console.WriteLine("这是Fun3方法");
}
static int Fun2(int value)
{
return value;
}
static string Fun4()
{
return "这是Fun4方法";
}
static int Fun5()
{
return 5;
}
static void Fun6(int value, string value2)
{
}
static string Fun7(int value)
{
return "这是Fun7方法";
}
习题

namespace 委托练习题
{
class Program
{
abstract class Person
{
public abstract void Eat();
}
class Mom : Person
{
public Action beginEating;
public override void Eat()
{
Console.WriteLine("Mom eats.");
}
public void MakeFood()
{
Console.WriteLine("Mom makes food.");
Console.WriteLine("Mom makes food done.");
if(beginEating!= null)
{
beginEating();
}
}
}
class Dad : Person
{
public override void Eat()
{
Console.WriteLine("Dad eats.");
}
}
class Child : Person
{
public override void Eat()
{
Console.WriteLine("Child eats.");
}
}
static void Main(string[] args)
{
Mom mom = new Mom();
Dad dad = new Dad();
Child child = new Child();
//做好了通知所有人
mom.beginEating += dad.Eat;
mom.beginEating += child.Eat;
mom.beginEating += mom.Eat;
mom.MakeFood();
}
}
}
namespace 委托练习题2
{
class Program
{
public class Monster
{
public Action<Monster> deadDoing;
public int money = 10;
public void Die()
{
if (deadDoing!= null)
{
deadDoing(this);
}
//死了之后清空委托
deadDoing = null;
}
}
public class Player
{
private int myMoney;
public void DoingWhenMonsterDie(Monster monster)
{
myMoney += monster.money;
Console.WriteLine("I have " + myMoney + " coins.");
}
}
public class UI
{
private int showMoney;
public void DoingWhenMonsterDie(Monster monster)
{
showMoney += monster.money;
Console.WriteLine("UI:Player got " + monster.money + " coins.");
}
}
public class Chengjiu
{
private int showKillCount;
public void DoingWhenMonsterDie(Monster monster)
{
showKillCount += 1;
Console.WriteLine("Chengjiu:Player killed " + showKillCount + " monsters.");
}
}
static void Main(string[] args)
{
Monster monster = new Monster();
Player player = new Player();
UI ui = new UI();
Chengjiu chengjiu = new Chengjiu();
monster.deadDoing += player.DoingWhenMonsterDie;
monster.deadDoing += ui.DoingWhenMonsterDie;
monster.deadDoing += chengjiu.DoingWhenMonsterDie;
monster.Die();
}
}
}
事件
事件是委托的安全包裹,让委托的使用更加安全
这是一种特殊的变量类型
申明语法:访问修饰符 event 委托类型 事件名
作用:
- 作为成员变量存在于类、接口、结构体中
- 委托怎么用,事件就怎么用
事件和委托的区别:事件不能在类的外部赋值、调用,只能在内部封装起来调用,所以更安全
事件的申明
//委托的申明
public Action myFun;
//事件的申明
public event Action myEvent;
事件的使用
namespace 事件;
class Test
{
//委托的申明
public Action myFun;
//事件的申明
public event Action myEvent;
public Test()
{
//二者使用完全一样
myFun = TestFun;
myFun += TestFun;
myFun();
myFun.Invoke();
myFun = null;
myEvent = TestFun;
myEvent += TestFun;
myEvent();
myEvent.Invoke();
myEvent = null;
}
public void TestFun()
{
Console.WriteLine("TestFun");
}
//事件只能在类内部被封装调用,所以会更安全
public void DoEvent()
{
if(myEvent != null) myEvent();
}
}
class Program
{
static void Main(string[] args)
{
Test t = new Test();
//委托可以在外部赋值、调用
t.myFun = Func;
t.myFun = null;
t.myFun();
t.myFun.Invoke();
//委托不能在类的外部赋值、调用
// t.myEvent = Func; 错误
//委托可以在外部+=,-=
t.myEvent += Func;
// t.myEvent(); 错误
//委托可以作为临时变量存放函数
Action f = Func;
//事件不能作为临时变量存放函数
// event Action e = Func; 错误
}
static void Func()
{
}
}
总结:为什么要用事件?
- 防止外部随意置空、调用委托
- 事件相当于对委托又进行了一个封装,让他更加安全,只能在类的内部封装调用
匿名函数
Lambda表达式
List排序
协变逆变
多线程
预处理器指令
反射和特性
程序集:代码集合会被编译器编译为一个程序集,在windows中,就是后缀.dll库文件和.exe可执行文件
元数据:用来描述数据的数据。程序中的类,类中的成员 就是程序的元数据。
反射
反射:在程序运行时,通过反射可以得到其他或自身程序集代码的元数据。
——也就是通过反射可以得到类、函数、变量、对象,然后实例化、调用他们
反射的作用:
- 反射可以在程序编译后获得信息,所以反射提高了程序的拓展性、灵活性
- 程序运行时,可以得到所有元数据及其特性、可以实例化对象、操作对象、创建新对象
——也就是通过反射,可以直接去调用另一个程序集里写好的代码的各种信息
反射是unity脚本工作的基本原理
namespace 反射;
class Test
{
private int i = 1;
public int j = 2;
public string str = "123";
public Test() { }
public Test(int i)
{
this.i = i;
}
public Test(int i, string str) : this(i)
{
this.str = str;
}
public void Speak()
{
Console.WriteLine(i);
}
}
Type——类的信息类
Type是反射的基础,是访问元数据的主要方式
使用Type的成员获取有关类型申明的信息(构造函数、方法、字段、属性、类的事件)
三种获取Type的方式:
//获取Type:
// 1.object中的.GetType()获取对象的Type
int a = 42;
Type type = a.GetType();
Console.WriteLine(type);
// 2.通过typeof关键字,传入类名,获取对象的Type
Type type2 = typeof(int);
Console.WriteLine(type2);
// 3.通过类名,获取对象的Type
Type type3 = Type.GetType("System.Int32");
Console.WriteLine(type3);
每一个类型的Type是唯一的,所以虽然有三个Type变量来存,但这里指向堆内存里的地址是一样的
获取类的程序集信息:
//得到类的程序集信息
// 通过Type变量名.Assembly得到类型所在程序集信息
Console.WriteLine(type.Assembly);
获取类中的所有公共成员:
//获取类中的所有公共成员:
// 先得到类的Type
Type t = typeof(Test);
// 用.GetMembers()获取类中的所有公共成员,需要先using System.Reflection;
MemberInfo[] infos = t.GetMembers();
foreach (var info in infos)
{
Console.WriteLine(info);
}
获取类的公共构造函数并调用:
//获取类的公共构造函数并调用:
// 1.用.GetConstructors()获取所有构造函数
ConstructorInfo[] cis = t.GetConstructors();
foreach (var ci in cis)
{
Console.WriteLine(ci);
}
// 2.用GetConstructor()获取其中一个构造函数并用.Invoke()执行
//步骤如下:
//得构造函数,传入Type数组,数组中的内容按顺序填入typeof(构造函数参数类型)
//执行构造函数,传入object数组,数组中按顺序传入参数
// 无参构造
// 得到无参构造,直接传参new Type[0]
ConstructorInfo info1 = t.GetConstructor(new Type[0]);
// 用.Invoke()执行无参构造,传参null
Test obj = info1.Invoke(null) as Test;
Console.WriteLine(obj.j);
// 有参构造
// 得到有参构造,直接传参new Type[]{typeof( )}
ConstructorInfo info2 = t.GetConstructor(new Type[] { typeof(int) });
obj = info2.Invoke(new object[] { 1 }) as Test;
Console.WriteLine(obj.str);
ConstructorInfo info3 = t.GetConstructor(new Type[] { typeof(int), typeof(string) });
obj = info3.Invoke(new object[] { 1, "fuck" }) as Test;
Console.WriteLine(obj.str);
获取类的公共成员变量:
//获取类的公共成员变量:
// 1.用.GetFields()得到所有公共成员变量,存在FieldInfo[]数组中
FieldInfo[] fields = t.GetFields();
foreach (FieldInfo field in fields)
{
Console.WriteLine(field);
}
// 2.用.GetField("公共成员变量名")得到指定名称的公共成员变量,存在FieldInfo中
FieldInfo infoJ = t.GetField("j");
Console.WriteLine(infoJ);
FieldInfo infoStr = t.GetField("str");
Console.WriteLine(infoStr);
// 3.通过反射获取和设置对象的值
Test test = new Test();
test.j = 99;
test.str = "2222";
// 通过反射,用 对象的某个成员变量名.GetValue(对象名) 获取对象的某个成员变量的值
Console.WriteLine(infoJ.GetValue(test));
// 通过反射,用 对象的某个成员变量名.SetValue(对象名, 要设置的值) 设置指定对象的某个成员变量的值
infoJ.SetValue(test, 999);
Console.WriteLine(infoJ.GetValue(test));
获取类的公共成员方法:
//获取类的公共成员方法:
//通过Type类的GetMethod()方法,获取类中的公共方法,存在MethodInfo中
Type strType = typeof(string);
MethodInfo[] methodInfos = strType.GetMethods();
foreach (MethodInfo methodInfo in methodInfos)
{
Console.WriteLine(methodInfo);
}
// 1.如果有方法重载,用Type数组表示参数类型
MethodInfo subStr = strType.GetMethod("Substring",
new Type[] { typeof(int), typeof(int) });
// 2.调用该方法
// 注意:如果是静态方法,Invoke()中的第一个参数要传入null
string str = "1234567890";
object result = subStr.Invoke(str, new object[] { 2, 4 });
Console.WriteLine(result);
其他获取:
//其他获得:
// 得枚举:GetEnumName
// 得事件:GetEvent
// 得属性:GetProperty
// 得接口:GetInterface
Activator——快速实例化对象的类
将Type对象快捷实例化为对象
#region Activator
//先得到Type
Type testType = typeof(Test);
//1.无参构造Activator.CreateInstance(Type对象)
Test testObject = Activator.CreateInstance(testType) as Test;
Console.WriteLine(testObject.str);
//2.有参构造Activator.CreateInstance(Type对象,参数)
testObject = Activator.CreateInstance(testType, 99) as Test;
Console.WriteLine(testObject.j);
testObject = Activator.CreateInstance(testType, 99, "111111") as Test;
Console.WriteLine(testObject.str);
#endregion
Assembly——程序集类
用来加载其他程序集
#region Assembly
//三种加载程序集的方法
//1.Load("程序集名称") 同一文件下的其他程序集
Assembly a1 = Assembly.Load("System");
//2.LoadFrom("程序集绝对路径") 加载一个完整的程序集及其依赖
//注意:可以用反转义字符@
Assembly a2 = Assembly.LoadFrom("/Users/User/Desktop/CSharp/CSharp_advanced/反射/bin/Debug/net7.0/反射.dll");
//3.LoadFile("程序集绝对路径") 仅加载指定文件中的程序集,不处理依赖
Assembly a3 = Assembly.LoadFile("/Users/User/Desktop/CSharp/CSharp_advanced/Lesson18_练习题/bin/Debug/netcoreapp3.1/Lesson18_练习题.dll");
Type[] types = a3.GetTypes();
foreach (Type t in types)
{
Console.WriteLine(t);
}
//加载完程序集中的类对象,下面使用反射
Type icon = a3.GetType("Lesson18_练习题.Icon");
MemberInfo[] members = icon.GetMembers();
foreach (MemberInfo m in members)
{
Console.WriteLine(m);
}
//通过反射实例化
//这是一个枚举Type
Type moveDir = a3.GetType("Lesson18_练习题.E_MoveDir");
FieldInfo right = moveDir.GetField("Right");
//直接实例化对象
object iconObj = Activator.CreateInstance(icon, 10, 5, right.GetValue(null));
//通过反射得到对象中的方法
MethodInfo move = icon.GetMethod("Move");
MethodInfo draw = icon.GetMethod("Draw");
MethodInfo clear = icon.GetMethod("Clear");
Console.Clear();
while (true)
{
Thread.Sleep(1000);
clear.Invoke(iconObj, null);
move.Invoke(iconObj, null);
draw.Invoke(iconObj, null);
}
#endregion
习题


浙公网安备 33010602011771号