C#进阶学习

发布时间:2024年01月13日


请学完 C#核心学习再来学习进阶。

简单数据结构类

ArrayList

ArrayList是一个C#为我们封装好的类,它的本质是一个object类型的数组。

声明

using System.Collections;//需要引用命名空间
ArrayList array = new ArrayList();

增删查改

array.Add(1);
array.Insert(1,"123");

array.Remove(1);//移除指定元素,从头往后找
array.RemoveAt(2);//移除指定位置的元素
array.Clear();//清空

array[0];//得到指定位置的元素
array.Contains("123");//查看元素是否存在
array.IndexOf(1);//正向查找元素位置,找到返回位置,找不到返回-1
array.LastIndexOf(1);//反向查找元素位置(返回从头开始的索引数)

array[0] = "999";//改

遍历

//长度
array.Count
//容量(避免产生过多的垃圾)
array.Capacity
//普通遍历
for(int i = 0;i < array.Count;i++)
{
	Console.WriteLine(array[i]);
}
//迭代器遍历
foreach(object item in array)
{
	Console.WriteLine(item);
}

装箱拆箱

ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来存储数据,自然存在装箱拆箱。当往其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。所以ArrayList尽量少用,之后会有更好的数据容器。

int i = 1;
array[0] = i;//装箱
i = (int)array[0];//拆箱

Stack

Stack是一个C#为我们封装好的类,它的本质也是object[]的数组,只是封装了特殊的存储规则。stack时栈存储容器,栈是一种先进后出数据结构,先存入的数据后获取,后存入的数据先获取。

声明

using System.Collections;
Stack stack = new Stack();

增取查改

//入栈
stack。Push(1);

//出栈
object v = stack.Pop();

//看栈顶
stack.Peek();
//查看元素是否存在于栈中
stack.Contains("123");

//清空
stack.Clear();

遍历

//长度
stack.Count;
//用foreach遍历(从栈顶到栈底)
foreach(object item in stack)
{
	Console.WriteLine(item);
}
//另类遍历方式:将栈转换为object数组(从栈顶到栈底)
Object[] array = stack.ToArray();
for(int i = 0;i < array.Length;i++)
{
	Console.WriteLine(array[i]);
}
//循环弹栈
while(stack.Count > 0)
{
	object o = stack.Pop();
	Console.WriteLine(o);
}

装箱拆箱

同ArrayList。

Queue

Queue是一个c#为我们封装好的类,它的本质也是object[]的数组,只是封装了特殊的存储规则。Queue是队列存储容器,队列是一种先进先出的数据结构,先存入的数据先获取,后存入的数据后获取。

声明

System.Collections
Queue queue = new Queue();

增取查改

queue.Enqueue(1);//增

object v = queue.Dequeue();//取

queue。Peek()//查
queue.Contains(1)//查看元素是否存在于队列中

queue.Clear();//改

遍历

queue.Count//长度
//foreach遍历
foreach(object item in queue)
{
	Console.WriteLine(item);
}
//将队列转换为数组遍历
object[] array = queue.ToArray();
for(int i = 0;i < array.Length;i++)
{
	Console.WriteLine(array[i]);
}
//循环出列
while(queue.Count > 0)
{
	object o = queue.Dequeue();
	Console.WriteLine(o);
}
	

Hashtable

Hashtable(又称散列表)是基于键的哈希代码组织起来的键/值对,它的主要作用是提高数据查询的效率,使用键来访问集合中的元素。

声明

System.Collections
Hashtable hashtable = new Hashtable();

增删查改

hashtable.Add(1,"123");//增

hashtable.Remove()//只能通过键去删除
hashtable.Clear()//直接清空

hashtable[1]//查
//查看是否存在
hashtable.Contains(1)//根据键检测
hashtable.ContainsKey(2)
hashtable.ContainsValue(12)//根据值检测

hashtable[1] = "100.5f";//改

遍历

hashtable.Count//对数
//遍历所有键
foreach(object item in hashtable.Keys)
{
	Console.WriteLine(item);
	Console.WriteLine(hashtable[item]);
}
//遍历所有值
foreach(object item in hashtable.Values)
{
	Console.WriteLine(item);
}
//键值对一起遍历
foreach(DictionaryEntry item in hashtable)
{
	Console.WriteLine(item.Key);
	Console.WriteLine(item.Value);
}
//迭代器遍历法
IDictionaryEnumerator item = hashtable.GetEnumerator();
bool flag = item.MoveNext();
while(flag)
{
	Console.WriteLine(item.Key);
	Console.WriteLine(item.Value);
	flag = item.MoveNext();
}

装箱拆箱

同ArrayList

泛型

泛型实现了类型参数化,达到代码重用的目的,通过类型参数化来实现同一份代码上操作多种类型。泛型相当于类型占位符,定义类或方法使用替代符代表变量类型,当真正使用类或者方法时再具体指定类型

泛型分类

泛型类:

class 类名<泛型占位字母>
class TestClass<T>
{
	public T value;
}

泛型接口:

interface 接口名<泛型占位字母>
interface TestInterface<T>
{
	T Value
	{
		get;
		set;
	}
}

泛型函数:

函数名<泛型占位字母>(参数列表)
//普通类中的泛型方法
class Test2
{
	public void TestFun<T>(T value)
	{
		Console.WriteLine(value);
	}
}
//泛型类中的泛型方法
class Test2<T>
{
	public void TestFun<K>(K k)
	{
		
	}
	//这不是泛型方法
	public void TestFun(T t)
	{
	}
}
//调用
Test2<int> tt2 = new Test2<int>();
tt2.TestFun(10);
tt2.TestFun<string>("123");
tt2.TestFun<float>(1.2f);

注意: 泛型占位字母可以有多个,用逗号分开

泛型的作用

1、不同类型对象的相同逻辑处理就可以选择泛型
2、使用泛型可以一定程度避免装箱拆箱

泛型约束

让泛型的类型有一定的限制。
关键字: where
类型:

where 泛型字母:(约束的类型)

1、值类型

while 泛型字母:struct
class Test<T> where T:struct
{
	public T value;
	public void TestFun<k>(K v) where K:struct
	{
	}
}

2、引用类型

where 泛型字母:class
class Test<T> where T:class
{
	public T value;
	public void TestFun<k>(K v) where K:class
	{
	}
}

3、存在无参公共构造函数

where 泛型字母:new()
class Test<T> where T:new()
{
	public T value;
	public void TestFun<k>(K v) where K:new()
	{
	}
}

4、某个类本身或者其派生类

where 泛型字母:类名
class Test<T> where T:Test1
{
	public T value;
	public void TestFun<k>(K v) where K:Test1
	{
	}
}

5、某个接口的派生类型

where 泛型字母:接口名
class Test<T> where T:IFly
{
	public T value;
	public void TestFun<k>(K v) where K:IFly
	{
	}
}

6、另一个泛型类型本身或者派生类型

where 泛型字母:另一个泛型字母
class Test<T,U> where T:U
{
	public T value;
	public void TestFun<k,V>(K v) where K:V
	{
	}
}

泛型组合使用:

class Test<T> where T:class,new()
{
}

多个泛型有约束:

class Test<T,K> where T:class,new() where K:struct
{
}

常用泛型数据结构类

List

List是一个c#为我们封装好的类,它的本质是一个可变类型的泛型数组,List类帮助我们实现了很多方法,如泛型数组的增删查改。

声明

using System.Collections.Generic
List<int> list = new List<int>();
List<string> list2 = new List<string>();

增删查改

list.Add(1);//增

List<string> lisStr = new List<string>();
listStr.Add("123");
list2.AddRange(lisStr);
list.Remove(1);//移除指定元素
list.Remove(0);//移除指定位置的元素
list.Clear();//清空

list[0];//得到指定位置的元素
list.Contains(1);//查看元素是否存在
int index = list.IndexOf(2);//正向查找元素位置,找不到返回-1
index = list.LastIndexOf(2);//反向查找元素位置,找不到返回-1

list[0] = 99;//改

遍历

list.Count;//长度
list.Capacity;//容量,避免产生垃圾
//foreach遍历
foreach(int item in list)
{
	Console.WriteLine(item);
}

Dictionary

可以将Dictionary理解为拥有泛型的hashtable,它也是基于键的哈希代码组织起来的键值对。键值对类型从Hashtable的object变为了可以自己制定的类型。

声明

using System.Collections.Generic
Dictionary<int,string> dictionary = new Dictionary<int,string>();

增删查改

dictionary.Add(1,"123");//增

dictionary.Remove(1);//只能通过键去删除,删除不存在的没反应
dictionary.Clear();//清空

dictionary[2];//通过键查看值,找不到直接报错
dictionary.ContainsKey(1);//根据键查找
dictionary.ContainsValue("123");//根据值查找

dictionary[1] = "555";//改

遍历

//遍历所有键
foreach(int item in dictionary.Keys)
{
	Console.WriteLine(item);
	Console.WriteLine(dictionary[item]);
}
//遍历所有值
foreach(string item in dictionary.Values)
{
	Console.WriteLine(item);
}
//键值对一起遍历
foreach(KeyValuePair<int,string> item in dictionary)
{
	Console.WriteLine(item.Key);
	Console.WriteLine(item.Value);
}

LinkedList

LinkedList是一个C#为我们封装好的类,它的本质是一个可变类型的泛型双向链表。

声明

using System.Collections.Generic
LinkedList<int> linkList = new LinkedList<int>();

增删查改

linkedList.AddLast(10);//在链表尾部添加元素
linkedList.AddFirst(20);//在链表头部添加元素
//在某一个节点之后添加一个结点
LinkedListNode<int> n = linkedList.Find(20);
linkedList.AddAfter(n,15);
//在某一个节点之前添加一个节点
LinkedListNode<int> n = linkedList.Find(20);
linkedList.AddBefore(n,15);

linkedList.RemoveFirst();//移除头节点
linkedList.RemoveLast();//移除尾结点
linkedList.Remove(20);//移除指定结点
linkedList.Clear();//清空

LinkedListNode<int> first = linkedList.First;//头节点
LinkedListNode<int> last = linkedList.Last;//尾结点
LinkedListNode<int> node = linkedList.Find(3);//找到指定值的结点
linkedList.Countains(1);//判断是否存在

linkedList.First.Value = 10;//改

遍历

//foreach遍历
for(int item in LinkedList)
{
	Console.WriteLine(item);
}
//通过节点遍历
LinkedListNode<int> nowHead = linkedList.First;
while(nowHead != null)
{
	Console.WriteLine(nowNode.Value);
	nowNode = nowNode.Next;
}

泛型栈和队列

using System.Collections.Generic
Stack<int> stack = new Stack<int>();
Queue<int> queue = new Queue<int>();

使用上和stack和queue一毛一样。

委托

委托是函数的容器,可以理解为表示函数的变量类型,用来存储、传递函数。委托本质上是一个类,用来定义函数的类型(返回值和参数的类型),不同的函数必须对应和各自“格式”一致的委托。

基本语法

关键字: delegate
可以声明在namespace和class语句块中。

访问修饰符 delegate 返回值 委托名(参数列表);

定义自定义委托

1、访问修饰默认不写为public,在别的命名空间中也能使用。
2、委托的规则的声明是不能重名的(在同一语句块中)

delegate void MyFun();
public delegate int MyFun2(int a);

声明委托

static void Fun()
{

}
MyFun f = new MyFun(Fun);
f.Invoke();//调用
MyFun f2 = Fun;
f2();//调用

使用定义好的委托

1、作为类的成员
2、作为函数的参数

class Test
{
	public MyFun fun;
	public MyFun2 fun2;
	public void TestFun(MyFun fun,MyFun2 fun2)
	{
	}
	public void AddFun(MyFun fun,MyFun2 fun2)
	{
		this.fun += fun;
		this.fun2 += fun2;
	}
	public void RemoveFun(MyFun,MyFun2 fun2)
	{
		this.fun -= fun;
		this.fun2 -= fun2;
	}
}  

委托变量可以存储多个函数(多播委托)

MyFun ff = Fun;
ff += Fun;//ff存储了两个Fun,说明Fun被执行了两次
ff();

系统定义好的委托

//无参无返回值
using System;
Action action = Fun;
action += Fun3;
action();

//可以指定返回值类型的泛型委托
static string Fun4()
{
	return "";
}
Func<string> funString = Fun4;

//可以传n个参数的 系统提供了1到16个参数的委托
Action<int,string,bool> action2 = Fun6;

//可以传n个参数的并且有返回值的,系统提供了16个委托
Func<int,int> func2 = Fun2;

事件

事件是基于委托的存在,事件是委托的安全包裹,让委托的使用更具有安全性。事件是一种特殊的变量类型。

事件的使用

访问修饰符 event 委托类型 事件名;
class Test
{
	public Action myFun;
	public event Action myEvent;
	public Test()
	{
		myFun = TestFun;
		myFun += TestFun;
		myFun -= TestFun;
		myFun();
		myFun.Invoke();
		myFun = null;
		
		myEvent = TestFun;
		myEvent += TestFun;
		myEvent -= TestFun;
		myEvent();
		myEvent.Invoke();
		myEvent = null;
		

1、事件是作为成员变量存在于类中
2、委托怎么用事件就怎么用
事件与委托的区别:
1、不能在类外部赋值
2、不能在类外部调用
注意: 事件只能作为成员存在于类和接口以及结构体中。

事件的作用

1、防止外部随意置空委托。
2、防止外部随意调用委托。
4、事件相当于对委托进行了一次封装,让其更加安全。

匿名函数

匿名函数的使用主要是配合委托和事件进行使用,脱离委托和事件是不会使用匿名函数的。

基本语法

何时使用:
1、函数中传递委托参数时
2、委托或事件赋值时

delegate (参数列表)
{
	
};

使用

//无参无返回
Action a = delegate()
{
	Console.WriteLine("匿名函数逻辑");
}
a();
//有参
Action<int,string> b = delegate(int a,string b)
{
}
b(100,"123");
//有返回值
Fun<string> c = delegate()
{
	return "123123";
}
c();

可以作为参数传递和返回值使用

缺点

添加到委托或事件容器中后不记录无法单独移除。

lambad

可以将lambad表达式理解为匿名函数的缩写,它除了写法不同外,使用上和匿名函数一模一样,都是和委托或事件配合使用的。

语法

(参数列表)=>
{
}

使用

//无参无返回值
Action a = ()=>
{
	Console.WriteLine("123");
}
//有参
Action<int> a2 = (int value)=>
{
	Console.WriteLine("123");
}
//参数类型可以省略,参数类型和委托或事件容器一致
Action<int> a3 = (value)=>
{ 
}
//有返回值
Fun<string,int> a4 = (value)=>
{
	return 1;
}

其他传参和缺点与匿名函数一样

闭包

内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

class Test
{
	public event Action action;
	public Test()
	{
		int value = 10;
		action = () =>
		{
			Console.WriteLine(value);
		};
	}
}

List排序

List自带排序方法

list.Sort();

自定义类排序

//继承接口IComparable
class Item : IComparable<Item>
{
	public int money;
	public Item(int money)
	{
		this.money = money;
	}
	public int CompareTo(Item other)
	{
		//返回值:
		//小于0:放在传入对象的前面
		//等于0:保持当前的位置不变
		//大于0:放在传入对象的后面
		if(this.money > other.money) 
			return 1;
		else return -1;
		return 0;
	}
}

利用委托函数排序

static int SortShopItem(ShopItem a,ShopItem b)
{
	if(a.id > b.id)
		return 1;
	else return -1;
}
shopItem.Sort(SortShopItem);

协变逆变

用于在泛型中修饰泛型字母的,只有泛型接口和泛型委托能使用。

协变(out)

和谐的变化,自然的变化,因为里氏替换原则父类可以装子类,所以子类变父类感受是和谐的,例如:string变成object。

逆变(in)

逆常规的变化,不正常的变化,因为里氏替换原则父类可以装子类,但是子类不能装父类。所以父类变子类感受是不和谐的,例如object变成string。

作用

//用out修饰的泛型只能作为返回值
delegate T TestOut<out T>(T v);
//用in修饰的泛型只能作为参数
delegate void TestIn<int T>(T t);
//遵循里氏替换原则
//协变:父类泛型委托装子类泛型委托
//逆变:子类泛型委托装父类泛型委托

多线程

进程:
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。即打开一个应用程序就是在操作系统上开启了一个进程,进程之间可以相互独立运行、互不干扰,进程之间也可以相互访问、操作。
线程:
操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,我们目前写的程序中,都在主线程中。简单理解线程就是代码从上到下运行的一条“管道”。
多线程:
我们可以通过代码开启新的线程,可以同时运行代码的多条“管道”就叫多线程。

//引用命名空间
using System.Threading;
//1、申明一个新的线程
//注:线程执行的代码需要封装到一个函数中
static void NewThreadLogic()
{
}
Thread t = new Thread(NewThreadLogic);
//2、启动线程
t.Start();
//3、设置为后台线程
//当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
//后台线程不会防止应用程序的进程被终止掉
//如果不设置为后台线程,可能导致进程无法正常关闭
t.IsBackground = true;
//4、关闭释放一个线程
//如果开启的线程中不是死循环,是能够结束的逻辑,那么不用去刻意的关闭它。
//如果是死循环,想要中止这个线程,有两种方式
//死循环中加bool
bool isRunning = false;
while(isRunning)
{
}
//通过线程提供的方法(注意在.Net core版本中无法中止,会报错)
t.Abort();
t = null;
//为了防止报错,可以加个异常处理(try……catch)
//5、线程休眠
//让线程休眠多少毫秒,在哪个线程里执行,就休眠哪个线程
Thread.Sleep(1000);

线程之间共享数据:
多个线程使用的内存是共享的,都属于该应用程序(进程)。所以要注意当多线程同时操作同一片内存区域时可能会出问题,可以通过加锁的形式避免问题。

//lock
//当我们在多个进程当中想要访问同样的东西,进行逻辑处理时,为了避免不必要的逻辑顺序执行的差错
//lock(引用类型对象)
static object obj = new object();
while(true)
{
	lock(obj)
	{
		//代码逻辑1
	}
}
while(true)
{
	lock(obj)
	{
		//代码逻辑2
	}
}

多线程的意义:
可以用多线程专门处理一些复杂耗时的逻辑,比如寻路、网络通信等等。

预处理器指令

编译器:
编译器是一种翻译程序,它用于将源语言程序翻译为目标语言程序。

源语言程序:某种程序设计语言写成的,如C#、C、C++、Java等语言写的程序。
目标语言程序:二进制数表示的伪机器代码写的程序。

预处理器指令:
1、预处理器指令指导编译器在实际编译开始之前对信息进行预处理。
2、预处理器指令都是以#开始。
3、预处理器指令不是语句,所以它们不以分号;结束。
4、目前我们经常用到的折叠代码块就是预处理器指令。
常见的预处理器指令:
1、#define:定义一个符号,类似一个没有值的变量。
#undef:取消define定义的符号,让其失效。
两者都是写在脚本文件最前面,一般配合if指令使用或配合特性
2、#if、#elif、#else、#endif
和if语句规则一样,一般配合#define定义的符号使用,用于告诉编译器进行编译代码的流程控制。

#define Unity4
#define Unity2017
//如果发现有Unity4这个符号,那么其中包含的代码就会被编译器编译
#if Unity4
	Console.WriteLine("123");
#elif Unity2017
	Console.WriteLine("2017");
#else 
	Console.WriteLine("0");
#endif

3、#warning、#error
告诉编译器是报警还是报错误,一般还是配合if使用。

#warning 这个版本不合法
#error 这个版本不准运行

反射

程序集:
程序集是经由编译器得到的,供进一步编译执行的那个中间产物,在Windows系统中,它一般表现为后缀为.dll(库文件)或者是.exe(可执行文件)的格式。程序集就是我们写的一个代码集合,我们现在写的所有代码最终都会被编译器翻译为一个程序集供别人使用,如一个代码库文件(dll)或者一个可执行文件(exe)。
元数据:
元数据就是用来描述数据的数据,这个概念不仅仅用于程序上,在别的领域也有元数据。程序中的类、类中的函数,变量等信息就是程序的元数据。有关程序以及类型的数据被称为元数据,他们保存在程序集中。
反射:
程序正在运行时,可以查看其他程序集或者自身的元数据。一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射。在程序运行时,通过反射可以得到其他程序集或者自己程序集代码的各种信息:类、函数、变量、对象等等,实例化它们,执行它们,操作他们。
反射的作用:
因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。
1、程序运行时得到所有元数据,包括元数据的特性
2、程序运行时,实例化对象,操作对象。
3、程序运行时创建新对象,用这些对象执行任务。

class Test
{
	private int i = 1;
	public int j = 0;
	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的成员获取有关类型声明的信息,有关类型的成员(如构造函数、方法、字段、属性和类的事件)。

//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 type3 = Type.GetType("System.Int32");
Console.WriteLine(type3);

得到类的程序集信息:

//可以通过Type得到类型所在程序集信息
Console.WriteLine(type.Assembly);

获取类中的所有公共成员:

//首先得到Type
Type t = typeof(Test);
//引用命名空间
using System.Reflection;
//然后得到所有公共成员
MemberInfo[] infos = t.GetMembers();
for(int i = 0;i < infos.Length;i++)
{
	Console.WriteLine(infos[i]);
}

获取类的公共构造函数并调用:

//1、获取所有构造函数
ConstructorInfo[] ctors = t.GetConstructors();
for(int i = 0;i < ctors.Length;i++)
{
	Console.WriteLine(ctors[i]);
}
//2、获取其中一个构造函数并执行
//得到构造函数传入Type数组,数组中内容按顺序是参数类型。
//执行构造函数传入object数组表示按顺序传入的参数
//Ⅰ得到无参构造
ConstructorInfo info = t.GetConstructor(new Type[0]);
Test obj = info.Invoke(null) as Test;//因为无参所以传null
//Ⅱ得到有参构造
ConstructorInfo info2 = t.GetConstructor(new Type[] {typeof(int)});
obj = info2.Invoke(new object[]{2}) as Test;

ConstructorInfo info3 = t.GetConstructor(new Type[] {typeof(int),typeof(string)});
obj = info3.Invoke(new object[]{4,"123"}) as Test;

获取类的公共成员变量:

//1、得到所有成员变量
FieldInfo[] fieldInfos = t.GetFields();
for(int i = 0;i < fieldInfos.Length;i++)
{
	Console.WriteLine(fieldInfos[i]);
}
//2、得到指定名称的公共成员变量
FieldInfo infoJ = t.GetField("j");
Console.WriteLine(infoJ);
//3、通过反射获取和设置对象的值
Test test = new Test();
test.j = 99;
test.str = "2222";
//Ⅰ通过反射获取对象的某个变量的值
Console.WriteLine(infoJ.GetValue(test));
//Ⅱ通过反射设置指定对象的某个变量的值
infoJ.SetValue(test,100);
Console.WriteLine(infoJ.GetValue(test));

获取类的公共成员方法:

//通过Type类中的GetMethod方法得到类中的方法
//MethodInfo是方法的反射信息
Type strType = typeof(string);
//1、如果存在方法重载用Type数组表示参数类型
MethodInfo[] methods = strType.GetMethods();
for(int i = 0;i < methods.Length;i++)
{
	Console.WriteLine(methods[i]);
}
MethodInfo subStr = strType.GetMethod("Substring",new Type[] {typeof(int),typeof(int)});//Substring方法,后面是两个参数开始位置,长度
//2、调用该方法
//注意:如果是静态方法,Invoke中的第一个参数传null即可。
string str = "Hello,World!";
object result = substr.Invoke(str,new object[] {7,5});
Console.WriteLine(result);

其他:
①得枚举:GetEnumName、GetEnumNames
②得事件:GetEvent、GetEvents
③得接口:GetInterface、GetInterfaces
④得属性:GetProperty、GetPropertys
等等。
Activator:
用于快速实例化对象的类,用于将Type对象快捷实例化为对象,先得到Type,然后快速实例化一个对象。

Type testType = typeof(Test);
//1、无参构造
Test testObj = Activator.CreateInstance(testType) as Test;
//2、有参构造
testObj = Activator.CreateInstance(testType,99) as Test;

Assembly(程序集类):
主要用来加载其他程序集,加载后,才能用Type来使用其他程序集中的信息,如果想要使用不是自己程序集中的内容,需要先加载程序集,比如dll文件(库文件),简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

//三种加载程序集的函数
//一般用来加载同一文件下的其他程序集
Assembly assembly2 = Assembly.Load("程序集名称");
//一般用来加载不在同一文件下的其他程序集
Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
Assembly assembly3 = Assembly.LoadFile("要加载的文件的完全限定路径");

类库工程的创建:
在这里插入图片描述

特性

特性是一种允许我们向程序集添加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。特性提供功能强大的方法以将声明信息与C#代码(类型、方法、属性等)相关联。特性与程序实体关联后,即可在运行时使用反射查询特性信息。特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中,它可以放置在几乎所有的声明中(类、变量、函数等等申明)。
特性本质是个类,我们可以利用特性类为元数据添加额外信息。比如一个类,成员变量、成员方法等等为他们添加更多的额外信息,之后可以通过反射来获取这些额外信息。
自定义特性:

//继承特性基类:Attribute
class MyCustomAttribute : Attribute
{
	public string info;
	public MyCustomAttribute(string info)
	{
		this.info = info;
	}
}

特性的使用:

//基本语法
[特性名(参数列表)]
//本质上就是在调用特性类的构造函数,写在类、函数、变量上一行,表示他们具有该特性信息。
[MyCustom("这个是我自己写的一个用于计算的类")]
class MyClass
{
	[MyCustom("这是一个成员变量")]
	public int value;
	[MyCusto("这是一个用于计算加法的函数")]
	public void TestFun([MyCustom("函数参数")] int a)
	{
	}
}
MyClass mc = new MyClass();
Type t = mc.GetType();
//判断是否使用了某个特性
//参数一:特性的类型
//参数二:代表是否搜索继承链(属性和事件忽略此参数)
if(t.IsDefined(typeof(MyCustomAttribute),false))
{
	Console.WriteLine("该类型应用了MyCustom特性");
}
//获取Type元数据中的所有特性
object[] array = t.GetCustomAttributes(true);
for(int i = 0;i < array.Length;i++)
{
	if(array[i] is MyCustomAttribute)
	{
		Console.WriteLine(array[i] as MyCustomAttribute).info);
		(array[i] as MyCustomAttribute).TestFun();
	}
}

限制自定义特性的使用范围:
通过为特性类加特性限制其使用范围

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,AllowMultiple = true,Inherited = true)]
//参数一:AttributeTargets--特性能够用在哪些地方
//参数二:AllowMultiple--是否允许多个特性实例用在同一个目标上
//参数三:Inherited--特性是否能被派生类和重写成员继承

