马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
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
假如没有为泛型类型参数提供类型实参,那么这就是一个未绑定泛型类型。假如指定了类型实参,该类型就称为一个已构造类型
- 使用泛型类型或方法时要用真实类型代替,称为类型实参
- 类型参数"吸收"信息,类型实参"提供"信息,类型实参必须是类型
- 泛型类型可以重载,只需改变类型参数数量
泛型方法声明分析:- List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> convert)
- List<TOutput>:返回类型
- ConvertAll:方法名
- <TOutput>:类型参数
- Converter<T,TOutput>:参数类型(泛型委托),Converter
- 方法是将List中的每个元素都转换为另一种类型,并且以新的List返回包含转换后元素的列表
- convert:参数名
复制代码 List.ConvertAll方法实战:- 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 where T : class
- 值类型约束
T : struct,确保使用类型实参是值类型,包括枚举,但将可空类型清除在外,被约束为值类型后,就不允许使用==和!=进行比较
- 构造函数类型约束
T : new,必须是所有类型参数的最后一个约束,它查抄类型实参是否有一个可用于创建类型实例的无参构造函数
这适用于所有值类型:所有没有显式声明构造函数的非静态,非抽象类,所有显式声明了一个公共无参构造函数的非抽象类
使用工厂风格的设计模式时,构造函数类型约束非常有用。在这种设计模式中,一个对象将在须要时创建另一个对象。当然,工厂经常须要生成与一个特定接口兼容的对象——这就引入了最后一种约束
4. 转换类型约束
允许指定另一种类型,类型实参必须可以通过同等性,引用或装箱转换隐式地转换为该类型.还可以规定一个类型实参必须可以转换为另一个类型实参,这称为类型参数约束
[!tip]
default关键字用于获取给定类型的默认值。它可以用于泛型类型、引用类型和值类型
可空类型
可空类型的焦点部分是System.Nullable。除此之外,静态类System.Nullable提供了一些工具方法,可以简化可空类型的使用
该T类型参数有一个值类型约束
Nullable最重要的部分就是它的属性,即HasValue和Value
假如存在一个非可空的值(按照以前的说法,就是“真正”的值),那么Value表示的就是这个值。假如(概念上)不存在真正的值,就会抛出一个InvalidOperationException
而HasValue是一个简朴的Boolean属性,它指出是存在一个真正的值,还是应该将实例视为null
Nullable引入了一个名为GetValueOrDefault的新方法,它有两个重载方法,假如实例存在值,就返回该值,否则返回一个默认值。此中一个重载方法没有任何参数(在这种情况下 会使用基础类型的泛型默认值),另一个重载方法则允许你指定要返回的默认值
Nullable是一个结构:一个值类型
T?是一种用于表示可空整数的数据类型。它现实上是System.Nullable的缩写形式- 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;
- }
- }
复制代码 可以用一个表达式表示方法主体时,可以只指定谁人表达式,不使用大括号,不使用return语句,也不添加分号- //使用yield return来迭代上述集合
- public IEnumerator GetEnumerator()
- {
- for(int index = 0;index < values.Length;++index)
- yield return values[(index+startingPoint)%values.Length]
- }
复制代码 可以使用隐式类型的参数列表,即只写出参数名,没有类型,但隐式和显式类型的参数 不能混合匹配.要么整个列表都是显式类型的,要么全部都是隐式类型的- int ID;
- public int ID
- {
- get {return ID;}
- private set {ID = value;}
- }
复制代码 使用Lambda表达式记载事件- using SC = System.Collections;
- //使用::命名空间别名修饰符来告知编译器使用别名
- SC::IEnumerator
- //使用命名空间层级的根或全局命名空间定义别名
- global::名字
- //外部别名
- extern alias FirstAlias;//导入具有相同名称的不同程序集中的类型,指定外部别名
- using FA = FirstAlias;//使用命名空间别名来使用外部别名
复制代码 表达式树
主要用于LINQ,是一种表示代码中的表达式的数据结构。它允许在运行时以一种结构化的方式表示代码,并对其进行操纵
是对象构成的树,树中每个节点本身都是一个表达式。不同的表达式类型代表能在代码中执行的 不同操作:二元操作(例如加法),一元操作(例如获取一个数组的长度),方法调用,构造函数调用等等
System.Linq.Expressions命名空间包含了代表表达式的各个类,它们都继承自Expression,一个抽象的主要包含一些静态工厂方法的类,这些方法用于创建其他表达式类的实例
Expression类也包括两个属性
- Type属性代表表达式求值后的.NET类型,可把它视为一个返回类型。例如,假如一个表达式要获取一个字符串的Length属性,该表达式的类型就是int
- NodeType属性返回所代表的表达式的种类。它是ExpressionType枚举的成员,包括LessThan、Multiply和Invoke等。仍然使用上面的例子,对于myString.Length这个属性访问来说,其节点类型是MemberAccess
看不懂了,完全不能理解它的作用- public struct Fu
- {
- public int Value{get;private set;}
- public Fu(int value) :this()
- {
- this.Value = value;
- }
- }
复制代码 可以用Labmbda表达式创建表达式树,但不能将带有一个语句块的表达式或包含赋值操作的表达式转换- //定义一个简单的类
- public class Person
- {
- public string Name { get; set; }
- public int Age { get; set; }
- }
- //在创建对象时使用对象初始化器
- //省略了构造函数圆括号,如果类型有一个无参构造函数就可以使用这种简写方式
- Person person = new Person
- {
- Name = "Alice",
- Age = 30
- };
复制代码 扩展方法
C#的扩展方法是一种特殊类型的静态方法,它允许向现有的类添加新的方法,而无需修改原始类的代码。扩展方法通过添加一个静态方法来为已有的类新增功能
使用扩展方法必须具有以下特征
- 必须在非嵌套,非泛型的静态类中,必须是静态方法
- 至少有1个参数
- 第1个参数必须附加this作为前缀,且不能有其他任何修饰符
- 第1个参数类型不能是指针类型
- var names = new List<string>
- {
- "Hollyy","Jon","Tom"
- };
复制代码 定义了一个名为MultiplyBy的扩展方法,它担当一个整数类型的参数并返回一个颠末乘法运算后的整数。可以通过.运算符在整数值上调用这个扩展方法,尽管整数类型并没有定义这个方法
要注意的是,扩展方法并不会修改现有的类或接口,它只是一种语法上的便利,允许像调用实例方法一样调用这些静态方法
[!attention]
假如存在得当的实例方法,实例方法肯定优先于扩展方法冲突,且编译器不会提示存在一个和现有实例方法匹配的扩展方法
Enumerable类
大多数时间无需功能强盛的查询表达式解决题目,Enumerable含有大量方法- Dictionary<string,int> nameAgeMap = new Dictionary<string,int>
- {
- {"Hollyy",36},
- {"Jon",23},
- {"Tom",19}
- };
复制代码 使用示例- //创建具有Name和Age属性的匿名类型对象
- var tom = new {Name="Tom",Age=19};
复制代码 C#4
简化代码
可选参数和命名实参
参数(也称为形式参数)变量是方法或索引器器声明的一部分,而实参是调用方法或索引器时使用的表达式- //使用匿名方法创建委托实例,接收一个string类型返回一个int值
- //Func的最后一个泛型参数表示方法的返回类型,而之前的泛型参数表示方法的参数类型
- Func<string,int> returnLength;
- returnLength = delegate(string text){return text.Length;};
- //使用Lambda表达式
- returnLength = (string text)=>{return text.Length;};
复制代码 声明可选参数可以在不须要时在调用时省略它们- (string text) => text.Length;
复制代码
- 可选参数必须出如今必备参数后,参数数组(用params修饰符修饰)除外
- 参数数组不能声明为可选,假如调用者没有指定值,将使用空数组代替
- 可选参数不能使用ref或out修饰符
- 可选参数必须是常量:数字/字符串常量/null/const/枚举成员/default(T)操作符
命名实参
通过指定参数名来为方法的参数赋值,而不必严酷按照方法定义时的参数次序,编译器会判断参数名称是否准确,并将指定指赋给该参数,命名实参可以与可选参数同时出现- text => text.Length;
- //还可以省略圆括号
复制代码 C#5
使用async/await进行异步编程
异步编程可以编写非阻塞的代码,以便在等待诸如I/O操作或长时间运行的计算时,允许其他代码继续执行
C#6引入异步函数概念,通常指使用async修饰符声明,可包含await表达式的方法或匿名函数
假如表达式等待的值还不可用,那么异步函数将立即返回,当该值可用时,异步函数将在得当的线程上回到离开的地方继续执行,此前"在这条语句完成之前不执行下一条语句"的流程依然不变,只是不再阻塞- //标题,发送者,事件参数
- 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) ;//运行了应用程序的消息循环,启动了窗体并处理事件
复制代码
- async关键字
- 标记方法或Lambda表达式,表示是异步
- 异步方法返回类型通常是Task或Task,这样可以在异步操作完成时提供对结果的访问,并保持异步方法的非阻塞特性,返回还可以是void类型,除此之外不能使用其他返回类型
它们与异步操作的状态和结果同步,这允许调用方通过等待异步任务的完成来获取结果,而不会阻塞主线程
- Task:当异步方法不返回任何结果时,可以使用Task作为返回类型
- Task:当异步方法须要返回一个结果时可以使用,可以改为特定返回类型
- await关键字
- 只能在异步方法内使用, 用于暂停异步方法执行,等待一个异步操作完成
- // 创建参数表达式
- 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
复制代码异步方法几乎包含所有通例C#方法所包含的内容,只是多了一个await表达式。可以使用任意控制流:循环、异常、using语句等
C#5异步函数特性的一大好处是,它为异步提供了同等的方案。但假如在命名异步方法以及触发异常等方面做法存在着差异,则很容易粉碎这种同等性。微软因此发布了基于任务的异步模式(Task-based Asynchronous Pattern,TAP),即提出了每个人都应遵守的约定
- 异步方法名称应以Async为后缀,如GetImageAsync,但大概存在命名冲突,建议使用TaskAsync后缀
- 创建异步方法通常考虑提供4个重载,具有雷同基本参数,但要提供不同选项,以用于进度陈诉和取消操作
调用者信息特性
CallerFilePathAttribute、CallerLineNumberAttribute和CallerMemberNameAttribute
均位于System.Runtime.CompilerServices命名空间下。和其他特性一样,在应用时可以省略Attribute后缀- Expression<Func<int>> return5=()=>5;
- Func<int> compiled=return5.Compile();
复制代码 特性对动态类型无效,成员特性名适用于所有成员
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|