C#学习

2020/09/11 program 共 18312 字,约 53 分钟

基础语法

杂项

异常筛选器
使用索引器初始化关联集合
默认接口方法
元组switch
record
A a = new();
模式匹配
为空判断:string.IsNullOrWhiteSpace   ,  if(name is not null)
nint

using static

using static System.Console; 

// 此时访问Console.WriteLine方法可以不写Console类的名称。
WriteLine("Hel1o World!");

命名空间别名

using ext = Organization.Component.Extensions; 
using mps = Organization.Component.MainParts;

指针unsafe

指针操作在C#语言中被认为是不安全的,因此,如果要使用指针变量,则必须将相关的代码写在unsafe代码块中,或者在方法的声明中加入unsafe修饰符。 例如,以下代码在unsafe代码块中声明指针类型的变量。

byte b=255;

unsafe{
	byte*pb=&b;
}

unsafe void DoSomething(){
}

对象名nameof

在应用程序中输出变量的名称,实际就是获得变量名称的字符串表示形式,这需要用到nameof运算符。

Console.WriteLine($"变量{nameof(strvar)}的值为{strvar}。";

默认值

int v=default(int); string s=default(string); 
int v=default; strings=default;
Console.WriteLine($"int类型的默认值:{v}"; 
Console.WriteLine($"string类型的默认值:{s??"nul1"}";

?、 ??、 ?: 、?.、?[ ]

_myEvent?.Invoke(this,++c);

使用dotnet命令运行应用程序

dotnet Demo.d11-hello-world

switch模式匹配

在case语句中使用when子句

获取键盘按键

ConsoleKey.LeftArrow

初始化只读字段

只读字段与常量不同,常量必须在声明时赋值,而且不能使用表达式的运算结果赋值(例如,不能把某个变量的值赋给常量),但只读字段是可以使用其他表达式的运算结果来赋值的。只读字段和常量有一个共同点一—初始化之后不能在代码中修改。初始化只读字段有两种方法:

  • 第一种是在声明之后立即赋值;
  • 第二种是先声明字段,然后在类构造函数中赋值。

可变个数的方法参数

在声明方法参数时,可以使用数组类型的参数,并且在前面加上params关键字。这表明在调用方法时,可以直接输入要传递给数组参数的值(即直接传数组元素),每个元素之间用英文的逗号隔开,与传递普通参数一样。由于params关键字所修饰的参数是数组类型,所以参数个数是可变的,也可以是0个。 一个方法中只能使有一次用params关键字修饰的参数,而且该参数必须位于方法参数列表的最后,其后面不能出现其他参数。

static void Sample(params int[]numbers)

引用

除了可以使用按引用传递的输入参数外,还可以使用按引用传递的返回值。与按引用传递的输入参数相似,按引用传递的返回值也使用ref关键字。例如以下方法就可以按引用传递返回的数据。

ref string GetName(int index);

如果变量是按引用赋值,那么当代码修改变量所指向的对象时,变量所引用的内容也会同步被修改。 当要返回的对象包含比较复杂的数据时(尤其是结构,它会自我复制),可以将其按引用方式返回,以节省对象数据在复制过程中的性能开销。使用按引用传递的返回值时,需要注意以下几点:

  • 当方法(或属性)要按引用返回时,必须有一个变量来存放对象引用,并且该变量的生命周期必须大于该方法(属性)。也就是说,在方法(属性)内部声明的变量是不能按引用返回的,因为当方法(属性)返回后变量的生命周期就结束了,所以这个变量应当是类(或结构)级别的字段(一般是私有字段)。
  • 不能直接返回null,否则会发生错误。因为该值无任何引用,无法进行传递。但是用来存放引用的变量是可以为null的,例如类级别的一个字段可以赋null值,当该字段被作为按引用传递的返回值时不会发生错误。
  • 使用ref关键字声明的变量,可以引用单个对象实例,也可以引用数组中的某个元素,但不能引用List对象中的元素。List对象不是直接访问元素实例,它内部有封装和传递变量的过程,而由于数组是可以直接引用元素实例的,因此数组中的元素可以被ref关键字声明的变量引用。 为了更好地对比普通返回值与按引用传递的返回值之间的不同,本实例声明了两个基本相同的类,类中都封装一个int类型的私有字段,并通过Value属性公开该字段的值。而这两个类的差异就在于Value属性的返回方式,一个是普通的按值返回,另一个则是按引用返回。

声明时初始化属性

public string Code {get;set;}
public string Code { get;set;}="abc";

委托delegate

理解为C/C++里的函数指针。

delegate double DoSomething(intx,double y);

委托属于数据类型,因此可以直接在命名空间下声明,并使用delegate关键字。

static double RunHere(int a,double b) {return a+b;}

实例化DoSomething委托:

DoSomething dele=new DoSonething(RunHere);

DoSomething dele=RunHere;
double res=dele(16,27.67d);

多播委托

允许委托实例绑定多个方法,其基础类型为MulticastDelegate,但是在实际编写代码时是无须考虑该基类的。在代码中,可以使用“+”运算符(加号)添加要绑定的方法,或者使用“一”运算符(减号)移除已绑定的方法。在多播委托实例上添加或移除方法实例,使用更多的是“十=”与“—=”运算符。

匿名方法

匿名方法使用delegate关键字作为方法名,后接方法参数列表以及方法体。匿名方法可以做到在不定义新方法的前提下直接给委托变量赋值。某个委托类型的声明如下。

delegate int DoSomething(int a,int b);

在声明委托变量后,可以用匿名方法直接赋值,而不需要定义新的方法来绑定。

DoSomething d = delegate(int j,int k){return j+k;}

封装事件

事件是类型中的一种成员对象,它是委托类型。主要是运用了委托可以绑定一个或多个方法的特点,当作为事件的委托被调用时,就能连带地调用其绑定的方法。这样一来,类型中的代码只负责引发(调用)事件,而不需要考虑如何响应事件。声明事件需要使用event关键字,例如下面委托将作为某个类的事件类型。

public delegate void TestEventDelegate(object obj,int arg);

在类中,可以用以上委托来定义事件。

public event TestEventDelegate Play;

事件一般应该声明为公共成员,这样才能方便外部代码引用,以绑定响应事件的方法。 假设变量vt是某个类的实例,可以把它的Play事件与方法绑定。

tn.Play+=delegate(object o,int a){Console.WriteLine(a);}

只要Play事件在类中被调用,与其绑定的方法也会被调用,这样就达到了“当某件事情发生时,代码会做出响应”的效果。就像上面代码所演示的,Play事件绑定了一个匿名方法,在匿名方法中通过WriteLine方法输出事件参数的值。绑定方法后,一旦Play事件被调用,那么屏幕上就会立即输出事件参数的值。 上面所举例的事件定义是直接公开事件委托的,在某些特殊情况下,也可以像属性那样,把事件所使用的委托进行封装,即类型的内部用一个私有字段来存储委托实例,然后将event关键字所定义的事件对外公开,但字段本身不对外公开。这种封装一般用在需要对赋值给委托的方法实例进行验证的场合。

// 定义一个委托,用来作为事件类型。 public delegate void DemoEventDelegate(object obj,int count);

// 声明一个测试类,并包含事件成员。

public class Test { 

    DemoEventDelegate _myEvent; 

    public event DemoEventDelegate Worked {
        add {
            _myEvent += value; 
        }
        remove {
            _myEvent -= value; 
        } 
    }
}

以上代码中,通过一个_myEvent私有字段来保存事件,对外公开的事件是Worked。_

_ 与属性类似,属性通常用get和set访问器,而事件也有两个访问器:

  • add访问器用于向作为事件的委托添加要绑定的方法实例;
  • remove访问器用于从作为事件的委托实例中移除某个方法实例。
  • value是一个关键字,表示赋值给委托的值,作用与属性中的value关键字相同。

这种封装常用于验证,例如在add的时候,可以检查值是否为null。

add{
    if(value!=null)
        myEvent +=value;
}

在类中,需要用代码来调用事件委托,这样才能引发事件,此处定义一个公开的Run方法,当方法被调用时,会调用事件委托,然后也会调用与委托绑定的所有方法。

private int c=0;
public void Run() {_myEvent?.Invoke(this,++c);
}

每次调用都会先让c字段的值加上1再传递处理事件的方法。

// 实例化用于测试的类。
Testt=new Test();

// 为Worked事件绑定处理代码,此处使用了Lambda表达式。当Worked事件发生时会在屏幕上输出一行文本。
t.Worked += (k,f)=>Console.Writeline($"你已调用了{f)次实例。”);

// 为了验证事件是否会发生,可以连续调用4次Run方法。
t.Run();
t.Run();
t.Run();
t.Run();

框架提供的委托类型Action和Func

为了提高开发人员的效率(无须自己定义过多的委托类型),.NET基础框架公开了许多委托类型,并且这些委托类型都带有泛型参数,可以最大程度地扩充其灵活性。这些委托可以分为两类:

  • Action。用于匹配返回类型为void的方法,可以匹配0到16个参数的方法。在实际开发中,这已经能够处理绝大部分的情形了,通常很少会定义参数过多的方法。
  • Func。用于匹配返回类型为非void类型的方法,同样也能匹配0到16个输入参数。所有Func委托的泛型参数列表中,最后一个(TResult)都表示方法的返回值类型。指定Func委托的返回类型总是在泛型参数的最后一个。例如,Func<int,int,string>可以匹配有两个int类型输入参数、返回值为string类型的方法。
// 定义两个返回值为void类型的方法。
static void TestA(string name,int age) {Console.WriteLine($"{name}今年{age]岁了。”); }
static void TestB(String name) { Console.WriteLine(”你好,{0}",name);}

// 再定义两个返回值为非void类型的方法。
static int TestC(DateTime dt) { return dt.Year; }
static long TestD(int start,int end) { return r;}

// 使用Action委托绑定TestA和TestB方法。
Action<string,int>dl=TestA; d1("Bob",28); Action<string>d2=TestB; d2("Jim");
// 使用Func委托绑定TestC和TestD方法。
Func<DateTime,int>d3=TestC; Console.WriteLine("今年是{0}年。"d3(DateTime.Now)); 
Func<int,int,long>d4=TestD; 1ong res=d4(2,4); Console.WriteLine("计算结果:{0}"res);

例如上面要绑定TestD方法的委托,返回类型为long,因此使用的委托是Func<int,int,long>。

将方法作为参数进行传递

如果没有委托类型,是无法将一个方法作为参数进行传递的,这也是委托的另一个作用。因为委托本身是类,属于引用类型,通过参数传递后,它所绑定的方法的引用也随之被传递。虽然是间接地实现将方法人为参数传递,但也的确能实现该功能。 本实例以Predicate委托为例,在一个整形数组中查找出可以被2或3整除的整数。 Predicate委托实例可以用于数组或列表类型,主要功能是用于查找匹配的元素。这使得元素的查找逻辑与集合对象分开,开发者可以通过Predicate委托来绑定自定义的查找方法。

int[]arr={16,21,20,11,18,37,41,77};

// 调用FindAll方法查找所有匹配的元素,被找到的元素会组成一个新的数组并返回。
int[]resarr=Array.FindA1l(arr,element=>if(((element$2)==0)l|((element $3)==0)){return true;}return false;};

Predicate委托的声明如下:

public delegate bool Predicate<in T>(T obj);

该委托带有一个T泛型参数,可以最大限度地发挥其灵活性,返回类型为布尔类型,如果元素找到就返回true,要是找不到就返回false。FindAll方法在访问数组中的每个元素时,都会调用Predicate委托实例并把元素传递进去,如果返回true表明是匹配的,否则就是不匹配的。

Lambda表达式

Lambda表达的作用与匿名方法相似,但书写起来比匿名方法简洁,而且Lambda方法能够让编译器自动推测参数的数据类型,因此一般只需要给出参数名称即可,不要求明确指定参数的类型。Lambda表达式可以直接赋值给委托变量。 Lambda表达式的格式如下。

<参数列表>=><方法体>
    
// 如果没有参数,就需要一对空白的括号。
    
()=><方法体>

如果Lambda表达式的方法体有多行代码,则需要写在一对大括号中。

  • 调用基类构造函数 :base(param)

  • 重载基类函数:基类函数用virtual修饰,派生类同名函数用override修饰。

  • 彻底替换基类函数:同名函数用new修饰:

    //基类 
    public void Work() 
    //派生类替换
    public new void Work()
    
  • 接口:interface(或用abstract

垃圾回收:GC.Collect();

using语句块

在using语句块中,当代码流程执行完退出using语句块后,TextWriter中实现的Dispose方法就会被调用,以进行资源清理工作。

using(TextWriter wt = new TextWriter()) {
	wt.WriteText(”编程真快乐。”);
}

IDisposable接口

通过实现IDisposable接口进行回收清理,比析构函数更易于使用。首先,实现IDisposable接口后,类型会公开Dispose方法,可以在代码中随时调用以执行清理工作;其次,实现了IDisposable接口的类型可用于using语句块,当using语句块代码执行完成后会自动调用Dispose方法来清理资源。

显式实现接口

显式实现接口就是在实现接口的成员前面加上接口的名称。这种方法可以有效解决接口成员冲突问题。例如,IA接口中包含Play方法,而IB接口中也包含Play方法,若使用常规方式同时实现这两个接口,那么类型中只能有一个Play方法。

因为两个接口中Play方法的签名相同,只能出现一个。假设这两个接口中的Play方法签名相同,但是所代表的功能和含义不同,即IA接口中的Play方法用来播放音乐,而IB接口中的Play方法用来开始玩游戏。要同时实现这两个Play方法,就要让它显式地实现两个接口了。上述代码可以修改为以下形式。

class Test:IA,IB{
    void IA.Play(){}
    void IB.Play(){}
}

显式实现接口后,无法通过实现类的变量去调用Play方法,必须使用接口来声明变量才能访问。例如,如果要调用IA接口的Play方法,就要用IA类型去声明变量,赋值时引用Test类的实例,然后再通过变量调用Play方法。

显式实现接口后,无法通过实现类的变量去调用Play方法,必须使用接口来声明变量才能访问。例如,如果要调用IA接口的Play方法,就要用IA类型去声明变量,赋值时引用Test类的实例,然后再通过变量调用Play方法。

阻止类被继承sealed(密封类)

相当于c++里的final

匿名类

用var,例如:

var d = new { a=1, b=2};  d.a

枚举

指定枚举的基础类型

在声明枚举类型时,如果不指定其基础类型,则其常量值默认为int类型。在有特殊需要的情况下,可以明确指定枚举中常量值的基础类型。必须使用整数值的基础类型,例如int、byte、uint、long、ulong等,不能使用非整数类型,例如double。

常量的标志位运算

因为枚举类型的基础类型是整数类型,因此,枚举的常量值之间也可以进行位运算。即按位“与”(And)、按位“或”(Or),以及取反(Not)、异或(Xor)等。 支持按位运算的枚举类型在声明时需要应用FlagsAttribute,例如以下形式。

获取枚举中常量的名称

Enum.GetNameEnum.GetNames
string[]days=Enum.GetNames(typeof(DayOfWeek));

检查枚举实例中是否包含某个标志位

HasFlag

自定义特性类

特性是一种比较特殊的类,通常作为代码对象的附加部分,用于向运行时提供一些补充信息。特性一般有以下特征。

  1. 从Attribute类派生。
  2. 类型名称一般以Attribute结尾。尽管这不是语法规则,但开发者应当遵守这一约定。使用以Attribute结尾的类名,一方面便于他人识别(一看名字就知道是特性类);另一方面,在输入代码时可以将Attribute后缀省略,编译器能够自动识别。例如,MTAThreadAttribute 类是一个特性类,在代码中可以直接输入MTAThread。
  3. 在声明特性类时必须在类上应用AttributeUsageAttribute。AttributeUsageAttribute本身也是一个特性类,用于标注当前声明的特性类应用于哪些对象。例如,该特性是否只能在类上面应用,还是可以同时在类、属性和方法上应用。

注意:不要把特性与代码注释混淆。代码注释虽然也起着附加说明的作用,但是不参与代码编译。而特性是一种类,它本身是参与代码编译的,而且可以在代码中访问。

在同一对象上应用多个特性实例

在声明特性类时,应用的AttributeUsageAttribute特性类有一个名为AllowMultiple的属性,用于设置所声明的特性类是否允许在同一个代码对象上应用多个特性实例。该属性默认为false,即在同一个代码对象上只能应用一个特性实例。

在运行阶段检索特性实例

特性类的作用是为代码对象应用一些辅助的信息,因此在应用程序运行阶段,可以检索特性类实例的相关数据,对数据进行验证。 要实现在运行阶段检索特性,需要用到反射技术,即在运行时获取类型以及其成员相关的信息,然后再查找出已应用特性的对象。 通过Type类可以得到各种代码对象(类、方法、属性、字段等)的信息,它们的共同基类是MemberInfo,通过GetCustomAttribute扩展方法(在CustomAttributeExtensions类中定义)可以直接检索到已应用的特性实例。

PropertyInfo类表示与属性有关的信息,调用它的GetValue方法可以获取属性的当前值。将属性的当前值与MyAttribute实例中指定的值进行比较,可以验证属性值是否符合要求。

typeof运算符的作用

除了可以调用对象实例的GetType方法来获取Type实例外,还可以使用typeof运算符。使用该运算符的优点是不需要事先创建类型实例,直接将类型名称传递给运算符就能得到对应的Type实例。 Type类封装与某个类型相关的各种信息,例如类型名称、所继承的基类、是否为泛型类型等。通过Type类实例还可以对指定类型使用反射技术,例如在运行阶段动态调用某个方法。

将字节数组转换为字符串

BitConverter类是有特定用途的类型转换辅助类,它主要完成基础类型与字节数组之间的转换操作。其中,ToString方法可以将一个字节数组转换为单个字符串实例,数组中的每字节均输出为两位十六进制数,字节与字节之间用字符“-”连接。

超大整数BigInteger BigInteger类型的整数没有限定最大值和最小值,也就是说,理论上它可以存储无限大的整数。然而受到计算机内存的限制,很难真正做到“无限制大”。当Biglnteger耗尽可用内存时,就会抛出OutOfMemoryException异常。

日期时间DateTime

常用字符串操作

  • +、contains,split,join
  • 反转字符串
static string ReverseString(string input) char[]charr=input.ToCharArray(); Array.Reverse(charr); return new string(charr);
  • 填充剩余空白:padleft padright

判断字符是否为数字

char结构有两个方法可以用于检测某个字符是否为数字。

  • IsDigit方法仅能用于判断标准的十进制数字字符,即从0到9,共10个字符。

  • IsNumber方法的适用面更广,不仅可用于判断标准十进制数字字符,它还可用于判断其他Unicode数字字符,例如,“②”“)”“iv”“1/2”等。

使用StringBuilder组装字符串

“@”符号,表示将忽略字符串中的转义字符

处理字符串中出现的双引号

"the output content is \"data updated\""

如果字符串常量使用了“@”,此时转义失效,需要在字符串中出现的双引号可以用连续两个双引号表示。

如果字符串常量使用了“@”,此时转义失效,需要在字符串中出现的双引号可以用连续两个双引号表示。

@"run ""cmd"""

格式控制符

比较多,略,用到的时候查询

Console.WriteLine(”保留5位小数:{0}",d.ToString("0.00000")); 
Console.WriteLine("保留3位小数:{0}"d.ToString("0.000")); 
Console.WriteLine"保留1位小数:{0}"d.ToString("0.0")); 
Console.WriteLine("保留整数:{0}"d.ToString(“#”));

从二进制字符串产生int实例

Convert类公开了一组方法,支持将整数字符串转换为整数类型,这些方法包括:

byte ToByte(string value,int fromBase);
short ToInt16(string value,int fromBase); 
static int ToInt32(string value,int fromBase); 
long ToInt64(string value,int fromBase); 
sbyte ToSByte(string value,int fromBase); 
ushort ToUInt16(string value,int fromBase); 
uint ToUInt32(string value,int fromBase);
ulong ToUInt64(string value,int fromBase); 

这些方法都有一个共同的参数—fromBase。此参数用于指定value参数属于什么进 制的字符串。如果字符串表示的是十六进制的数值,那么fromBase参数的值为16。

Parse与TryParse方法

Parse方法一旦分析失败就会抛出异常。而TryParse方法在字符串分析失败后不会抛出异常,如果分析成功,该方法返回true,否则返回false。

对字符串进行UTF-8编码

在System.Text命名空间下的Encoding类可以完成字符串与字节序列之间的转换。

字符串的HTML编码

WebUtility类位于System.Net命名空间下,其功能是进行字符串的HTML编码与解码操作。

泛型与集合

实现泛型接口

当某个类型要实现包含泛型参数的接口时,可以考虑以下两种情况:

  1. 在实现泛型接口时就明确泛型参数的类型,即使用确定的类型替换接口中的泛型参数。
  2. 泛型参数的类型依然未确定,可以用当前类型中所指定的泛型参数去替换接口中原有的泛型参数。

泛型方法

泛型方法的声明方式与泛型类相似,在方法名称后直接指定泛型参数,而且也可以使用类型约束。 调用泛型方法时可分两种情况讨论:

  1. 如果类型参数被用于输入参数,那么在调用泛型方法的时候不需要明确指定参数类型,编译器会根据输入参数的值推断出类型。
  2. 如果泛型参数作为方法返回值,那么在调用方法时应当明确指定类型,因为编译器不能推断出返回值的类型。

Array类

Array类是所有数组类型的隐式基类,它与代码中所使用的数组实例之间的继承关系是由编译器来完成的,开发人员不需要编写实现代码。

查找符合条件的元素

有两种方法可以查找数组中满足特定条件的元素。

  • 第一种方法是查找单个元素。执行程序会逐一验证数组中的元素,一旦遇到符合条件的元素,就马上将该元素返回。无论数组中有多少个元素符合查找条件,代码只会返回第一个找到的元素。这种查找方式可以调用Find方法完成。

  • 第二种方法是把数组中所有符合条件的元素重新组合为一个新的数组实例,并返回给代码调用者。这种方式可以通过调用FindAl1方法来完成。 不论是Find方法还是FindAll方法,都带有一个委托类型的参数——Predicate,该委托的定义如下。

    delegate bool Predicate<in T>(T obj)
    

    输入参数obj可通过泛型参数来确定类型,在与该委托绑定的方法中,开发人员可以根据实际需求编写代码,对obj参数传递的对象进行验证,如果符合条件就返回true,否则返回false。 在Find方法或者FindAll方法中,执行程序会为数组中的每个元素调用一次Predicate实例,并把此元素传递给obj参数。只要调用委托的结果返回true,就表示它就是要查找的元素。

  • FindIndexFindLastIndex方法支持对数组中的元素进行查找

  • 判断数组中是否存在满足条件的元素,可以调用Exists方法

无重复元素的集合HashSet

HashSet是一个泛型集合,与其他集合类相比,它有一个明显的特征——该集合不能包含重复的元素。当调用Add方法向集合中添加元素后会返回一个布尔值,如果成功添加就返回true;如果在集合中已经存在要添加的元素,Add方法会返回false,并且元素不会被添加到集合中。

双向链表LinkedList

LinkedList是一个比较有趣的集合,它属于“双向”链表,支持如下操作。

  1. 在列表首部插入元素。
  2. 在列表尾部插入元素。
  3. 在某个元素之前插入元素。
  4. 在某个元素之后插入元素。
  5. 元素可以从列表中移除,也可以重新加入到列表的任意位置。

LinkedList是泛型集合,可用实际类型替换类型参数T。加入到列表中的元素都由LinkedListNode对象进行维护,称为结点。当元素被移除之前,可以通过结点的Previous 属性获得当前元素的前一个结点,或通过Next属性获取下一个结点。 每个LinkedListNode实例也是可单独维护的,结点从一个列表中移除并添加到另一个列表的过程中,不会分配新的内存空间。访问Value属性可以获取到与结点对应的元素值。 运用LinkedList集合类,可以对元素进行任意顺序“组装”。

自动排序的字典集合SortedDictionary<TKey,TValue>

SortedDictionary<TKey,TValue>与常规字典集合相比,多了一个特殊功能——自动排序。向字典中添加键/值对时,会自动将集合中所有项按照键(Key)进行排序。 可以自定义SortedDictionary集合的排序规则

ArrayList的使用

ArrayList类与List有些类似,支持在集合中添加和删除元素,但是ArrayList是面向object类型的,读写元素的过程中会发生大量的类型转换,从而增加性能开销。因此,比较庞大的集合应当优先考虑使用泛型集合,而小型集合使用ArrayList类所带来的性能开销较小

使用Span提升处理字符串的性能

.NET中许多类型都是在托管堆中分配内存的,在对连续内存块进行操作时,某些数据类型会比较花时间,其中最典型的是字符串。在处理字符串的过程中会不断创建新的实例,这些过程必将占用一定的时间。 .NET Core类库提供了一种特殊的结构——Span,它可以用于操作各种连续分布的内存数据。可以通过以下来源初始化Span

  1. 常见的托管数组。
  2. 栈内存中的对象。
  3. 本地内存指针。

此外,Span支持GC功能,不需要显式释放内存。与Span对应,还有一个只读版本——**ReadOnlySpan**。

多个Task同时操作ConcurrentBag集合

ConcurrentBag是一个泛集合,它较为明显的优点是可以在多个线程上同时访问,并且该集合是无序的,即从集合中取出元素的顺序可能与放入的顺序不一致。 要向集合中添加元素可以调用Add方法。而取出元素则有两个方法可用:Try Take方法取出元素然后把元素从集合中删除,TryPeek方法取出元素但不会删除元素。 要判断ConcurrentBag集合中是否存在元素,一种方法是访问Count属性,它表示集合中包含元素的个数;另一种方法是访问IsEmpty属性,如果集合为空则返回true。

跨线程访问BlockingCollection集合

BlockingCollection集合允许多线程访问(添加或移除元素)。当集合中的元素数量达到容量上限时,添加操作会被阻止,直到集合中有元素被移除(重新获得可用容量);同样地,当从集合中移除元素时,如果集合中无可用元素,那么移除操作会被阻止,直到集合中有新的元素加入。 多个线程可以同时调用Add或TryAdd方法向集合添加元素,也可以同时调用Take或TryTake方法移除元素。当调用CompleteAdding方法后,就不能再向集合中添加元素了,否则会引发异常。

元组Tuple类的使用

.NET框架最早引入元组的概念,是通过Tuple类实现的,而且存在多个泛型版本,可容纳元素数量为1~8个。

推荐使用的元组—ValueTuple

ValueTuple的使用方法与Tuple类似,但ValueTuple是值类型,不需要分配堆内存,效率更优于Tuple类。此外,ValueTuple的Item*成员都是公共字段,这使得ValueTuple在初始化之后仍然可以修改元素。 与Tuple类一样,ValueTuple也有两种初始化方法:一种是直接调用构造函数初始化;另一种是调用静态的Create方法直接获得元组实例。

将元组解构为变量

声明元组时,如果不使用变量名,则编译器会将元组中的各个字段自动解构为单独的变量,其语法如下。 (<类型><字段>,<类型><变量>,…)=(<为字段分配的值>);这样声明之后,元组中的每个字段都可以作为单独的变量被访问。当然也可以用var关键字来声明。 var(<字段列表>)=(<为字段赋值>);

求工序列表中最长的加工周期 var max=works.Max(w=>w.EndTime-w.StartTime); Console.WriteLine(“最长加工周期为:{0:$d}天{0:%h}时{0:%m}分{0:%s)秒”,max);

筛选出两个序列中的差异元素

当序列A调用Except扩展方法并将序列B作为参数传递时,该方法将筛选出序列A中与序列B不相同的元素;相反地,如果序列B调用Except扩展方法并将序列A传递给方法的参数,那么该方法将筛选出序列B中与序列A不相同的元素。调用方法后得到的返回值是一个新的序列,里面包含了有差异的元素。

通过FirstOrDefault方法获取序列中的第一个元素。

处理First方法抛出的异常

First方法的作用是从序列中取出第一个元素,但是如果序列是空的,调用First方法就会发生异常。解决方案有以下两种:第一种方案是将调用First方法的代码写在try…catch语句块中,显式捕捉可能发生的异常;第二种方案是调用另一个与First功能相似的扩展方法——FirstOrDefault,这个方法的功能也是从序列中取出第一个元素,但是如果序列是空的,就会返回元素类型的默认值。例如,如果元素类型是int,当获取不到元素时,方法就返回0。

GroupBy将原始序列进行分组

要将原始序列中的元素进行分组,可以调用GroupBy扩展方法。此方法有多个重载版本,本实例使用了以下重载的形式。 IEnumerableGroupBy<TSource,TKey,TResult>(this IEnumerable< TSource>source,Func<TSource,TKey>keyselector,Func< TKey,IEnumerable,TResult>resultSelector);其中有三个类型参数:TSource是原始序列中的元素,TKey是分组依据(例如按某对象的Age属性进行分组,那么TKey就是Age属性的类型),TResult是返回给调用方的已分组序列中的元素类型。 keySelector委托用于产生分组依据,resultSelector委托则用于产生分组结果。 resultSelector委托有两个输入参数:第一个参数是分组依据,即该分组的“标题”;第二个参数是隶属该分组下的元素所组成的子序列。

LINQ

按员工所属部门分组

通过LINQ查询,将员工信息序列按部门名称分组。 var g=from e in empsgroup e by e.Department;

使用LINQ将序列转换为XML文档

将字典集合转换为字符串序列

使用并行LINQ

文件

File.AppendA11Text(file_name,"绝对有佳人,”);
File.WriteA11Text(fileName,"第一次写入的文本。”);

使用LINQ语句查询,并按文件大小排序。

var q=from f in dir.EnumerateFiles("demo_*") order by f.Length select(FilelName:f.Name,FileSize:f.Length);

内存流MemoryStream

使用StreamWriter类读写文件

压缩与解压缩

使用DeflateStream类压缩文件

在System.IO.Compression命名空间下,框架已经封装了一组常用的类,用于对流进行压缩和解压缩,这些类的操作方法与流相似(毕竟它们都是从Stream类派生出来的)。

创建Zip压缩文档

ZipArchive类支持对zip压缩文档的基本管理,压缩文档中的每个文件(实体)由ZipArchiveEntry 类进行维护。调用ZipArchiveEntry实例的Delete方法可以将文件从zip文档中删除;调用Open方法将得到一个流实例,可以对压缩文档中的文件实体进行读写操作。 本实例将完成两项操作:首先创建一个zip压缩文档,并向该文档添加三个文件实体,每个实体都写入内容。然后将该压缩文档中的文件实体解压出来,分别存储到三个文本文件中。

使用GZipStream类压缩文件

读写内存映射文件MemoryMappedFile

内存映射文件,其实是在应用程序内存空间中划分的一块特殊内存,可以像操作磁盘文件那样,在内存中新建文件,并写入或读取内容。调用CreateViewStream方法可以获得一个MemoryMappedViewStream实例,并通过流来读写文件。

命名管道实例

实现本地进程之间的通信

命名管道是一种比较简单易用的通信方式,它支持同一台计算机上进程与进程之间,或者不同计算机上进程与进程之间的数据传输。要使用命名管道进行进程间的通信,需要用到System.IO.Pipes命名空间中的以下两个类。

  1. NamedPipeServerStream类:通信中的服务器,实例化该类型之后,需要调用WaitForConnection 方法或者WaitForConnectionAsync方法来侦听客户端连接。
  2. NamedPipeClientStream类:通信中的客户端,实例化该类型后,调用Connect方法可以向服务器发起连接请求。 NamedPipeServerStream类与NamedPipeClientStream类都是Stream的派生类,因此它们都可以以流的方式发送或接收数据。

单向管道通信

在调用NamedPipeServerStream和NamedPipeClientStream类的构造函数时,可以传递一个direction参数,该参数类型是PipeDirection枚举,用来指定管道的通信方向,它定义了以下三个值。

  1. Out:管道仅为输出模式,即只能写入消息。
  2. In:管道仅为输入模式,即只读通信。
  3. InOut:双向通信,可以写入消息,也可以读取消息。

当未指定direction参数的情况下,默认生成双向通信的管道(InOut)。 本实例将实现单向通信,服务器只能用于发送消息,而客户端只能读取消息。

序列化

二进制序列化用到的是BinaryFormatter类(需要引入System.Runtime.Serialization. Formatters.Binary命名空间),序列化时只要调用Serialize方法即可。

使用DataContractSerializer类进行序列化

将类型实例序列化为JSON格式

DataContractJsonSerializer类(位于System.Runtime.Serialization.Json命名空间中)支持把类型实例序列化为JSON数据格式。JSON格式数据的体积小,结构简单,在跨平台与跨网络传输数据时更为方便,因此在Web领域被广泛使用。

在序列化时忽略某些字段

XML序列化

XmlSerializer与XML序列化

异步与并行

启动新线程,并把文件名传递给新线程。 newThread.Start(file_name);真正向新线程传递数据的是在调用Start方法的时候。 调用Join方法,阻止主线程,等待新线程执行完成再继续。 newThread.Join();

  • 等待线程信号——ManualResetEvent
  • 等待线程信号—AutoResetEvent

线程锁

在.NET框架中则是以Monitor类来实现线程锁的,调用Enter方法锁定资源,随后调用Exit方法解锁资源。

要对代码进行修改,加上lock 语句块。

并行任务

启动Task的三种方法

并行任务能够充分利用多核处理器的资源,而且由框架底层自动调配。从综合性能上说,使用Task更优于Thread。执行新Task的方法与Thread相似,也是通过委托类型来封装要运行的代码。

串联并行任务

在一些复杂的处理逻辑中,经常会执行多个并行任务,并且这些任务都需要按照一定的顺序执行,在这种情况下,把并行任务进行串联比等待事件句柄信号更简单。 Task类公开ContinueWith实例方法,调用该方法后,会将当前任务与下一个要执行的任务串联,当前任务执行完成后就会启动下一个任务。

使用Parallel类执行并行操作

Parallel类是一个轻量级的并行操作执行类,主要用在基于for或foreach循环的并行代码上,该类会充分调配处理器的资源来运行循环,提升性能。 本实例将使用Parallel类启动并行的foreach循环来向文件写数据,每一轮循环负责写一个文件。

异步等待语法

声明异步方法

异步等待语法主要由一对语言关键字组成:async和await,这两个关键字通常是成对出现的。要让方法支持异步等待,必须要让方法的返回类型为Task或者Task。 方法的名称可以用“Async”结尾,以标注它是异步方法,例如RunAsync、DownloadAsync等。 调用异步方法时,可以加上await关键字,表示异步等待。在调用异步方法时,当前线程会进入等待状态,但不会阻塞;当异步方法执行完成后,会回到当前线程中并继续执行后面的代码。在使用了await关键字的方法中需要加上async关键字,表示调用了异步代码。

CancellationTokenSource

C# 使用 CancellationTokenSource 终止线程

网络编程

  • Socket

  • TcpListener 、TcpClient
  • UdpClient
  • HttpWebRequest 、HttpWebResponse
HttpWebResponse response =(HttpWebResponse)request.GetResponse();
using(Stream respStream=response.GetResponseStream()) {
	//写入文件 
	using(FileStream fs=new FileStream("down.jpg", FileMode.Create)) {
		respStream.CopyTo(fs);
	}
}
  • HttpClient

反射与Composition

文档信息

Search

    Table of Contents