系统自带特性–过时特性(Obsolete)
用于提示用户使用的方法等成员已经过时,建议使用新方法,一般加在函数前的特性。

class TestClass
{
	//参数一:调用过时方法时提示的内容
	//参数二:true:使用该方法时会报错
	//false:使用该方法时直接警告
	[Obsolete("OldSpeak方法已经过时了,请使用Speak方法"true)]
	public void OldSpeak(string str)
	{
	}
	public void Speak()
	{
	}
}

系统自带特性–调用者信息特性:
需要引用命名空间using System.Runtime.CompilerServices
1、哪个文件调用?
CallerFilePath
2、哪一行调用?
CallerLineNumber
3、哪个函数调用?
CallerMemberName
一般作为函数的参数的特性

public void SpeakCaller([CallerFilePath]string str,[CallerLineNumber]string fileName = "",int line = 0,[CallerMemberName]string target = "")
{
}

系统自带特性–条件编译特性(Conditional):
需要引用命名空间using System.Diagnostics;
它和预处理指令#define配合使用,主要可以用在一些调试代码上,有时想执行,有时不想执行的代码。

#difine Fun
[Conditional("Fun")]
static void Fuc()
{
	Console.WriteLine("Fun执行");
}

系统自带特性–外部Dll包函数特性(DllImport):
需要引用命名空间using System.Runtime.InteropServices;
用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。一般用来调用 C 或者 C++的Dll包写好的方法。

