目录
1.泛型结构
1.1扩展方法和泛型类
1.2泛型委托
2.协变和逆变
21.协变
2.2 逆变
3.迭代器
3.1IEnumerator接口
3.2 IEnumerable 接口
3.3.使用yeild创建可枚举类型
4.Linq
4.1Join
4.2.let子句
4.3.into子句
5.异步编程
5.1线程知识点
5.2 ValueTask
5.3 Task.Delay
5.4.BackgroundWorker
6. 异步编程旧模式
6.1介绍
6.2. BeginInvoke和EndIvoke
6.3 等待直到完成
6.4 轮询模式
6.5 回调模式
6.6 计时器
public class Holder
{
T[] Vals = new T[3];
public Holder(T t0,T t1,T t2)
{
Vals[0] = t0; Vals[1] = t1; Vals[2] = t2;
}
public T[] GetValues() => Vals;
}
public static class ExtendHolder
{
public static void Print(this Holder h)
{
var arr=h.GetValues();
Array.ForEach(arr, x=>Console.Write(x+" - "));
}
}
internal class GenericDelegate
{
public static void Test()
{
var myDel=new Func(Simple.PrintString);
Console.WriteLine($"Total : {myDel(100, 1)}");
}
}
public delegate TR Func(T1 p1,T2 p2);
public class Simple
{
static public string PrintString(int p1,int p2)
{
int total = p1 + p2;
return total.ToString();
}
}
先看一个例子:
internal class Convariance
{
public static Dog DogMaker() => new Dog();
public static void Test()
{
Factory dogMaker = DogMaker;
Factory animalMaker = dogMaker;
Console.WriteLine(animalMaker().legs.ToString());
}
}
class Animal { public int legs = 4; }
class Dog:Animal { }
delegate T Factory();
上面的代码会编译不通过:
虽然Dog是Animal的派生类,但是委托Factory
仅将派生类型用作输出值与构造委托之间的常数关系叫作协变,为了让编译器知道这是我们的期望,必须使用out关键字标记委托声明中的类型参数。
class Animal { public int legs = 4; }
class Dog:Animal { }
delegate T Factory();
与协变类似,但是又相反,协变是输出类型的代理,逆变则是为了输入类型,看下面的例子:
internal class Contravariance
{
delegate void Action(T t);
static void ActOnAnimal(Animal a) => Console.WriteLine(a.legs);
public static void Test()
{
Action act1 = ActOnAnimal;
Action act2 = act1;
act2(new Dog());
}
}
同理,如果没有“in”,在act2赋值时会报错,因为Dog可以当作Animal类参数,但本身Action
实现了IEnumerator接口的枚举器包含3个函数成员:Current,MoveNext,以及Reset。
可枚举类型是指实现了IEnumerable 接口的类,IEnumerable接口只有一个成员GetEnumerator方法,它返回对象的枚举器。看一下综合例子:
internal class IEnumeratorStudy
{
public static void Test()
{
Spectrum spectrum=new Spectrum();
foreach(var s in spectrum)
Console.WriteLine(s);
}
}
class ColorEnumerator:IEnumerator
{
string[] colors;
int position = -1;
public ColorEnumerator(string[] theColors)
{
colors = new string[theColors.Length];
for (int i = 0; i < theColors.Length; i++)
colors[i] = theColors[i];
}
public object Current
{
get
{
if (position == -1)
throw new InvalidOperationException();
if(position>=colors.Length)
throw new InvalidOperationException();
return colors[position];
}
}
public bool MoveNext()
{
if(position
使用yeild很容器创建可枚举类型(不是枚举器),后台会帮我们做很多事情。
internal class YeildStudy
{
public static void Test()
{
MyClass mc=new MyClass();
foreach(var shade in mc)
{
Console.WriteLine(shade);
}
//使用类枚举器方法
foreach(var shade in mc.BlackAndWhite())
{
Console.WriteLine(shade);
}
}
}
public class MyClass
{
public IEnumerator GetEnumerator()
{
IEnumerable myEnumerable = BlackAndWhite();
return myEnumerable.GetEnumerator();
}
public IEnumerable BlackAndWhite()
{
yield return "Black";
yield return "Gray";
yield return "white";
}
}
Linq中的join子句和SQL中的join子句很相似。不同的是linq的join子句不但可以在数据库的表上执行廉洁,还可以在集合对象上进行这个操作。廉洁操作接受两个集合,然后创建一个临时的对象集合,其中每个对象包含两个原始集合对象中的所有字段。
举个例子,倘若你有一个学生表,一个选课表:
Student[] students = new Student[]
{
new Student(){Id=1,Name="张三"},
new Student{Id=2,Name="李四"},
new Student(){Id=3,Name="王五"}
};
CourseStudent[] courses = new CourseStudent[]
{
new CourseStudent(){ CourseName="美术",StuId=1},
new CourseStudent(){CourseName="美术",StuId=2},
new CourseStudent(){CourseName="历史",StuId=1},
new CourseStudent(){CourseName="历史",StuId=3},
new CourseStudent(){CourseName="物理",StuId=3}
};
现在你要找出选了历史的同学的姓名,怎么写呢?用join
var query = from s in students
join c in courses
on s.Id equals c.StuId
where c.CourseName == "历史"
select s.Name;
Array.ForEach(query.ToArray(),x=>Console.WriteLine(x));
这样写就可以找出来了。其实也不一定非要用join,下面这个办法也是可以的
var query2= from s in students
from c in courses
where c.CourseName=="历史" && s.Id==c.StuId
select s.Name;
可能会存在效率问题,个人感觉join的效率会更高。
但是from子句在两个集合没有连接关系时更实用,看个例子:
public static void Test2()
{
var A=new[] {3,4,5,6};
var B = new[] { 6, 7, 8, 9 };
var ans=from a in A
from b in B
where a>4 && b<=8
select new {a,b,sum=a+b}; // 匿名对象
Array.ForEach(ans.ToArray(),x=>Console.WriteLine(x));
}
输出为:
{ a = 5, b = 6, sum = 11 }
{ a = 5, b = 7, sum = 12 }
{ a = 5, b = 8, sum = 13 }
{ a = 6, b = 6, sum = 12 }
{ a = 6, b = 7, sum = 13 }
{ a = 6, b = 8, sum = 14 }
let子句接受一个表达书运算,并且吧它赋值给一个需要在其它运算中使用的标识符。
拿上面的例子,我们可以优化匿名对象:
var ans=from a in A
from b in B
let sum=a+b
where a>4 && b<=8
select new {a,b,sum}; // 匿名对象
效果和上面是一模一样的。
值得一提的是,查询表达式可以后任意个where子句,我们换个条件:
var ans2=from a in A
from b in B
let sum=a*b
where a%2==0
where sum<40
select new {a,b,sum};
Array.ForEach(ans2.ToArray(), x => Console.WriteLine(x));
如果条件很多,建议用多个where,而不是与或非,这样更清晰一些。
查询延续子句可以接受查询的一部分的结果,并赋予一个名字,从而可以在查询的另一部分中使用。看个例子:
var groupA=new [] {3,4,5,6};
var groupB=new [] {4,5,6,7};
var someInts = from a in groupA
join b in groupB on a equals b
into groupAB
from c in groupAB
select c;
Array.ForEach(someInts.ToArray(), x => Console.WriteLine(x));
ValueTask
Task.Delay 方法创建一个Task对象,该对象将暂停其在线程中的处理,并在一定时间之后完成。与Tread.Sleep阻塞线程不同的是,Task.Delay不会阻塞其它线程。看下面例子:
首先是没有await等待:
public class Sample
{
Stopwatch sw=new Stopwatch();
public void Run()
{
Console.WriteLine("Caller:Before Call");
ShowDelayAsync();
Console.WriteLine("Caller: After call");
}
private async void ShowDelayAsync()
{
sw.Start();
Console.WriteLine($" Before Delay: {sw.ElapsedMilliseconds}");
Task.Delay(1000);
Console.WriteLine($" After Delay: {sw.ElapsedMilliseconds} ");
}
}
运行结果:
Caller:Before Call
Before Delay: 0
After Delay: 3
Caller: After call
很显然没有等待,Task.Delay是异步执行。加上等待之后呢?
private async void ShowDelayAsync()
{
sw.Start();
Console.WriteLine($" Before Delay: {sw.ElapsedMilliseconds}");
await Task.Delay(1000);
Console.WriteLine($" After Delay: {sw.ElapsedMilliseconds} ");
}
结果就是我们想要的了:
Caller:Before Call
Before Delay: 0
Caller: After call
After Delay: 1021
如果用sleep函数代替:
private async void ShowDelayAsync()
{
sw.Start();
Console.WriteLine($" Before Delay: {sw.ElapsedMilliseconds}");
// await Task.Delay(1000);
Thread.Sleep(1000);
Console.WriteLine($" After Delay: {sw.ElapsedMilliseconds} ");
}
结果如下:
Caller:Before Call
Before Delay: 0
After Delay: 1005
Caller: After call
虽然也满足了延迟,但是整个进程都停滞了,所以主线程的顺序也改变了。
3个事件,用于发送不同的程序事件和状态,你需要写这些事件的处理方法来执行合适的程序的行为:
// 摘要:
// Occurs when System.ComponentModel.BackgroundWorker.RunWorkerAsync is called.
public event DoWorkEventHandler? DoWork
//
// 摘要:
// Occurs when System.ComponentModel.BackgroundWorker.ReportProgress(System.Int32)
// is called.
public event ProgressChangedEventHandler? ProgressChanged
//
// 摘要:
// Occurs when the background operation has completed, has been canceled, or has
// raised an exception.
public event RunWorkerCompletedEventHandler? RunWorkerCompleted
三个方法用于开始行为或改变状态:
第一个函数DoWork是必须的,因为它包含后台线程执行的代码。在DoWork函数中 通过调用ReportProgress方法与主线程通信,届时将处罚ProgressChanged事件,主线程可以用附加到ProgressChanged事件上的处理程序处理事件。
附加到RunWorkerComplete事件的处理程序应该包含在后台线程完成DoWork事件处理程序的执行之后需要执行的代码。
三个委托如下:
public delegate void DoWorkEventHandler(object? sender, DoWorkEventArgs e);
public delegate void ProgressChangedEventHandler(object? sender, ProgressChangedEventArgs e);
public delegate void RunWorkerCompletedEventHandler(object? sender, RunWorkerCompletedEventArgs e);
具体讲解可以看这个博客;
下面是一个典型的例子:
internal class BackgroundWorkerStudy
{
public static void Test()
{
SimpleWorker simpleWorker = new SimpleWorker();
Task.Run(() => simpleWorker.Start());
while (true)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("enter anykey to over background task!");
var s = Console.ReadLine();
simpleWorker.Cancel();
break;
}
Console.WriteLine("Input anykey to exit!");
Console.ReadLine();
}
}
public class SimpleWorker
{
BackgroundWorker bgWorker = new BackgroundWorker();
public SimpleWorker()
{
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.DoWork += DoWork_Handler;
bgWorker.ProgressChanged += ProgressChange_Handler;
bgWorker.RunWorkerCompleted += RunWorkCompleted_Handler;
}
private void DoWork_Handler(Object sender, DoWorkEventArgs args)
{
var worker = sender as BackgroundWorker;
for (int i = 1; i <= 100; i++)
{
if (worker.CancellationPending)
{
args.Cancel = true;
break;
}
else
{
worker.ReportProgress(i * 10);
Thread.Sleep(1000);
}
}
}
private void ProgressChange_Handler(Object sender, ProgressChangedEventArgs args)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Now it is running: {args.ProgressPercentage}");
for (int i = 0; i < args.ProgressPercentage / 10; i++)
{
Console.Write("*");
}
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
}
private void RunWorkCompleted_Handler(object sender, RunWorkerCompletedEventArgs args)
{
if (args.Cancelled)
Console.WriteLine("Progress was cancled!");
else
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Pregress Completed normally");
}
}
public void Cancel()
{
bgWorker.CancelAsync();
}
public void Start()
{
if (!bgWorker.IsBusy)
bgWorker.RunWorkerAsync();
}
}
现在有了await/async之后,我们编写异步程序基本上都是用这个。然而你仍然有可能需要使用旧的模式来生产异步代码。学习这种旧模式面对async/await特性会有更深刻的认识。
如果委托对象在调用列表中只有一个方法,它就可以异步执行这个方法。委托类有两个方法:BeginInvoke和EndInvoke,它们就是用这个来实现目的的。
当调用委托的BeginInvoke方法时,他开始在一个独立的线程上执行引用方法。之后立即返回原线程,原始线程可以继续,而引用方法会在线程池的线程中并行执行。
当程序希望获取已完成的异步方法的结果时,可以检查BeginInvoke返回IAsyncResult的IsCompleted属性,或调用委托的EndInvoke方法来等待委托完成。
有三种调用模式:
在调用BeginInvoke时,参数列表中的实际参数组成如下:
BeginInvoke从线程池中获取一个线程并且让引用方法在新的线程中开始运行。
BeginInvoke返回一个实现IAsyncResult接口的对象的引用。这个接口引用包含了在线程池中运行异步方法的当前状态,然后原始线程可以继续执行。
EndInvoke方法用来获取由一步方法调用返回的值,并且释放线程使用的资源。EndInvoke有如下的特性:
它接受BeginInvoke方法返回的IAsyncResult对象的引用作为参数,并找到它关联的线程。如果线程池的线程已经退出,则做下面2件事情:
如果调用EndInvoke时,线程还在运行,调用线程就会停止并等待它完成,然后再清理并返回值。如果异步方法触发了异常,则调用EndInvoke时会抛出异常。看个简单的例子:
public class AsyncInvoke
{
delegate long Mydele(int x, int y);
public AsyncInvoke()
{
}
private static long Sum(int x, int y) => x + y;
public static void Test()
{
Mydele del = new Mydele(Sum);
IAsyncResult result = del.BeginInvoke(3, 4, null, null);
long ans = del.EndInvoke(result);
Console.WriteLine($"ans: {ans}");
}
}
public class AsyncInvoke
{
delegate long Mydele(int x, int y);
public AsyncInvoke()
{
}
private static long Sum(int x, int y) => x + y;
private static long Sum2(int x,int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
public static void Test2()
{
Mydele d = new Mydele(Sum2);
Console.WriteLine("Before BeginInvoke");
var iar = d.BeginInvoke(2, 4, null, null);//开始异步调用
Console.WriteLine("After BeginInvoke");
Console.WriteLine("Doing Stuff");
long result = d.EndInvoke(iar);
Console.WriteLine($"After EndInvoke: {result}");
}
}
代码输出如下:
Before BeginInvoke
After BeginInvoke
Doing Stuff
Inside Sum
After EndInvoke: 6
AsyncResult对象包含一个叫作AsyncDelegate的属性,它返回一个指向被调用来启动异步方法的委托的引用,但是这个属性是类对象的一部分而不是接口的一部分。
IsCompleted属性返回一个布尔值,表示一步方法是否完成。AysncState属性返回对象的一个引用,作为BeginInvoke方法调用时的State参数。因此可以利用这个参数实现轮询。
public static void Test3()
{
Mydele d = new Mydele(Sum2);
IAsyncResult iat = d.BeginInvoke(4, 3, null, null);
Console.WriteLine("After BeginInvoke!");
while(!iat.IsCompleted)
{
Console.WriteLine("Not done!");
for (int i = 0; i < 1000000; i++)
;
}
Console.WriteLine("Done!");
long result = d.EndInvoke(iat);
Console.WriteLine($"Result: {result}");
}
BeginInvoke参数列表中最后两个额外参数由回调方法使用。
第一个参数callback是回调方法的名字
第二个参数state可以是null,或要传入回调的一个对象引用。可以哦通过IAsyncResult参数的AsyncState属性来获取这个对象,参数的类型是Object。
回调方法:
void AsyncCallBack(IAsyncResult iar)
private static void CallWhenDone(IAsyncResult iar)
{
Console.WriteLine(" Inside CallWhenDone.");
AsyncResult ar = (AsyncResult)iar;
var del = (Mydele)ar.AsyncDelegate;
long result = del.EndInvoke(iar);
Console.WriteLine($" The result is {result}");
}
public static void Test4()
{
Mydele d = new Mydele(Sum2);
Console.WriteLine("Before BeginInvoke");
IAsyncResult iat = d.BeginInvoke(4, 3, CallWhenDone, null);
Console.WriteLine("Doing more work in Main");
Thread.Sleep(1000);
Console.WriteLine("Done with Main. Exiting");
}
.Net 有好几个可以用的Timer类,这里主要介绍System.Threading中的那个
可以看到Timer有好多种构造函数,看个简单的例子:
internal class TimerStudy
{
int TimesCalled = 0;
void Display(object state)
{
Console.WriteLine($"{(string)state} {++TimesCalled}");
}
public static void Test()
{
TimerStudy timerStudy = new TimerStudy();
//2秒后第一次调用,每秒重复一次
Timer timer = new Timer(timerStudy.Display, "Processing timer event", 2000, 1000);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("计时器开始...");
Console.ReadLine();
}
}
可以使用change方法来改变他的开始时间和周期,但是一旦计时器启动之后就不能更改了。
此外,还有2个计时器: