——C#1.0版本时代委托 微软公司在C#中引入了委托(delegate)类型概念,通过委托可以将方法引用作为其他方法的参数进行传递,因此委托可以理解为一种托管的强类型的方法指针。 定义委托包含指定类型和返回值:
public delegate int Arithmetic(int num1,num2);
注:被委托的方法的签名与委托的签名一致
//定义一个委托
public delegate int Arithmetic(int num1,int num2);
public class Progran{
//定义被委托的方法
public static int Add(int num1,int num2){
return num1+num2;
}
//定义接收方法引用作为参数数的方法
public static void RunOperation(Arithmetic arith){
int n1=10;
int n2=20;
Console.WriteLine("{0}+{1}",n1,n2,arith(n1,n2));
}
}
//实例化委托并将委托对象传给执行方法
static void Main(string[] args){
Arithmetic arith=new Arithmetic(Add);
RunOperation(arith);
}
使用委托使程序员可以将方法引用封装在委托对象内,然后可以将该委托对象传递给可调用所有引用方法的代码,委托是一个面向对象且类型安全的指针,委托优点如下: 1、相当于方法作为另一个方法参数 2、在两个不能直接调用的方法中作为桥梁,如在多线程中的跨线程的方法调用就得用委托 3 、当不知道方法具体实现什么时使用委托,如在事件中使用委托
——C#2.0版本时代委托
匿名方法提供了一种传递代码块作为委托参数的技术,匿名方法没有委托只有主体语法如下: 委托 委托对象=delegate(参数列表){ //方法体 }
匿名方法最明显的好处就是可以降低额外另一个方法的工作量,另外一个好处就是可以直接访问调用者的变量,从而降低传参的复杂长度
匿名方法不是一个事先定义的方法,而是使用一个委托的代码块,在使用时和普通方法并没有区别,但是匿名方法可以在一定程度上减少系统开销
注: 1、匿名方法无法访问外界范围内带ref与out关键字的参数 2、在匿名块中不能访问unsafe的代码 3、在is运算符的左边用不了匿名方法
//定义一个委托
public delegate int Arithmetic(int num1,int num2);
public class Program{
static void Main(stirng [] args){
//实例化委托
Arithmetic arith=delegate(int num1,int num2){
return num1+unm2;
};
//调用委托
int n1=30;
int n2=40;
Console.WriteLine("{0}+{1}={3}",n1,n2,arith(n1,n2));
}
}
——C#3.0版本时代委托
Lambda表达式,是一种匿名方法,lambda方法体多于一条语句的要用大括号括起来,Lambda表达式中参数只在表达式有效
Lambda表达式使用情况: 1、将执行的代码传递给异步方法
2、编写LINQ查询表达式
3、创建表达式数
public delegate int Arithetic(int num1,int num2);
pulbic class Program{
static void Main(string[] args){
//使用Lambda表达式实例化委托
Arithmetic arith=(n1,n2)=>n1+n2;
//调用委托
int num1=11;
int num2=22;
Console.WriteLine("{0}+{1}={2}",num1,num2,arith(num1,num2));
}
}
Lambda表达式的变量范围: 在定义Lambda表达式的方法内或包含Lambda表达式的类型内,Lambda表达式可以应用范围内的外部变量,将以这种方式捕获的变量进行存储以备在Lambda表达式中使用,即使在其他情况下,这些变量将超出范围并进行垃圾回收,必须明确地分配外部变量,然后从能在Lambda表达式中使用该变量。
下列规则适用于Lambda表达式中的变量范围:
1、在Lambda表达式中捕获的变量将不会被作为垃圾回收,直至引用变量的委托符合垃圾回收的条件
2、在封闭方法中看不到Lambda表达式内引入的变量
3、Lambda表达式无法从封闭方法中直接捕获in、ref、out参数
4、Lambda表达式中的return语句不会导封闭方法返回
5、如果相应跳转语句的目标位于Lambda表达式块之外,Lambda表达式不得包含goto、break或continue语句,同样如果目标在块内部,在Lambda表达式块外部使用跳转语句也是错误的
——Action<T>委托和Function<T>委托
定义委托符合一下四种情况:
无参、无返回值委托
有参、无返回值委托
有参、有返回值委托
五参、有返回值委托
Action<T>委托的使用:适合无参、无返回值和有参、无返回值,其中<T>是泛型语法,表示类型的占位符,在委托使用时才会确定T的具体类型
Action 委托对象 //无参无返回值
Action<T>委托对象 //有一个参数、无返回值
Action<T1,...,Tn> //有多个参数、无返回值
Funct<T>委托使用:适合在无参、有返回值和有参、有返回值这两种应用场景下使用
Func<TResult> 委托对象 //无参有返回值
Func<T,TResult> 委托对象 //有一个参数、有返回值
Func<T1,....,Tn,TResult> //有多个参数、有返回值
其中<T1,...,Tn>表示泛型语法中参数类型占位符,TResult 表示泛型语法中返回类型占位符
查询数字集合中偶数代码块如下:
class Program{
static void Main(string[] args){
//实例化委托
Func<int,bool>func=(num)=>num%2==0;
List<int>numbers=new List<int>{1,2,3,4,5,6,7,8,9,10};
for(var i=0;i<numbers.Count;i++){
//调用委托
if(func(numbers[i])){
Console.WriteLine("偶数:{0}",numbers);
}
}
}
}
为了方便重用该筛选功能,可以使用扩展方法和泛型技术对集合增加条件过滤方法,在条件过滤方法中,过滤条件在方法调用时通过委托来确定eg:
//定义静态类
public static class ListExtend{
//定义扩展方法
public static List<T> ToFilter<T>(this List<T> self,Func<T,bool>func){
List<T> newList=new List<T>();
for(var i=0;i<self.Count;i++){
//调用委托
if(func(self[i])){
newList.Add(self[i]);
}
}
return newList;
}
}
class Program{
static void Main(stirng[] args){
//定义数字集合
List<int>numbers=enw List<int>{1,2,3,4,5,6,7,8,9,10};
//使用ToFilter<int>()方法过滤偶数
var list=numbers.ToFilter<int>((num)=>num%2==0);
//输出过滤后的结果
foreach(var item in list){
Console.WriteLine(item);
}
}
}
——C#新增语法特性
nameof:用于获取变量、类型或成员的简单(非限定)字符串名称,可以在错误消息中使用类型或成员的非限定字符串名称,而无需对字符串进行硬编码,这样也方便重构
定义Func()方法验证字符串的参数是否为空:
private void Func(string msg){
if(string.IsNullOrEmpty(msg)){
throw new ArgumentException(nameof(msg));
}
}
using System;
using SystemTest=System.Text;
namespace _01_nameof{{
class Program{
private static void Func1(int x){}
private string F<T>()=>nameof(T);
private void Func2(string msg){}
static void Main(stirng[] args){
var program=new Program();
Console.WriteLine(nameof(SystemTest));
Console.WriteLine(nameof(Func1));
COnsole.WriteLine(nameof(Program));
Console.WriteLine(nameof(F));
Console.Read();
}
}
}
注:如果需要获取完全限定名,可以通过typeof表达式和nameof结合使用
内插字符串:在C#6.0之后可以用$来构造字符串,内插字符串表示类似于包含表达式模板字符串,内插字符串表达式通过将包含的表达式替换为表达式结果的ToString变现形式来创建字符串
var name="C# New features";
Console.WriteLine($"Hello,{name}");
注:想在内插字符串中包含大括号,即 “{}”,请使用两个大括号即 “{ {} }”
null条件运算符:在C#6.0之后用于执行成员访问(?.)或索引(?[ ])操作之前,可以测试是否存在null值,这些运算符可以让程序员编写更少的代码来检查null值
string name=null;
Console.WriteLine($"1:{name?.Length}");
name="C# New features";
Console.WriteLine($"2:{name?.Length}");
Console.WriteLine($"3:{name?[0]}");
//该运算符的另一种用途是可以使用非常少的代码以线程安全的放肆调用委托如下
//普通的委托调用
Func<int>func=()=>0;
if(func!=null){
func();
}
//简化调用
func?.Invoke();
自动实现的属性:通过与初始化字段相似的方式来初始化制动属性,当属性访问器中不需要任何其他逻辑时,自动实现的属性会使属性声明更加简介,eg:定义Name属性并赋初始值的代码如下:
class MyClass{
public string Name{get;set;}="C# New features";
}
static void Main(stirng[] args){
var myClass=new MyClass();
Console.WriteLine(myClass.Name);
Console.Read();
}
让Name提供默认的返回值也可以理解为如下代码:
class MyClass{
public string Name{get;set}
public MyClass(){
Name="C# New feathers";
}
}
只有get访问器的自动属性: 可以定义只读自动属性,而不必使用完整属性语法定义属性,可以在声明属性的位置或类型的构造函数中初始化属性,如下代码展示了自动属性的新旧语法:
class Person{
//新语法
private string Name {get;}="C# New features";//不用带上private set;
//旧语法
public int Age{get;private set;};
}
具有表达式主体的函数成员:用于Lambda表达式中相同的轻量语法,声明具有代码表达式主体的成员,具有立即返回表达式结果,或单个语句作为方法主题的方法定义很常见,以下代码是使用=>定义此类方法语法快捷方式:
class MyClass{
public int this[int id]=>id; //索引
public double Add(int x,int y)=>x+y; //带返回值方法
public void Output()=>Console.WriteLine("Hi,C# New feathers");//无返回值方法
}
索引初始值设定项:可以初始化支持索引编制的集合的特定元素(初始化字典)如果集合支持索引,可以指定索引元素
以下代码定义了字典集合并设置了初始值:
//旧的方式
var otherNums=new Dictionary<int,string>(){
{1,"one"},
{2,"two"},
{3,"three"}
}
//新的语法
var nums=new Dictionary<int string>{
[7]="seven",
[9]="nine",
[13]="thirteen"
}
using static 类型:可以导入静态类型的可访问静态成员,以便可以在不使用类型名限定访问的情况下引用成员
using System;
using static System.Console;
namespace usingStaticNs{
class Program{
static coid Main(string[] args){
Console.WriteLine("Hello World!");
WriteLine("Hello World");//使用了 using static System.Console
}
}
}
注:using static仅导入可访问的静态成员和指定类型中声明的嵌套类型,不会导入继承的成员
元组(Tuple):元组在.Net Framework4.0就有了,但有以下两个缺点:
1、tuple会影响代码的可读性,因为它的属性名都是I“tem1,Item2...”
2、Tuple还不够轻量级,因为它是引用类型(Class)
在C#7.0中的元组(Value Tuple)解决了上述两个缺点:
1、ValueTuple支持语义上的字段命名
2、ValueTuple是值类型(Struct)
注:在C#7.0中使用ValueTuple需要引入System.ValueTuple程序集
使用元组步骤如下:
var tuple={1,2}; //使用简化方式创建元组
var tuple2=ValueTuple.Create(1,2); //使用静态方法Create()创建元组
var tuple3=new ValueTuple<int,int><1,2>; //使用new运算符创建元组
WriteLine($"first:{tuple.Item1},secone:{tuple.Item2},上面3种都是等价的");
原理解析:上面3种都是使用new运算符来创建实例的
创建给字段命名的元组如下:
//左边指定字段名称
(int one,int two)tuple={1,2};
WriteLine($"first:{tuple.one},secone:{tuple.two}");
//右边指定字段名称
var tuple2=(one:1,two:2);
WriteLine($"first:{tuple2.one},second:{tuple2.two}");
//左右两边同时指定字段名称
(int one,int two)tuple3=(first:1,second:2);/*此处会有警告:由于目标类型(XX)以及指定了其他名称,因为忽略元组名称*/
WriteLine($"first:{tuple3.one},second:{tuple3.two}");
注:左右两边同时指定字段名称,会使用左边的字段名称覆盖右边的字段名称(一 一对应)
解构元组:将主体分解成部分,C#7.0支持
var(one,two)=GetTuple();
WriteLine($"first:{one},second:{two}");
static(int,int)GetTuple(){
return(1,2)
}
原理解析:解构元组就是将元组中的字段值赋值给声明局部变量
注:在解构时,“=”左边能提取变量的数据类型(如上),元组中字段类型相同时即可提取具体类型,也可以是隐式类型,但元组中字段类型不相同时只能提取隐式类型
解构可以应用于.Net的任意类型,但需要编写Deconstruce()方法成员(实例或扩展)代码如下:
public class Student
{
public string Name{get;set;}
public int Age{get;set;}
public Student(string name,int age){
Name=name;
Age=age;
}
public void Desconstruct(out string name,out int age){
name=Name;
age=Age;
}
static void Main(string[] args){
var (Name,Age)=new Student("Mike",30);
WriteLine($"name:{Name},age:{Age}");
}
}
原理解析:编译后就是由其实例调用Deconstruct()方法,然后给局部变量赋值
分析:1、元组的原理是利用了成员类型的嵌套或者是成员类型的递归
2、在编译器很智能的前提下才能提供元组简洁的语法