[DllImport("Test.dll")]
public static extern int Add(int a,int b);

迭代器

迭代器有时又称为光标(cursor),是程序设计的软件设计模式,迭代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不是暴露其内部的标识。在表现效果上看,是可以在容器对象(例如链表或数组)上遍历访问的接口,设计人员无需关心容器对象的内存分配的实现细节,可以用foreach遍历的类、都是实现了迭代器的。
标准迭代器的实现方法:
1、关键接口:IEnumerator、IEnumerable
2、命名接口:using System.Collections
可以通过同时继承IEnumerable和IEnumerator实现其中的方法。

class CustomList : IEnumerable,IEnumerator
{
	private int[] list;
	//从-1开始的光标,用于表示数据得到了哪个位置
	private int position = -1;
	public CustomList()
	{
		list = new int[] {1,2,3,4,5,6,7,8};
	}
	public IEnumerator GetEnumerator()
	{
		Reset();
		return this;
	}
	public object Current
	{
		get{
			return list[position];
		}
	}
	public bool MoveNext()
	{
		++position;
		return position < list.Length;
	}
	public void Reset()
	{
		position = -1;
	}
}
CustomList list = new CustomList();
//1、先获取in后面这个对象的IEnumerator,会调用对象其中的GetEnumerator方法来获取
//2、执行得到这个IEnumerator对象中的MoveNext方法
//3、只要MoveNext方法的返回值是true,就会得到当前的值,然后复制给item
foreach(int item in list)
{
	
}

