C#高级编程1
抽象类 和接口
抽象类 |
抽象类是包含抽象方法的类; 抽象方法只有方法声明,没有方法体; 抽象类和抽象方法用关键字abstract标识; 抽象类不能实例化,对抽象类的实例化是非法的; 但是可以声明抽象类的引用,指向实现该抽象类的子类对象; 抽象类不能使用sealed关键字,sealed和abstract在概念上是相反的; 抽象类的子类中必须实现全部抽象方法,否则子类也要声明为抽象类; 实现了抽象类方法的子类,称为抽象类的具体类、或者实现类; 定义抽象方法是为了让不同的子类对抽象方法有不同的实现; 抽象类不能多继承;
|
接口 |
接口是一个特殊的类,用于定义子类需要遵循的规则; 接口使用interface进行声明; 接口中不能有字段,因为字段必须分配内存空间,而接口无法实例化; 接口中可以定义属性、方法、索引器; 接口中所有属性默认具有public和abstract属性; 接口的实现类中,必须对所有的成员进行实现; 接口可以多继承,子类必须实现所有接口中的所有成员;
|
|
|
抽象类和接口对比
异同 |
抽象类 |
接口 |
成员 |
可以有字段成员; 方法、属性成员; |
不能有字段成员;(报错:接口不能包含实例字段) 只能有方法和属性成员; |
实例化对象 |
否 |
否 |
声明引用 |
是 |
是 |
多继承 |
否 |
是 |
访问权限 |
默认public; 可以public; 可以protected; 不能private; |
默认public; 可以public; 可以protected; 不能private;(错误:虚拟成员或抽象成员不能是私有的) |
abstract |
要显示声明abstract; 有abstract成员; 也可以有非abstract成员; |
所有成员默认abstract属性; 也可以显示声明; 所有成员都是abstract; |
override |
实现抽象成员不需override; |
实现抽象成员不需override; |
多级继承 |
抽象类可以继承自其他抽象类; |
接口可以继承自其他接口,并且可以多继承; |
示例
/// <summary> /// 会飞的接口 /// </summary> interface IFlyable { string Name { get; } void Fly(); }
/// <summary> /// 鸟类,实现会飞的接口 /// </summary> class Bird : IFlyable { private string _name; public string Name { get =>_name; } public Bird(string name) { _name = name; } public void Fly() { Console.WriteLine($"我是一只鸟,我的名字叫 {Name},我会飞"); } } /// <summary> /// 飞机类,实现会飞的接口 /// </summary> class Plane : IFlyable { private string _name; public string Name { get => _name; } public Plane(string name) { _name = name; } public void Fly() { Console.WriteLine($"我是一架飞机,我的名字叫 {Name},我会飞"); } } static void Main(string[] args) { Bird bird = new Bird("山鸡"); Plane plane = new Plane("C919"); bird.Fly(); plane.Fly(); } 输出 我是一只鸟,我的名字叫 山鸡,我会飞 我是一架飞机,我的名字叫 C919,我会飞 |
class Person { private string _name; private float _height; private float _weight; /// <summary> /// 构造函数 /// </summary> /// <param name="name">姓名</param> /// <param name="height">身高(米)</param> /// <param name="weight">体重(公斤)</param> public Person(string name,float height, float weight) { _name = name; _height = height; _weight = weight; } /// <summary> /// 身高(米) /// </summary> public float Height { get => _height; set => _height = value; } /// <summary> /// 体重(公斤) /// </summary> public float Weight { get => _weight; set => _weight = value; } /// <summary> /// 姓名 /// </summary> public string Name { get => _name; set => _name = value; }
//计算肥胖的公式有多个,先是体重指数,用体重(kg)/身高(m)^2 ,正常范围是18.5-22.9,大于等于24为超重,大于等于28为肥胖 /// <summary> /// 是否过于肥胖 /// </summary> public void BMI() { float bmi = _weight / (_height * _height); if (bmi > 28) Console.WriteLine($"尊敬的 {_name},您属于肥胖类型"); else Console.WriteLine($"尊敬的 {_name},您不属于肥胖类型"); }
} static void Main(string[] args) {
//位置参数:按照参数顺序定位参数 Person zhuwuneng = new Person("猪悟能", 100f, 1.8f); zhuwuneng.BMI();
//命名参数:按照参数名称定位参数,与位置无关 Person zhubajie = new Person(name: "猪八戒" ,weight: 100f, height: 1.8f); zhubajie.BMI(); } 输出: 尊敬的 猪悟能,您不属于肥胖类型 尊敬的 猪八戒,您属于肥胖类型 |
委托、Lambda表达式
委托 |
当需要将方法作为参数传递给另一个方法时,就要使用委托; C#不允许直接传递方法,而是把方法封装到对象中,这个对象就是委托; 委托是一种特殊的对象,其中包含一个或多个函数的地址; 委托本质是一个类,要先创建类,然后实例化类的对象,才可以使用它; 继承关系:自定义委托类delegate→System.MulticastDelegate→System.Delegate delegate int CalInvoker(int);//定义一个委托类 CalInvoker calInvoker;实例化委托类的对象
Action<T>表示返回类型为void的函数,参数最多16个; Func<T>表示有返回值的函数,入参最多16个,返回值1个; Action<in T1,in T2> Func<in T1,out TResult> |
|
|
lambda |
使用场景:把lambda表达式赋值给委托类型;只要使用委托形参的地方,都可以传入lambda表达式作为实参; lambda运算符=>左侧是所需参数(方法形参),右边是实现代码(方法体); 参数类型推断:lambda表达式参数的类型根据定义的委托类型进行推断; 只有一行代码的lambda表达式,可以不使用花括号{},可以不使用return;(编译器自动添加return) 多行代码的lambda表达式,必须使用花括号{}和return关键字; 闭包:通过lambda表达式访问外部变量,这叫做闭包; lambda表达式可以修改闭包(外部变量)的值,并且修改的值可以被外部访问; lambda表达式的内部实现: x=>x+val; public class AnonymousClass { private int val; public AnonymousClass(int val){this.val=val;} public int AnonymousMethod(int x){return x+val;} } labmda表达式可以用于任何类型为委托的地方; 类型为Expression或者Expression<T>时,可以使用lambda,编译器会生成一个表达式树; |
事件 |
事件使用2个参数的方法:第一个参数是一个对象,是事件的发送者;第二个参数提供了事件相关信息,第二个参数随不同的事件类型而改变; EventHandler<TEventArgs>第一个参数必须是object类型,第二个参数是T类型,必须派生自EventArgs; public event EventHandler<CarInfoEventArgs> NewCarInfo; public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where TEventArgs:EventArgs private EventHandler<CarInfoEventArgs> _newCarInfo; public event EventHandler<CarInfoEventArgs> NewCarInfo {add=>_newCarInfo+=value;remove=>_newCarInfo-=value;} 触发事件 EventHandler<CarInfoEventArgs> newCarInfo=NewCarInfo; if(newCarInfo!=null){newCarInfo(this,new CarInfoEventArgs(car));} protected virtual void RaiseNewCarInfo(string car) {NewCarInfo?.Invoke(this,new CarInfoEventArgs(car))} |
事件测试 |
using System;
namespace ConsoleCore0104 { //事件参数类 public class CarInfoEventArgs : EventArgs { public string Car { get; } public CarInfoEventArgs(string car) => Car = car;
} //汽车经销商 public class CarDealer { //新车到店的事件容器,用于存放新车到店后的回调函数(事件处理函数) //EventHandler<TEventArgs>是一个泛型委托,指定参数类型就可以定义这个委托 //EventHandler<TEventArgs>第一个参数必须是object类型,第二个参数是T类型,必须派生自EventArgs; public event EventHandler<CarInfoEventArgs> NewCarInfo; public void NewCar(string car) { Console.WriteLine($"经销商:新车已到店 {car}"); //如果事件容器不为空,则调用执行其中的所有函数(顺序不定) //事件触发者是汽车经销商,所以sender就是this //?表示如果NewCarInfo不是null,就执行Invoke函数;如果为null,不执行Invoke函数; NewCarInfo?.Invoke(this,new CarInfoEventArgs(car)); Console.WriteLine(); } } //消费者 public class Consumer { private string _name; public Consumer(string name) => _name = name; //新车到店后的回调函数 //用+=将客户的回调函数添加到事件容器中 //用-=取消订阅(新车到店后不会再执行该函数) //如果客户订阅了新车到店事件,新车到店后就会执行该函数 public void NewCarIsHere(object sender, CarInfoEventArgs e) { Console.WriteLine($"尊敬的客户:{_name},新车已到店 {e.Car}"); } } //Main class Program {
static void Main(string[] args) { var dealer = new CarDealer();//实例化经销商 var ZhaoBenShan = new Consumer("赵本山");//实例化消费者--赵本山 dealer.NewCarInfo += ZhaoBenShan.NewCarIsHere;//赵本山--订阅新车到店 dealer.NewCar("比亚迪 唐");//经销商新车到店事件--比亚迪唐 var LiuNeng = new Consumer("刘能");//实例化消费者--刘能 dealer.NewCarInfo += LiuNeng.NewCarIsHere;//刘能--订阅新车到店 dealer.NewCar("长安UNIT");//经销商新车到店事件--长安UNIT dealer.NewCarInfo -= ZhaoBenShan.NewCarIsHere;//赵本山--取消订阅新车到店 dealer.NewCar("众泰保时捷");//经销商新车到店事件--众泰保时捷 } } } 输出结果: 经销商:新车已到店 比亚迪 唐 尊敬的客户:赵本山,新车已到店 比亚迪 唐
经销商:新车已到店 长安UNIT 尊敬的客户:赵本山,新车已到店 长安UNIT 尊敬的客户:刘能,新车已到店 长安UNIT
经销商:新车已到店 众泰保时捷 尊敬的客户:刘能,新车已到店 众泰保时捷
|
总结 |
发布者:谁创建了委托(事件),谁就是发布者; 发布者:发布者负责委托内函数(函数的容器、函数指针的容器)的调用; 订阅者:谁向委托添加函数,谁就是订阅者; 订阅者:订阅者负责定义函数,并将函数添加到委托中(函数的容器中); 订阅者:订阅者定义的函数类型必须和发布者的委托类型匹配; |
public void ParallelTest()
{
sb.Clear();
var cts = new CancellationTokenSource();
cts.Token.Register(() => sb.Append("\n任务已取消\n"));
new Task(() => { Thread.Sleep(500); cts.Cancel(); }).Start();
try
{
ParallelLoopResult res = Parallel.For(0, 100,
new ParallelOptions() { CancellationToken = cts.Token, },
x => {
sb.Append($"\n循环已开始,序号={x}\n");
int sum = 0;
for (int i = 0; i < 100; i++)
{
Thread.Sleep(2);
sum += i;
}
sb.Append($"\n循环已结束,序号={x}\n");
}
);
}
catch (Exception ex)
{
sb.Append("\n"+ex.Message+"\n");
}
richTextBox1.Text = sb.ToString();
}
public void ThreadTest2()
{
sb.Clear();
for (int i = 0; i < 5; i++)
{
System.Threading.ThreadPool.QueueUserWorkItem(JobForThread);
}
richTextBox1.Text = sb.ToString();
}
StringBuilder sb = new StringBuilder();
public void JobForThread(object state)
{
for (int i = 0; i < 3; i++)
{
sb.Append( $"这是第 {i} 个任务,运行的线程ID是 {System.Threading.Thread.CurrentThread.ManagedThreadId}\n");
}
System.Threading.Thread.Sleep(50);
}
public void ThreadTest()
{
int workThreadCount, completionPortThreadCount;
richTextBox1.Text += "\n默认值:\n";
System.Threading.ThreadPool.GetMaxThreads(out workThreadCount, out completionPortThreadCount);
richTextBox1.Text += "\n线程池中最大工作线程数=" + workThreadCount + "\n线程池中最大IO线程数=" + completionPortThreadCount;
// 线程池中最大工作线程数 = 2047
//线程池中最大IO线程数 = 1000
System.Threading.ThreadPool.GetMinThreads(out workThreadCount, out completionPortThreadCount);
richTextBox1.Text += "\n线程池中最小工作线程数=" + workThreadCount + "\n线程池中最小IO线程数=" + completionPortThreadCount;
richTextBox1.Text += "\n\n自定义值:\n";
System.Threading.ThreadPool.SetMaxThreads(100,200);
System.Threading.ThreadPool.SetMinThreads(1,2);
System.Threading.ThreadPool.GetMaxThreads(out workThreadCount, out completionPortThreadCount);
richTextBox1.Text += "\n线程池中最大工作线程数=" + workThreadCount + "\n线程池中最大IO线程数=" + completionPortThreadCount;
System.Threading.ThreadPool.GetMinThreads(out workThreadCount, out completionPortThreadCount);
richTextBox1.Text += "\n线程池中最小工作线程数=" + workThreadCount + "\n线程池中最小IO线程数=" + completionPortThreadCount;
}
默认值:
线程池中最大工作线程数=2047
线程池中最大IO线程数=1000
线程池中最小工作线程数=8
线程池中最小IO线程数=8
自定义值:
线程池中最大工作线程数=100
线程池中最大IO线程数=200
线程池中最小工作线程数=1
线程池中最小IO线程数=2
t.GetMembers();//获取所有成员
t.GetMethods();//获取所有方法
t.GetFields();//获取所有字段
t.GetProperties();//获取所有属性(get set封装的字段)
t.GetConstructors();//获取所有构造函数
t.GetDefaultMembers();//获取所有默认成员
通过type获取类的所有成员名称
public void TypeTest()
{
var t = typeof(Form);
var mems = t.GetProperties();
StringBuilder sb = new StringBuilder();
sb.Append(t.Name + "\n");
sb.Append(t.FullName + "\n");
sb.Append(t.Namespace + "\n");
sb.Append("\n");
foreach (var item in mems)
{
sb.Append(item.Name + "\n");
}
richTextBox1.Text = (sb.ToString());
}
类型
Thread |
Thread 需要自己调度,适合长跑型的操作。 |
ThreadPool |
ThreadPool是Thread基础上封装的一个线程池,目的是减少频繁创建线程的开销。 ThreadPool适合频繁、短期执行的小操作。 但是ThreadPool不能突然中断线程的执行,在多核时代,它的效率也不尽如人意。 |
Task |
Task或者说TPL(task parallel library)是一个更上层的封装,NB之处在于continuation。 能用Task就用Task,底下都是用的Thread或者ThreadPool。 |
Timer |
另外还有个特别的是Timer,所有Timer实例都是在一个专门的Timer线程上调度的。所以不要写的很重,要不然原本已经很低的精度会更加惨不忍睹。 |
分类
前台 后台 |
前台线程:主程序必须等待线程执行完毕后才能退出程序,Thread默认为前台程序,可以设置为后台程序; 后台线程:主程序执行完毕后就退出,不管线程是否完成,Thread Pool默认是后台程序 |
工作 I/O |
工作者线程:workerThreads是主要用作管理CLR内部对象的运作,通常用于计算密集的任务。在任务执行的过程中,需要CPU不间断地处理,所以,在工作者线程的执行过程中,CPU和线程的资源是充分利用的; I/O线程:completionPortThreads线程主要用来完成输入和输出的工作的,在这种情况下, 计算机需要I/O设备完成输入和输出的任务,在处理过程中,CPU是不需要参与处理过程的,此时正在运行的线程将处于等待状态,只有等任务完成后才会有事可做, 这样就造成线程资源浪费的问题,可以通过线程池来解决这样的问题。 |