C#1
//属性可以自动实现
public string Name{get; private set;}
public static List<Product> GetSampleProducts()
{
return new List<Product>//列表直接初始化
{
new Product {Name="JumpSugar",Price=3.4f},
}
}
委托
delegate
类型可以看作只定义了一个方法的接口,委托实例看作实现该接口的一个对象
简单委托的构成需满足以下条件
- 声明委托类型
指定参数列表和返回列表,规定类型实例能表示的操作
delegate void StringProcessor(string input);
-
必须有一个方法包含要执行的代码
要委托的方法需要具有和委托类型相同的签名
void Print(object x)
可以匹配,因为string
派生于object
-
必须创建一个委托实例
创建委托实例,指定在调用委托实例时就执行该方法
具体使用什么形式的表达式创建委托实例取决于操作使用实例方法还是静态
如果是静态方法只需指定类型名称,如果是实例方法需要先创建类型或它的派生类型的实例,和调用方法相同
- 必须调用委托实例
调用一个委托实例方法即可,该方法称为Invoke
using static System.Console;
delegate void StringProceesor(string message);//声明委托类型
class Person
{
string name;
public Person(string name)//构造函数和方法皆与委托类型兼容
{
this.name = name;
}
public void Say(string message)
{
WriteLine( $"{name} says: {message}" );
}
}
class Stuy
{
static void Main()
{
Person Alice = new Person( "Alice" );//使用构造函数创建对象
Person Bob = new Person( "Bob" );
StringProceesor AliceVoice, BobVoice;//声明创建委托变量
AliceVoice = new StringProceesor( Alice.Say );//将方法赋值给委托变量,意味着调用Alice方法就是执行Alice.Say方法
BobVoice = new StringProceesor( Bob.Say );//以此实现函数指针功能,在运行时动态赋值和调用不同方法
AliceVoice( "Hello Bob" );
BobVoice( "Hi Alice" );
}
}
[!info]
如果希望在单击某按钮后执行一些操作,使用委托就无需对按钮代码进行修改,只需调用某个方法
委托本质就是间接完成某些操作,增加复杂性的同时增加了灵活性
合并和删除委托
委托实例实际上有一个操作列表与之关联,称为委托实例的调用列表
System.Delegate
类型的静态方法Combine
和Remove
负责创建新的委托实例
Combine
负责将两个委托实例的调用列表连接到一起(一般不显式调用,使用+和+=操作符)Remove
负责从一个委托实例中删除另一个委托实例的调用列表(-和-=操作符)
委托是不易变的,创建委托实例后,有关它的一切不能改变,以此安全传递委托实例的引用,并把它们与其他委托实例合并.将null
和委托实例合并到一起,null
将被视为带有空调用列表的一个委托
调用委托实例时,它的所有操作都顺序执行,返回值只有最后一个方法的返回值,除非每次调用时使用Delegate.GetInvocationList
获取操作列表时都显式调用某委托
如果调用列表中的任何操作抛出一个异常都会阻止执行后续操作
[!note] 对事件的简单讨论
基本思想是让代码在发生某事件时做出响应,事件不是委托类型的字段,但C#提供了一种简写方式允许使用字段风格的事件
委托和事件都声明为具有一种特定的类型,对于事件来说,必须是一个委托类型
委托总结
-
委托封装了包含特殊返回类型和一组参数的行为,类似包含单一方法的接口
-
委托类型声明的类型签名决定哪个方法可用于创建委托实例,同时决定调用签名
-
委托实例是不易变的
-
每个委托实例都包含一个调用列表
-
委托实例可以合并到一起,也可以从一个委托实例中删除另一个
-
事件不是委托实实例,只是成对的add/remove方法,类似于属性的取值方法/赋值方法
类型系统的特征
C#是静态类型,每个变量都有一个特定的类型,且该类型在编译时是已知的,只有该类型已知的操作才允许,由编译器强制生效
object s = "hello";
Console.WritenLine((string)s.Length);
//编译器只把s看作object类型,如果想访问Length属性,必须让编译器知道s的值实际是一个字符串
而动态类型具有多种形式,它的实质是变量中含有值,但这些值不限于特定类型,编译器不能执行相同形式的检查。相反,执行环境试图采取一种合适的方式来理解引用值的给定表达式
[!info]
C#4引入了动态类型
引用类型的实例总是在堆上创建,但值类型不是一定保存在栈上,变量的值是它声明的位置存储的
假定一个类中有一个int
类型的实例变量,那么在这个类的任何对象中,该变量的值总是和对 象中的其他数据在一起,也就是在堆上。只有局部变量(方法内部声明的变量)和方法参数在栈上。 对于C#2及更高版本,很多局部变量并不完全存放在栈中
装箱和拆箱
- 装箱指将值类型转换为引用类型的过程,在装箱过程中,会创建一个包含值类型值的堆上的新对象,并将该值复制到新对象中
- 拆箱指将引用类型转换为值类型过程,在拆箱过程中,原始值类型值会从堆上的对象中提取出来,拆箱需要进行类型检查,确保引用类型确实包含期望的值类型
C#和.NET提供了一个装箱的机制,允许根据值类型创建一个对象,然后使用对这个新对象的一个引用
int i = 5;
object o = i;
int j = (int)o;
o的值必须是一个引用,而数字5不是引用,它是一个整数值。实际发生的事情就是装箱:运行时将在堆上创建一个包含值5的对象,o的值是对该新对象的一个引用。该对象的值是原始值的一个副本,改变i的值不会改变箱内的值
第3行执行相反的操作——拆箱。必须告诉编译器将object
拆箱成什么类型。如果使用了错误的类型(比如o原先被装箱成unit
或者long
,或者根本就不是一个已装箱的值)就会抛出一个InvalidCastException
异常。同样,拆箱也会复制箱内的值,在赋值之后,j和该对象之间不再有任何关系
[!caution]
要留意装箱和拆箱,是由于它们可能会降低性能。一次装箱或拆箱操作的开销是微不足道的,但假如执行千百次这样的操作,那么不仅会增大程序本身的操作开销,还会创建数量众多的对象,而这些对象会加重垃圾回收器的负担。这种性能损失通常也不是大问题,但还是应该引起注意
值类型和引用类型总结
-
引用类型变量的值是引用,而不是对象本身。不需要按引用来传递参数本身,就可以更改该参数引用的那个对象的内容
-
对于引用类型的变量,它的值永远是一个引用
-
对于值类型的变量,它的值永远是该值类型的一个值
-
无论是引用传递还是值传递,永远不会传递对象本身
-
引用类型的对象总是在堆上,值类型的值既可能在栈上,也可能在堆上,具体取决于上下文
-
引用类型作为方法参数使用时,参数默认是以“值传递”方式来传递的,但值本身是一个引用
-
值类型的值会在需要引用类型的行为时被装箱;拆箱则是相反的过程
C#2
使用泛型实现参数化类型
泛型有泛型类型(没有泛型枚举)和泛型方法
在期望出现一个普通类型的地方使用类型参数,类型参数是真实类型的占位符,在泛型声明中,类型参数放在一对尖括号中,使用逗号分隔,如Dictionary<Tkey,TValue>
如果没有为泛型类型参数提供类型实参,那么这就是一个未绑定泛型类型。如果指定了类型实参,该类型就称为一个已构造类型
-
使用泛型类型或方法时要用真实类型代替,称为类型实参
-
类型参数"接收"信息,类型实参"提供"信息,类型实参必须是类型
-
泛型类型可以重载,只需改变类型参数数量
泛型方法声明剖析:
List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> convert)
List<TOutput>:返回类型
ConvertAll:方法名
<TOutput>:类型参数
Converter<T,TOutput>:参数类型(泛型委托),Converter
方法是将List中的每个元素都转换为另一种类型,并且以新的List返回包含转换后元素的列表
convert:参数名
List<T>.ConvertAll<TOutput>
方法实战:
static double TakeSquareRoot(int x)
{
//计算给定整数的平方根
return Math.Sqrt(x);
}
//创建空整数列表
List<int> integers = new List<int>();
integers.Add(1);
integers.Add(2);
integers.Add(3);
integers.Add(4);
//创建类型为Converter<int,double>的委托converter并赋值方法
Converter<int,double> converter = TakeSquareRoot;
//将委托作为参数传递,将对列表每一个整数应用TakeSquareRoot方法,然后转换为浮点数
List<double> doubles=integers.ConvertAll<double>(converter);
foreach(double d in doubles)
{
Console.WriteLine(d);
}
类型约束
用于进一步指定哪一个类型实惨,约束要放到泛型方法或泛型类型声明的末尾,使用上下文关键字where
来引入,共有4种约束:
- 引用类型约束
确保使用类型实参是引用类型
struct Sample<T> where T : class
- 值类型约束
T : struct
,确保使用类型实参是值类型,包括枚举,但将可空类型排除在外,被约束为值类型后,就不允许使用==和!=进行比较 - 构造函数类型约束
T : new
,必须是所有类型参数的最后一个约束,它检查类型实参是否有一个可用于创建类型实例的无参构造函数
这适用于所有值类型:所有没有显式声明构造函数的非静态,非抽象类,所有显式声明了一个公共无参构造函数的非抽象类
使用工厂风格的设计模式时,构造函数类型约束非常有用。在这种设计模式中,一个对象将在需要时创建另一个对象。当然,工厂经常需要生成与一个特定接口兼容的对象——这就引入了最后一种约束
4. 转换类型约束
允许指定另一种类型,类型实参必须可以通过一致性,引用或装箱转换隐式地转换为该类型.还可以规定一个类型实参必须可以转换为另一个类型实参,这称为类型参数约束
[!tip]
default
关键字用于获取给定类型的默认值。它可以用于泛型类型、引用类型和值类型
可空类型
可空类型的核心部分是System.Nullable<T>
。除此之外,静态类System.Nullable
提供了一些工具方法,可以简化可空类型的使用
该T类型参数有一个值类型约束
Nullable<T>
最重要的部分就是它的属性,即HasValue
和Value
如果存在一个非可空的值(按照以前的说法,就是“真正”的值),那么Value
表示的就是这个值。如果(概念上)不存在真正的值,就会抛出一个InvalidOperationException
而HasValue
是一个简单的Boolean
属性,它指出是存在一个真正的值,还是应该将实例视为null
Nullable<T>
引入了一个名为GetValueOrDefault
的新方法,它有两个重载方法,如果实例存在值,就返回该值,否则返回一个默认值。其中一个重载方法没有任何参数(在这种情况下 会使用基础类型的泛型默认值),另一个重载方法则允许你指定要返回的默认值
Nullable<T>
是一个结构:一个值类型
T?
是一种用于表示可空整数的数据类型。它实际上是System.Nullable<int>
的缩写形式
int? nullable = 5;//创建可空整数类型
object boxed = nullable;//装箱为object
nullable = new int?();//设置为一个新的、空的可空整数值
nullable = (int?)boxed;//拆箱为可空整数类型
-
as
操作符用于将一个对象引用转换为指定类型(或其子类型)的引用,如果转换成功则返回转换后的引用,如果转换失败则返回null
as
操作符通常用于处理对象引用的安全转换,如果无法转换则不会引发异常 -
is
操作符用于检查一个对象是否兼容于指定类型(或其子类型),如果兼容则返回true
,否则返回false
is
操作符通常用于进行类型检查,以便根据对象的类型执行不同的操作 -
result = expression1 ?? expression2;
空合并运算符用于在对象为null
时提供一个默认值
如果expression1
不为 null,则result
等于expression1
的值;如果expression1
为 null,则result
等于expression2
的值
进入快速通道的委托
C#1中创建委托必须同时指定委托类型和要执行的操作
//C#1启动一个新线程
Thread t = new Thread(new ThreadStart(MyMethod));
//C#2可以简化,编译器可以通过上下文推断MyMethod方法是ThreadStart委托需要的方法签名,因此可以直接将方法传递给Thread类的构造函数,不需要显式创建委托
Thread t = new Thread(MyMethod)
使用匿名方法的内联委托操作
匿名方法允许指定一个内联委托实例的操作,作为创建委托实例表达式的一部分
//义了一个名为printReverse的Action<string>委托变量,并在委托中使用了匿名方法来将字符串反转
Action<string> printReverse = delegate(string text)
{
char[] chars = text.ToCharArray();
Array.Reverse(chars);
}
//可以使用lambda表达式来简化,Func泛型委托是表示有返回值的委托
Func<string, string> printReverse = (text) =>
{
char[] chars = text.ToCharArray();
Array.Reverse(chars);
return new string(chars);
};
逆变性不适用于匿名方法:必须指定和委托类型完全匹配的参数类型
可以省略参数列表,只需使用一个delegate
关键字,后跟方法代码块
button.Click += delegate{Console.WriteLine("hello");};
实现迭代器
迭代器是行为模式的一种范例,行为模式是简化对象间通信的设计模式
迭代器允许访问一个数据项序列中的所有元素
如果某个类型实现了IEnumerable
接口,就 意味着它可以被迭代访问。调用GetEnumerator
方法将返回IEnumerator
的实现,这就是迭代器本身。可以将迭代器想象成数据库的游标,即序列中的某个位置。迭代器只能在序列中向前移动,而且对于同一个序列可能同时存在多个迭代器操作
//新集合类型框架
using system;
using system.Collectionsions;
Ppublic class Iterationsample : IEnumerable
{
object[] values;
int startingPoint:
public Iterationsample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
/*实现GetEnumerator方法首先需要在某个地方存储某个状态
迭代器模式的一个重要方法就是不用一次返回所有数据,调用代码一次只获取一个元素
因此需要确定访问到数组中的哪个位置*/
public IEnumeratcr GetEnumerator()
{
return new IterationSampleIterator(this);
}
可以创建另外一个类来实现这个迭代器,使用C#嵌套类型可以访问它外层类型的私有成员这一特点,仅需存储一个指向父级IterationSample
类型的引用和关于所访问到的位置的状态
//嵌套类实现集合迭代器,该类在上述类内
class IterationSampleTerator : IEnumrator
{
IterationSample parent;
int position;
//构造函数,接收一个IterationSample对象
internal IterationSampleIterator(IterationSample parent)
{
this.parent = parent;
position = -1;
}
public bool MoveNext()//用于在集合中移动到下一个元素
{
if(position != parent.values.Length)
{
++position;
}
return position <parent.values.Length;
}
public object Current//获取当前元素
{
get
{
if(position == -1 || position == parent.values.Length)//判断是否越界
{
throw new InvalidOperationException();
}
//计算index
int index = position + parent.startingPoint;
index = index % parent.values.Length;
return parent.values[index];
}
}
public void Reset()
{
position = -1;
}
}
利用yield
简化迭代器
yield return
:将单个元素返回给调用者,然后继续迭代yield break
:终止迭代
//使用yield return来迭代上述集合
public IEnumerator GetEnumerator()
{
for(int index = 0;index < values.Length;++index)
yield return values[(index+startingPoint)%values.Length]
}
如果方法声明的返回类型是非泛型接口,那么迭代器块的生成类型yield type
是object
,否则就是泛型接口的类型参数
所有yield return
语句都必须返回和代码块的生成类型兼容的值
C#2其余特性
分部类型
由多个源代码文件组成的类型称为分部类型
使用partial
关键字,可以定义分部类,结构,方法
在单文件中,成员和静态变量初始化会按照它们在文件中的顺序来发生,不过使用多文件时,就不一定按照这样的顺序,依赖于文件中的声明顺序是脆弱的,要特别注意移动代码后的后果
分部类型可以用于辅助重构,将臃肿的类型分为较小类
独立的取值方法/赋值方法属性访问器
int ID;
public int ID
{
get {return ID;}
private set {ID = value;}
}
默认情况下取值方法/赋值方法和属性本身整体上保持一致的访问修饰符
命名空间别名
当命名空间名称出现冲突时,要么每次使用它们时都设定包含命名空间的完整名称,要么取别名以一种类似命名空间缩写的方式来区分二者
using SC = System.Collections;
//使用::命名空间别名修饰符来告知编译器使用别名
SC::IEnumerator
//使用命名空间层级的根或全局命名空间定义别名
global::名字
//外部别名
extern alias FirstAlias;//导入具有相同名称的不同程序集中的类型,指定外部别名
using FA = FirstAlias;//使用命名空间别名来使用外部别名
C#3
智能编译器
自动实现属性
C#2允许为取值方法和赋值方法指定不同的访问权限。这个特性并未取消,现在仍可使用。 另外还可以创建静态的自动属性
如果使用自动属性,那么所有构造函数都需要显式调用无参构造函数this()
只有这样,编译器才知道所有字段都被明确赋值
public struct Fu
{
public int Value{get;private set;}
public Fu(int value) :this()
{
this.Value = value;
}
}
隐式类型局部变量
局部变量可以使用var
类型,编译器会获取初始化表达式在编译时的类型,并使变量也具有那种类型,可以是任何普通的.NET类型,包括泛型,委托和接口
只有在以下情况下才能用隐式类型:
- 被声明的变量是一个局部变量,而不是静态字段和实例字段
- 变量在声明的同时被初始化
- 初始化表达式不是方法组,也不是匿名函数(如果不进行强制类型转换)
- 初始化表达式不是
null
- 语句中只声明了一个变量
- 你希望变量拥有的类型是初始化表达式的编译时类型
- 初始化表达式不包含正在声明的变量
简化初始化
将使用对象初始化程序,初始化列表指定了在对象创建好之后如何对其进行初始化,可以设置属性,设置属性的属性,还可以向通过属性访问的集合中添加内容
//定义一个简单的类
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
//在创建对象时使用对象初始化器
//省略了构造函数圆括号,如果类型有一个无参构造函数就可以使用这种简写方式
Person person = new Person
{
Name = "Alice",
Age = 30
};
使用集合初始化程序来创建新集合
var names = new List<string>
{
"Hollyy","Jon","Tom"
};
集合初始化列表并非只能应用于列表。任何实现了IEnumerable
的类型,只要它为初始化列表中出现的每个元素都提供了一个恰当的公有的Add
方法,就可以使用这个特性。Add
方法可以接受多个参数,只需把值放到另一对大括号中。最常见的应用就是创建字典
Dictionary<string,int> nameAgeMap = new Dictionary<string,int>
{
{"Hollyy",36},
{"Jon",23},
{"Tom",19}
};
匿名类型
//创建具有Name和Age属性的匿名类型对象
var tom = new {Name="Tom",Age=19};
Lambda表达式和表达式树
Lambda
表达式都可以看做是C#2的匿名方法的一种演变。匿名方法能做的几乎一切事情都可以用Lambda
表达式来完成,且几乎所有情况下,Lambda
表达式都更易读,更紧凑
Lambda
表达式的类型本身并非委托类型,但它可以通过多种方式隐式或显式地转换成一个委托实例
//使用匿名方法创建委托实例,接收一个string类型返回一个int值
//Func的最后一个泛型参数表示方法的返回类型,而之前的泛型参数表示方法的参数类型
Func<string,int> returnLength;
returnLength = delegate(string text){return text.Length;};
//使用Lambda表达式
returnLength = (string text)=>{return text.Length;};
可以用一个表达式表示方法主体时,可以只指定那个表达式,不使用大括号,不使用return
语句,也不添加分号
(string text) => text.Length;
可以使用隐式类型的参数列表,即只写出参数名,没有类型,但隐式和显式类型的参数 不能混合匹配.要么整个列表都是显式类型的,要么全部都是隐式类型的
text => text.Length;
//还可以省略圆括号
使用Lambda表达式记录事件
//标题,发送者,事件参数
static void Log(string title,object sender,EventArgs e)
{
Console.WriteLine($"Event:{title}");
Console.WriteLine($"Sender:{sender}");
Console.WriteLine($"Arguments:{e.GetType()}");
foreach(PropertyDescriptor prop in TypeDescriptor.GetProperties(e))//遍历时间参数的属性
{
string name = prop.DisplayName;
object value = prop.GetValue(e);
Console.WriteLine("{0}={1}",name,value);
}
}
Button button = new Button ( Text ="click me"};
//使用匿名方法将按键事件记录到Log中
button.click += (src, e) => Log("click", src, e);
button.KeyPress += (src, e) => Log("KeyPress", src, e):
button.Mouseclick += (src,e)=> Log("Mcuseclick", src,e);
Form form = new Form ( Autosize = true, Controls = ( button ) );//创建form窗体,设置自动大小,将按钮添加微信窗体控件
Application.Run(form) ;//运行了应用程序的消息循环,启动了窗体并处理事件
表达式树
主要用于LINQ,是一种表示代码中的表达式的数据结构。它允许在运行时以一种结构化的方式表示代码,并对其进行操纵
是对象构成的树,树中每个节点本身都是一个表达式。不同的表达式类型代表能在代码中执行的 不同操作:二元操作(例如加法),一元操作(例如获取一个数组的长度),方法调用,构造函数调用等等
System.Linq.Expressions
命名空间包含了代表表达式的各个类,它们都继承自Expression
,一个抽象的主要包含一些静态工厂方法的类,这些方法用于创建其他表达式类的实例
Expression
类也包括两个属性
Type
属性代表表达式求值后的.NET类型,可把它视为一个返回类型。例如,如果一个表达式要获取一个字符串的Length
属性,该表达式的类型就是int
NodeType
属性返回所代表的表达式的种类。它是ExpressionType
枚举的成员,包括LessThan、Multiply
和Invoke
等。仍然使用上面的例子,对于myString.Length
这个属性访问来说,其节点类型是MemberAccess
看不懂了,完全不能理解它的作用
// 创建参数表达式
ParameterExpression a = Expression.Parameter(typeof(int), "a");//创建一个表达式树节点,表示一个整数类型参数或变量,名称为a
ParameterExpression b = Expression.Parameter(typeof(int), "b");
// 创建加法表达式
BinaryExpression add = Expression.Add(a, b);
// 将表达式树封装为Lambda表达式
Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(add, a, b);
// 编译表达式树以获取委托
Func<int, int, int> addFunc = lambda.Compile();
// 执行委托
int result = addFunc(3, 5);
Console.WriteLine(result); // 输出: 8
可以用Labmbda
表达式创建表达式树,但不能将带有一个语句块的表达式或包含赋值操作的表达式转换
Expression<Func<int>> return5=()=>5;
Func<int> compiled=return5.Compile();
扩展方法
C#的扩展方法是一种特殊类型的静态方法,它允许向现有的类添加新的方法,而无需修改原始类的代码。扩展方法通过添加一个静态方法来为已有的类新增功能
使用扩展方法必须具有以下特征
- 必须在非嵌套,非泛型的静态类中,必须是静态方法
- 至少有1个参数
- 第1个参数必须附加
this
作为前缀,且不能有其他任何修饰符 - 第1个参数类型不能是指针类型
public static class IntExtensions
{
public static int MultiplyBy(this int number, int multiplier)
{
return number * multiplier;
}
}
class Program
{
static void Main()
{
int result = 5.MultiplyBy(3);
Console.WriteLine(result);
}
}
定义了一个名为MultiplyBy
的扩展方法,它接受一个整数类型的参数并返回一个经过乘法运算后的整数。可以通过.
运算符在整数值上调用这个扩展方法,尽管整数类型并没有定义这个方法
要注意的是,扩展方法并不会修改现有的类或接口,它只是一种语法上的便利,允许像调用实例方法一样调用这些静态方法
[!attention]
如果存在适当的实例方法,实例方法肯定优先于扩展方法冲突,且编译器不会提示存在一个和现有实例方法匹配的扩展方法
Enumerable类
大多数时候无需功能强大的查询表达式解决问题,Enumerable
含有大量方法
//Range方法将生成一个包含0到 9的整数序列
//Range方法并不会真的构造含有适当数字的列表,它只是在恰当的时间生成那些数
var range = Enumerable.Range(0,10);
//ALL方法判断序列中的所有元素是否满足指定条件
bool allChina = products.All(p => p.Region == "中国");
//Any方法判断序列中是否至少存在一个元素满足指定条件
bool anyChina = products.Any(p => p.Region == "中国");
//Count方法统计满足条件的记录数
int countIdGreater5 = products.Count(p => p.ID > 5);
//Max找出属性最大值
decimal maxPrice = products.Max(p => p.Price);
//Min找出属性最小值
int minId = products.Min(p => p.ID);
//Average方法计算属性平均值
decimal avgPrice = products.Average(p => p.Price);
//Sum方法计算属性的总和
decimal sumPrice = products.Sum(p => p.Price);
//Aggregate累加方法
//第1个参数是初始累加值,可选
//第2个参数是累加操作函数,接收两个参数,当前累加值和集合中每个元素,返回累加后的值
var result = numbers.Aggregate(0,(acc,val)=>acc+val);
//Reverse方法反转集合
var result=Enumerable.Range(0,10).Reverse();//9-0
//Where方法用于筛选集合中满足特定条件的元素
var result = numbers.Where(n => n % 2 == 0);
//Select方法对集合中的每个元素应用指定的转换操作,生成一个新的序列
var result = numbers.Select(n => n * 2);
//OrderBy对查询结果进行升序或降序排序
var sortedData = source.OrderBy(element => element.Property);
//如果要对多个属性进行排序,可在后面继续.ThenBy()
//降序排序
var sortedDataDescending = source.OrderByDescending(element => element.Property);
//group by子句用于按键对序列中的元素进行分组,返回一个将元素分组的序列
var groupLinq = (from p in products//对哪个里面的元素操作
group p by p.IsFavorite
into g//根据什么属性分组,存储到g中
select new { IsFavorite = g.Key, SumPrice = g.Sum(item => item.Price), CountItem = g.Count() }).ToList();//进行求和操作,然后转换为列表
使用示例
var sortedDepartments = company.Departments
.Select(dept => new
{
dept.Name,
Cost = dept.Employees.Sum(person => person.Salary)
})//计算每个部门的员工薪水总和
.OrderByDescending(deptwithCost => deptwithCost.Cost)
.ToList();//降序排序
var groupedBugs = bugs
.GroupBy(bug => bug.AssignedTo)//根据属性分组
.Select(list => new { Developer = list.Key, Count = list.Count() })//计算每个分组的数量
.OrderByDescending(x => x.Count)
.ToList();
C#4
简化代码
可选参数和命名实参
参数(也称为形式参数)变量是方法或索引器器声明的一部分,而实参是调用方法或索引器时使用的表达式
void Fu(int x,int y)//形参
{
...
}
int a = 10;
Fu(a,20);//实参
声明可选参数可以在不需要时在调用时省略它们
public void Person(string name,int age=18,int ID=0)
- 可选参数必须出现在必备参数后,参数数组(用
params
修饰符修饰)除外 - 参数数组不能声明为可选,如果调用者没有指定值,将使用空数组代替
- 可选参数不能使用
ref
或out
修饰符 - 可选参数必须是常量:数字/字符串常量/null/const/枚举成员/default(T)操作符
命名实参
通过指定参数名来为方法的参数赋值,而不必严格按照方法定义时的参数顺序,编译器会判断参数名称是否正确,并将指定指赋给该参数,命名实参可以与可选参数同时出现
void DisplayCustomerDetails(string name, int age, string city)
{
Console.WriteLine($"Name: {name}, Age: {age}, City: {city}");
}
// 使用命名实参来调用方法
DisplayCustomerDetails(age: 30, name: "Alice", city: "New York");
//如果要对包含ref或out的参数指定名称,需要将ref或out修饰符放在名称之后
Display(result : out number);
C#5
使用async/await进行异步编程
异步编程可以编写非阻塞的代码,以便在等待诸如I/O操作或长时间运行的计算时,允许其他代码继续执行
C#6引入异步函数概念,通常指使用async
修饰符声明,可包含await
表达式的方法或匿名函数
如果表达式等待的值还不可用,那么异步函数将立即返回,当该值可用时,异步函数将在适当的线程上回到离开的地方继续执行,此前"在这条语句完成之前不执行下一条语句"的流程依然不变,只是不再阻塞
//可以使用异步编程来处理诸如加载资源、网络请求和其他I/O操作等情况
//unity不是特别支持async,所以此处例子为协程
public class AsyncExample : MonoBehaviour
{
private string url = "http://example.com/yourimage.png"; // 图片的URL地址
private Texture2D texture; // 用于存储加载完的纹理
void Start()
{
StartCoroutine(LoadTextureAsync()); // 在Start方法中启动一个协程来异步加载纹理
}
private IEnumerator LoadTextureAsync()
{
//创建一个UnityWebRequest对象用于加载纹理
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(url))
{
//使用yield关键字等待异步操作的完成
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success) //检查异步操作的结果
{
//加载出错处理
}
else
{
//从UnityWebRequest中获取加载完的纹理
texture = DownloadHandlerTexture.GetContent(www);
}
}
}
}
//普通C#环境中的异步操作
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
class Program
{
//使用async标记为异步方法
static async Task Main(string[] args)
{
string filePath = "sample.txt"; // 定义文件路径
// 调用异步方法来读取文件内容
string content = await ReadFileAsync(filePath);
Console.WriteLine($"File content: {content}");
}
//使用async标记为异步方法,返回一个字符串
static async Task<string> ReadFileAsync(string filePath)
{
//使用FileStream打开文件流
using (FileStream sourceStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
{
//创建一个字节数组
byte[] buffer = new byte[sourceStream.Length];
//使用await等待异步读取文件操作完成
await sourceStream.ReadAsync(buffer, 0, (int)sourceStream.Length);
//将字节数组转换成字符串并返回
return Encoding.UTF8.GetString(buffer);
}
}
}
async
关键字- 标记方法或
Lambda
表达式,表示是异步 - 异步方法返回类型通常是
Task
或Task<T>
,这样可以在异步操作完成时提供对结果的访问,并保持异步方法的非阻塞特性,返回还可以是void
类型,除此之外不能使用其他返回类型
它们与异步操作的状态和结果同步,这允许调用方通过等待异步任务的完成来获取结果,而不会阻塞主线程Task
:当异步方法不返回任何结果时,可以使用Task
作为返回类型Task<T>
:当异步方法需要返回一个结果时可以使用,可以改为特定返回类型
- 标记方法或
await
关键字- 只能在异步方法内使用, 用于暂停异步方法执行,等待一个异步操作完成
public class Example
{
// 异步方法,用于获取数据
public async Task<string> GetDataAsync()
{
// 模拟异步操作,例如从网络或文件中获取数据
await Task.Delay(1000); // 使用await暂停当前异步方法的执行
return "Async data"; // 返回异步获取的数据
}
// 异步方法,运行异步操作
public async Task RunAsync()
{
Console.WriteLine("Start"); // 在控制台输出 Start
string result = await GetDataAsync(); // 使用await等待异步操作完成,并获取数据
Console.WriteLine(result); // 在控制台输出异步获取的数据
}
}
异步方法几乎包含所有常规C#方法所包含的内容,只是多了一个
await
表达式。可以使用任意控制流:循环、异常、using
语句等
C#5异步函数特性的一大好处是,它为异步提供了一致的方案。但如果在命名异步方法以及触发异常等方面做法存在着差异,则很容易破坏这种一致性。微软因此发布了基于任务的异步模式(Task-based Asynchronous Pattern,TAP),即提出了每个人都应遵守的约定
- 异步方法名称应以
Async
为后缀,如GetImageAsync
,但可能存在命名冲突,建议使用TaskAsync
后缀 - 创建异步方法通常考虑提供4个重载,具有相同基本参数,但要提供不同选项,以用于进度报告和取消操作
调用者信息特性
CallerFilePathAttribute、CallerLineNumberAttribute和CallerMemberNameAttribute
均位于System.Runtime.CompilerServices
命名空间下。和其他特性一样,在应用时可以省略Attribute
后缀
//CallerFilePathAttribute特性允许获取调用该方法的方法的源文件路径
public void Log(string message, [CallerFilePath] string filePath = "")
{
Console.WriteLine($"Message: {message}, Called from: {filePath}");
}
//CallerLineNumberAttribute特性允许获取调用该方法的方法的代码行号
public void Log(string message, [CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"Message: {message}, Called from line: {lineNumber}");
}
//CallerMemberNameAttribute特性允许获取调用该方法的方法的方法名
public void Log(string message, [CallerMemberName] string methodName = "")
{
Console.WriteLine($"Message: {message}, Called from method: {methodName}");
}
//多介绍一个好用的特性,DefaultValueAttribute,给成员添加默认值
[DefaultValue("Unknown")]
public string Name
{
get { return _name; }
set { _name = value; }
}
特性对动态类型无效,成员特性名适用于所有成员