用yield return语法糖实现迭代器:
yidld return是C#提供给我们的语法糖,所谓语法糖,也称糖衣语法。主要作用是将复杂逻辑简单化,可以增加程序的可读性,从而减少程序代码出错的机会。

1、关键接口:IEnumerable
2、命名空间:using System.Collections
让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可

class CustomList2 : IEnumerable
{
	private int[] list;
	public CustomList2()
	{
		list = new int[] {1,2,3,4,5,6,7,8};
	}
	public IEnumerator GetEnumerator()
	{
		for(int i = 0;i < list.Length;i++)
		{
			//yield关键字配合迭代器使用
			//可以理解为暂时返回保留当前的状态,一会还会回来
			yield return list[i];
		}
	}
}
//遍历
CustomList2 list2 =  new CustomList2();
foreach(int item in list2)
{
	Console.WriteLine(item);
}

特殊语法

var隐式类型:
var是一种特殊的变量类型,它可以用来表示任意类型的变量。
注意:
1、var不能作为类的成员,只能用于临时变量声明时,一般写在函数语句块中。
2、var必须初始化

var i = 5;
var array = new int[] {1,2,3,4};
var list = new List<int>();

设置对象初始值:
声明对象时,可以通过直接写大括号的形式初始化公共成员变量和属性

class Person 
{
	private int money;
	public bool sex;
	public string Name;
	{	
		get;
		set;
	}
	public int Age
	{
		get;
		set;
	}
}
Person p = new Person{sex = true,Age = 18,Name = "123"};
Person p2 = new Person{Age = 18};

设置集合初始值:
声明集合对象时,也可以通过大括号直接初始化内部属性。

int[] array2 = new int[]{1,2,3,4,5};
List<int> ListInt = new List<int>(){1,2,3,4,5};

匿名类型:
var变量可以声明为自定义的匿名类型

var v = new {age = 10,money = 11,name = "小明"};
Console.WriteLine(v.age);

可空类型:

//1、值类型是不能赋值为空的
int c = null;
//2、声明时在值类型后面加?可以赋值为空
int? c = null;
//3、判断是否为空
if(c.HashValue)
{
}
//4、安全获取可空类型值
int? value = null;
//Ⅰ如果为空,默认返回值类型的默认值
Console.WriteLine(value.GetValueDefault());
//Ⅱ也可以指定一个默认值
Console.WriteLine(value.GetValueDefault(100));

空合并操作符:
空合并操作符:??

左边值 ?? 右边值
//如果左边值为null,就返回右边值,否则返回左边值
//只要是可以为null的类型都能用
int? intV = null;
int intI = intV ?? 100;

内插字符串:
关键符号:$
$来构造字符串,让字符串中可以拼接变量

string name = "123";
Console.WriteLine($"好好学习,{name}");

单句逻辑简略写法:

if(true)
	Console.WrtieLine("234");
for(int i = 0;i < n;i++)
	Console.WriteLine(i);
while(true)
	Console.WriteLine("123");
文章来源:https://blog.csdn.net/K_CRACKING/article/details/135362237
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。