Basics
- decimal 不是基本类型
- bool和整数不能隐式转换
'a'
是char类型,“a”
是string类型- object类型的两个目的:
- 用来绑定所有子类型对象,和反射
- 重写。其实现有
Equals()
,ToString()
,GetType()
.
- string
@
--》不会转义- string 修改时, 是新建一个对象
$
--》C# 6新特性, 允许{}里放变量或者代码表达式
- Enum
- 枚举是用户定义的整数类型
- Enum.Parse返回的其实是一个对象
- 命名空间一般格式为CompanyName.ProjectName.SystemSection
Main()
方法- 使用
static
修饰符 - 在任意类中
- 返回
int
或者void
- 使用
- 预处理器指令
- 场景:划分版本,比如基本版和企业版
- 最好不用DEBUG,调试的时候用debug模式会失效
#warning
只是提示,#error
会退出编译,不生成IL代码#pragma
是抑制或者还原指定编译警告
- 编程准则
@
是使用关键字为标识符的方法,比如:@abstract
- C#常量最好不要全部是大写,很难阅读。还是Pascal
- 私有常常是下划线加小驼峰
private int _subscribed
ClassAndType
-
属性
- .net 4.0后精简为
public int Age {get; set;}
- 初始值为
public int Age {get; set;} = 50;
- .net 4.0后精简为
-
方法
- C# 6 提供lambda给一个语句的方法
public bool IsSquare(Rectangle rect) => rect.Height == rect.Width;
- 参数可以带名字
r.Move(x: 30, y: 40, width: 50)
- 必须为可选参数提供默认值,并且可选参数应该是最后一个参数
- 如果多个可选参数,可以传递任意可选参数,并且可以乱序
TestMethod(1, opt3: 4);
params
关键字是任意数量的参数。AnyNumberOfArguments(1, 3, 4)
public void AnyNumberOfArguments(params int[] data){ foreach(var x in data) { WriteLine(x); } }
- C# 6 提供lambda给一个语句的方法
-
构造函数
- 如果只有一个带单个参数的构造函数,编译器不会创建默认构建函数。意思是说不会有没带参数的构造函数
- 私有构造函数使用场景:
- 类只访问静态成员或属性
- 单例模式。只能用静态方法来实例化。
- 构造函数初始化器。可以用来调用同一个类的另一个构造函数,或者积累的构造函数(用base,而不是this)
public Car { private string _description; private uint _wheels; public Car(string description, uint wheels){ _description = description; _wheels = wheels; } public Car(string description) : this(description, 4) { } }
- 静态构造函数
- 无参数
- 只执行一次,通常在第一次调用类成员之前执行。
- 没有修饰符,因为总是在类加载时调用
- 只能访问类的静态成员
- 和无参数的实例构造函数不冲突
-
只读成员
- 带有
readonly
修饰符的字段只能在构造函数中分配值 - 作为类成员时,需要用
static
修饰符 - C# 6 自动实现的只读属性
public string Id { get;} = Guid.NewGuid().ToString()
- readonly和const的区别:
const readonly 默认静态的 需要用static修饰符 必须在声明时给与值 可以在构造函数中赋值 不能继承 能继承 静态成员 实例成员 - 带有
-
匿名类型
- 编译器会伪造一个类名,只有编译器使用它。对新对象使用反射,不会得到一致的结果。
var captiain = new { FirstName = "Leo", LastName = "Dai" }
- 编译器会伪造一个类名,只有编译器使用它。对新对象使用反射,不会得到一致的结果。
-
结构体
- 值类型
- 不支持继承
- 默认构造函数会初始化成员为其默认值
- 默认构造函数总是隐式给出,即使提供了其他参数的构造函数
- 自定义构造函数里必须给所有成员初始值
- C# 6才可以实现默认构造函数,之前是不行的
- 如果使用属性, 对象就必须用new
- 和类的区别:
结构体 类 值类型 引用类型 可以不用new新建,new只是调用构造函数来初始化所有字段 必须用new来分配堆内存 不能继承 能继承 默认构造函数总是给出 如过声明了构造函数,就不会给出默认构造函数 -
可空类型
int? x = null
int y = x ?? -1
如果x为空给-1,否则提取x的值
-
枚举
- 值类型
- 默认情况下,类型是int。可以变为其他整数类型(byte, short, int, long)
public enum Color : short { Red = 1, Green = 2, Blue = 3 }
-
部分类
partial class
- 可以在一个部分类中声明一个方法,而在另一个部分类中实现这个方法
- 部分方法不能使用 访问修饰符
public partial class PartialClass { public void MethodOne() { PartialFunction(); } partial void PartialFunction(); }
-
扩展方法
- 继承就是给对象添加功能的好方法,扩展方法就是给对象添加一功能的另一个选项,在不能继承时也可以使用这个选项
- 扩展方法是静态的,是类的一部分
- 使用this关键字和第一个参数来扩展字符串
- LINQ利用了许多扩展方法
public static class StringExtension { public static int GetWordCount(this string s) => s.split().Length; }
-
Object类
- 其实所有类型都最终派生自System.Object
ToString()
: 对象的字符串表示GetHashCode()
: Get HashCodeEquals()
,ReferenceEquals()
Finalize()
: 类似C++的析构函数,在对象被垃圾回收的时候调用。Object中的此方法实际什么都没做,会被GC忽略,如果对象有为托管的引用,就一般需要重写Finalize()
GetType()
: 反射,得到对象的信息MemberwiseClone()
: 浅拷贝
Inheritance
- 一句话形容多态:通过继承实现的不同对象调用相同的方法,表现出不同的行为,称之为多态
- 虚方法
virtual
- 基类中声明为
virtual
,就可以在任何派生类中重写该方法,用override
- 如果只是一行,C# 6中
virtual
可以用于lambda表达式 - C#中,函数默认情况下不是虚拟的,但可以显式的声明为
virtual
(构造函数除外)。Java中所有函数都是虚拟的。 - 如果签名相同的方法在基类和派生类中进行声明,但是没有用
virtual
和override
,派生类会隐藏基类方法。而且子类应该使用new关键字来隐藏基类方法。 - 密封方法的一个场景是:表明这个方法终止继承。
- 基类中声明为
public
,protected
和private
是逻辑访问修饰符。internal
是一个物理访问修饰符,其边界是一个程序集。- 抽象类和接口的相同点和区别
is
和as
不会抛出exception
托管和非托管的资源
- 值类型
- 栈实际是向下填充的,即由高内存地址向低内存地址填充
- 栈是先进后出
- 引用类型
- 堆是向上分配
- 对象其实分配了堆和栈的,栈是分配了对象堆的地址。如果实例化了,就可以在堆中找到。
- 垃圾回收
- 托管堆其实分为4部分:第0代,第1代,第2代和大对象堆
- 第0代:新创建的对象会驻留在这个地方。小但是快。
- 第1代:经过了一个垃圾回收执行后,第0代就会进入第1代。
- 第2代:再经过一个或者更多垃圾回收执行后,第1代酒会进去第2代。
- 大对象堆:用于处理较大对象(大于85000个字节)。不执行压缩过程。和第2代相似。
- 固定对象堆(POH):此新堆(与大对象堆(LOH)对等)将允许GC单独管理固定对象,从而避免固定对象对堆的负面影响。(.Net5.0添加)
当第0代分配完,没有容量时,就会自动执行垃圾回收。
- 托管堆其实分为4部分:第0代,第1代,第2代和大对象堆
- 处理非托管资源
- C#中定义析构函数时,编译器发送给程序集的实际是Finalize()方法
- C#析构函数的问题是它的不确定行。C++销毁对象时,立即执行析构函数。但是由于C#垃圾回收器的方式,无法确定析构函数什么时候执行。(因为销毁是由垃圾回收器执行的)
IDisposable.Dispose()
的实现是显式的释放非托管资源,提供精确的释放using
的一个用法:非托管资源对象在超出作用域时,自动调用Dispose()
方法。会try/catch/finally
等价的IL代码。using (var theInstance = new ResourceGobbler()) { // do your processing }
泛型
- 优点
- 性能。
ArrayList
类是存储对象(引用类型),在Add()
方法时是把一个对象作为参数,如果是值类型就有装箱操作。泛型List<T>
T就定义了类型,就不会有装箱拆箱操作。 - 类型安全。
ArrayList
类其实可以添加任意类型,如果foreach
迭代操作,就可能出现类型转换错误。泛型List<T>
最开始就定义了类型,如果添加了不同的类型,在编译的时候就会报错。 - 命名约定。
- 泛型类型用字母
T
作为前缀 - 没有特殊要求,泛型类型允许用任意类型替代,且只是用了一个泛型类型,就用
T
作为泛型类型的名称public class List<T> {} public class LinkedList<T> {}
- 如果泛型类型有特定要求,或者使用了两个或者多个泛型类型,就给描述性的名称
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e); public delegate TOutput Converter<TInput, TOutput>(TInput from); public class SortedList<TKey, TValue> {}
- 泛型类型用字母
- 性能。
- 泛型类
- 默认值
- 因为泛型类型可能为值类型或者引用类型,不能把null赋予值类型,所以泛型的默认值用
default
关键字,将null赋予引用类型,0赋予值类型。
- 因为泛型类型可能为值类型或者引用类型,不能把null赋予值类型,所以泛型的默认值用
- 约束
where
关键字实现约束。public class DocumentManager<TDocument> where TDocument: IDcoument { //Implementation }
- 泛型还可以有多个约束
public class MyClass<T> where T: IFoo, new (){ //Implementation }
(new()
这个约束是约束T必须有一个构造函数)
- 继承
- 派生类可以是泛型类或非泛型类
- 静态成员
- 静态成员只能在一个实例中共享。
public class StaticDemo<T> { public static int x; } StaticDemo<string>.x = 4; StaticDemo<int>.x = 5; WriteLine(StaticDemo<string>.x); // 4
- 静态成员只能在一个实例中共享。
- 默认值
- 泛型接口的抗变与协变
- 协变:将子对象到父对象的转换。泛型接口中用
out
关键字标注,意味着返回类型只能是T
。.net中参数是协变的。 - 抗变:将父对象到子对象的转换。泛型接口中用
in
关键字标注,意味着只能把泛型类型T
用作其方法输入。 - C#的抗变与协变
- 协变:将子对象到父对象的转换。泛型接口中用
- 泛型方法
- 泛型方法可以再非泛型类中定义
- 泛型方法可以像非泛型方法一样调用
//泛型方法调用 int i = 4; int j = 5; Swap<int>(ref i, ref j); //非泛型方法调用 int i = 4; int j = 5; Swap(ref i, ref j);
- 泛型方法也可以用
where
字句来限制,定义约束。public static decimal Accumulate<TAccount>(IEnumerable<TAccount> source) where TAccount: IAccount { // Implementation }
- 带委托的泛型。
//定义 public static T2 Accumulate<T1, T2>(IEnumberable<T1> source, Func<T1, T2, T2> action) { // Implementation T2 sum = default(T2); foreach(var account in source) { sum = action(account, sum); } return sum; } //调用 decimal amount = Algorithm.Accumulate<Account, decimal>(accounts, (item, sum) => sum += item.Balance);
- 方法是在编译期间定义的,而不是运行期间。
- C#之Action和Func的用法
数组
-
数组是引用类型
-
锯齿数组
int[][] jagged = new int[3][]; jagged[0] = new int[2] {1, 2}; jagged[1] = new int[6] {3, 4, 5, 6, 7, 8}; jagged[2] = new int[3] {9, 10, 11};
-
Clone()
是创建浅表副本,值类型会赋值所有的值;引用类型不复制元素,只复制引用。 -
Copy()
也是创建浅表副本。与Clone()
的区别是:Clone()
是创建一个新的数组,Copy
是传入一个阶数相同且有足够元素的已有数组。 -
IComparable<T>
和IComparer<T>
的区别IComparable
IComparer
用 CompareTo()
比较用 Compare()
比较class实现 IComparable
接口,class内部实现CompareTo()
方法一个独立的class来实现 IComparer
接口,并实现Compare()
方法调用比较方法不同 Array.Sort(persons);
Array.Sort(persons, new PersonComparer(PersonTypeEnum.FirstName));
如果有新的成员比较,需要修改原类 一个独立类,不需要修改原类,可以在此独立类中扩展 -
foreach
语句(以下是其实现的方式)// foreach语句 foreach(var p in persons) { WriteLine(p); } // 生成的IL代码 IEnumerator<Person> enumerator = persons.GetEnumerator(); while(enumerator.MoveNext()) { Person p = enumerator.Current; WriteLine(p); }
-
yield
语句- 集合迭代返回
yield return
。就是把迭代的每次return
都返回回来,而不是只一次。 IEnumerator<T>
是GetEnumerator()
使用的返回接口。其他自定义的方法使用IEnumerable<T>
接口- 实例
public class HelloCollection { public IEnumerator<string> GetEnumerator() { yield return "hello"; yield return "world!"; } } // 自定义方法 public IEnumerable<string> Reverse() { for (int i = names.Length - 1; i >= 0; i--) { yield return names[i]; } } // hello worl调用 var helloCollection = new HelloCollection(); foreach (var item in helloCollection) { WriteLine(item); }
- 集合迭代返回
运算符和类型强制转换
checked
和unchecked
checked
会做溢出检查,如果溢出就抛出OverflowException
。unchecked
不会做溢出检查,如果溢出就会,溢出的位就会丢弃,不会抛异常。unchecked
是默认行为。只有需要在有checked
标记的大段代码中执行不检查一部分代码,才需要显式的用unchecked
关键字。checked
会影响性能。
nameof
运算符是C#6新引入。改运算符接收一个符号,属性或方法,并返回名称。- 可空类型
- 使用关键字
?
类型声明是,例如int?
,编译器会解析它,以使用泛型类型Nullable<int>
。 - 在比较可空类型是,只要一个参数是
null
,比较结果就是false。
int? a = null; int? b = -5; if(a >= b) { WriteLine("a >= b"); } else { WriteLine("a < b"); } // 总是执行else语句
- 使用关键字
??
合并运算符- 如果第一个操作数不是
null
,整个表达式就等于第一个操作数的值; - 如果第一个操作数是
null
,整个表达式就等于第一个操作数的值;
- 如果第一个操作数不是
- 空值传播运算符,C# 6的一个杰出新功能
- 使用空值传播运算符来访问FirstName属性(
p.FirstName
),当p
为空时,返回null
,而不执行表达式右侧。public void ShowPerson(person p) { string firstName = p?.FirstName; }
- 访问数组的第一元素,如果是
null
,空合并运算符返回x1
的值。int x1 = arr?[0] ?? 0;
- 使用空值传播运算符来访问FirstName属性(
- 类型转换
- 只能从较小的整数类型隐式转换为较大的整数类型
- 可空类型不能隐式转换为非可空类型
- 显式转换有丢失数据的风险
int b = (int)a;
- 显式转换也有限制,在值类型转换时只能在数字,
char
类型和enum
类型之间转换。不能直接把布尔型转换为其他类型,也不能把别的类型转换为布尔型。
- 比较对象的相等性
ReferenceEquals()
用于比较引用,是个静态方法,传入两个引用对象。Equals()
用于比较值,其实每个类型都有这个虚方法,可以重写它。==
是个中间选项
- 运算符重载
public static Vector operator +(Vector left, Vector right) => new Vector(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
- C#中的Explicit和Implicit
- Implicit关键字用于声明隐式的用户定义类型转换运算符。
- Explicit关键字声明必须通过转换来调用的用户定义的类型转换运算符。不同于隐式转换,显式转换运算符必须通过转换的方式来调用,如果缺少了显式的转换,在编译时就会产生错误。
委托,Lambda表达式和事件
- 委托
- 委托是一种特殊类型的对象,其特殊性在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或者多个方法的地址。
- 委托的类型安全性非常高,在定义它时,必须给出它的参数和返回类型。而C/C++只是一个指针,可以指向任何一个地址。
- 定义一个委托其实是定义一个新的类,派生自
System.MulticastDelegate
。 - 调用委托类的
Invoke()
和委托实例加()
是完全相同的。firstStringMethod(); firstStringMethod.Invoke();
- 为减少输入量,在需要委托实例的每个位置可以只传入地址的名称,这称为委托推断。以下初始化是相同的作用。
// new 委托实例 GetAString firstStringMethod = new GetString(x.ToString); // 委托推断 GetAString firstStringMethod = x.ToString;
Action<T>
是没有返回的泛型委托,Func<T>
是有返回值的泛型委托。它们都是最多可接受16种不同的参数。Func<double, double>[] operations = { MathOperations.MultiplyByTwo, MathOperations.Square} // 使用Func在以下方法中 static void ProcessAndDisplayNumber(Func<double, double> action, double value) { double result = action(value); WriteLine(result); }
- 多播委托就是一个委托包含多个方法,按照顺序连续调用。返回必须是
void
,否则只能得到委托调用的最后一个方法的结果。Action<double> operations = MathOperations.MultiplyByTwo; operations += MathOperations.Square;
- 可以使用
GetInvocationList()
得到Delegate
对象数据组。Action dl = One; dl += Two; Delegate[] delegates = dl.GetInvocationList();
- 匿名方法。C# 2中引入,但是在C# 3.0后用lambda替代了。了解就行。
Func<string, string> anonDel = delegate(string param){ param += "string subfix"; }
- lambda表达式
- 单条代码。在方法内部需要花括号和
return
语句,因为编译器会添加一条默认的return
语句。Func<double, double> square = x => x * x; // 等同于 Func<double, double> square = x => { return x * x};
- 多条代码就必须添加花括号和
return
了。Func<string, string> lambda = param => { param += mid; param += " and this was added to the string."; return param; }
- 闭包。通过lambda表达式可以访问lambda表达式外部的变量。
- 实现方式:是因为每个lambda表达式都会创建一个匿名类,编译器会创建一个构造函数来传递外部变量。
- lambda可以用于类型为委托的任意地方。
- 单条代码。在方法内部需要花括号和
- 事件
- C/S应用事件,比如button事件
- 用
event
关键字定义EventHandler<TEvents>(object sender, TEventArgs e)
泛型。最新的lib,都已没有where TEventArgs: EventArgs
约束。public event EventHandler<CarInfoEventArgs> NewCarInfo;
- 参照代码sample
字符串和正则表达式
System.String
类- 是一个不可变的数据类型。一旦初始化,修改字符串或者运算符其实是新建一个字符串。
- 如果频繁修改字符串,使用
StringBuilder
。它指定了内存大小,修改字符串是在其内存块中修改。当超过最大值时,它的容量会翻倍。 - 如果不给
StringBuilder
设置初识容量,那该容量默认设置为16。var strBuilder = new StringBuilder("this is a string.", 150);
- NULL,"",String.Empty三者在C#中的区别
- 字符串格式
- 以下是相同的
//$ string s1 = "World"; string s2 = $"Hello, {s1}"; //StringFormat string s1 = "World"; string s2 = String.Format("Hello, {0}", s1);
- 转义花括号
string s = "Hello"; WriteLine($"{{s}} displays the value {s}"); // {s} displays the value Hello
- 大写字母
D
表示长日期格式字符串,小写字母d
表示短日期字符串var day = new DateTime(2025, 2, 14); WriteLine($"{day:D}"); WriteLine($"{day:d}"); WriteLine($"{day:dd-MMM-yyyy}"); // Friday, February 14, 2025 // 2/14/2025 // 14-Feb-2025
n
表示数字格式,e
表示指数格式,x
表示16进制,c
表示货币WriteLine($"{ i:n} , {i:e}, {i:x}, {i:c}");
#
格式说明符是一个数字站位符,如果数字可用,就显示数字;如果数字不可用,不显示数字。0
格式说明符是一个零占位符,显示相应数字,如果数字不存在,就显示零。double d = 3.1415926; WriteLine($"{d:###.###}"); WriteLine($"{d:000.000}");
- 以下是相同的
- 正则表达式
- 是一种专门用于字符串处理的语言。
- C# 名称空间:
System.Text.RegularExpression.RegEx
(更简单是调用其静态方法Regex()
) pattern
字符串前用@
。const string pattern = @"\bn";
集合
- 列表
- 泛型类
List<T>
实现了IList
,ICollection
,IEnumerable
,IList<T>
,ICollection<T>
,IEnumerable<T>
。 - 如果使用默认构造函数创建一个空列表,那它的初始容量是4,添加到第5个是,会扩容到8。以后每次扩容都是原来的2倍。
- 以下是设置集合的容量
List<int> intList = new List<int>(10); intList.Capacity = 20;
intList.TrimExcess()
是去除不需要的容量,如果容量超过90%,就什么都不做。Predicate<T>
是一个委托,返回一个布尔值。返回true
表示有匹配的元素,而且找到了;false
表示没有找到,继续搜索。public int FindIndex(Predicate<T> math); // Predicate<T>等同于下 public delegate bool Predicate<T>(T obj);
Find()
,FindIndex()
,FindAll()
需要使用Predicate<T>
类型参数,一般是用lambda。- 排序。和数组一样可以是实现
IComparable
和IComparer
。此外还可以是用Comparison<T>
委托。Reverse()
同理可用。//Comparison<T>委托定义 public void List<T>.Sort(Comparison<T>); //调用 racers.Sort((r1,r2) => r2.Wins.CompareTo(r1.Wins));
- 泛型类
- 队列
Queue<T>
- 先进先出。
- 没有
Add()
,Remove()
方法。只有用Enqueue()
和Dequeue()
来放在队尾和头部获取。 Peek()
。从队列头部读取元素,但不删除。
- 栈
Stack<T>
- 后进先出。
- 和
Queue<T>
类似,没有Add()
,Remove()
方法。只有用Push()
和Pop()
来放在z栈顶和从栈顶获取。 Contains()
,确定某个元素是否在栈中。
- 链表
LinkedList<T>
LinkedList<T>
包含了LinkedListNode<T>
类型的元素。LinkedListNode
包含了上一个元素,下一个元素的信息。- 方法有
AddAfter()
,AddBefore()
,AddFirst()
,AddLast()
,Remove
,RemoveFirst()
,RemoveLast()
和Find()
。
- 有序列表
SortedList<TKey, TValue>
- 值和键都可以是任意类型。
- 需要传递实现了
IComparer<TKey>
接口的对象,该接口用于列表中的元素排序。 - 一个键只能对应一个值。
- 使用
foreach
语句时,用KeyValuePair<TKey, TValue>
类型来接收返回的元素,如下。foreach(KeyValuePair<string, string>) book in books) { WriteLine($"{book.Key}, {book.Value}"); }
- 字典
Dictionary<TKey, TValue>
- 键必须是有重写
GetHashCode()
方法。用string
作为键非常方便和适用。
- 键必须是有重写
Lookup
类- 一个键对应多个值(一个集合)
- 不能像一般的字典一样
new
创建,而必须要调用ToLookup()
方法。var lookupRacers = racers.ToLookup(r => r.Country); foreach(Racer r in lookupRacers["Australia"]) { WriteLine(r); }
- 有序字典
SortedDictionary<TKey, TValue>
- 和有序列表
SortedList<TKey, TValue>
类似,键必须实现IComparable<TKey>
接口。 SortedList<TKey, TValue>
实现为一个基于数组的列表,而SortedDictionary<TKey, TValue>
是基于散列字典的,有以下不同特征SortedList<TKey, TValue>
使用的内存比SortedDictionary<TKey, TValue>
少SortedDictionary<TKey, TValue>
的元素插入和删除比较快- 已排好序的数据填充集合时,若不修改容量,
SortedList<TKey, TValue>
比较快
- 和有序列表
- 集
HashSet<T>
SortedSet<T>
HashSet<T>
包含不重复元素的无序列表,SortedSet<T>
包含不重复元素的有序列表
- 集合性能
特殊的集合
- 不变的集合
- 命名空间
System.Collections.Immutable
- 多线程中使用,是不能改变的集合
- 非泛型类
ImmutableArray
的Create
静态方法会返回泛型ImmutableArray
结构。 ImmutableArray<T>
也有Add()
,Remove()
,Replace()
方法,但是它不是修改原有的结合,而是新建一个新的,然后返回。//create ImmutableArray<string> a1 = ImmutableArray.Create<string>(); //add ImmutableArray<string> a2 = a1.Add("Leo").Add("HeiHeiHei");
- 普通集合
List<T>
可以用ToImmutableList()
扩展方法创建一个不变的集合。ImmutableList<Account> immutableAccounts = accounts.ToImmutableList();
- 可以用
ToBuilder()
创建一个可以改变的集合。反之,Builder
的ToImmutable()
方法,是创建一个新的不可变集合。ImmutableList<Accounts>.Builder builder = immutableAccounts.ToBuilder(); for(int i = 0; i < builder.Count; i++) { Account a = builder[i]; if(a.Amount < 0) { builder.Remove(a); } } ImmutableList<Account> overdrawnAccounts = builder.ToImmutable(); overdrawnAccounts.ForEach(a => WriteLine($"{a.Name} {a.Amount}"));
- 所有正常的集合类型都有不可变的结合,都是在前面添加
Immutable
,操作方法都是一样的。
- 命名空间
LINQ
- 延迟执行。因为使用
yield return
语句返回,所以在定义查询的时候,是不会执行的,而是在foreach
语句中执行。或者在定义的时候用ToList()
或者ToArray()
马上返回遍历结果。但是结果可能有些许不一样。参照实例代码。 - 筛选。
- 索引筛选。
Where()
方法的重载中,可以传递第二个参数--索引。如下://形式以A开头,索引为偶数的赛车手 var racers = Formular1.GetChampions().Where((r, index) => r.LastName.StartWith("A") && index %2 != 0);
- 类型筛选。使用
OfType()
扩展方法,把类型传递到泛型参数。object[] data = {"one", 2, 3, "four", "five", 6}; var query = data.ofType<string>();
- 索引筛选。
- 排序。以下同义。
// 1. 查询结果 var racers = (from r in Formular1.GetChampions() order by r.Country, r.LastName, r.FirstName select r).Take(10); // 2. 扩展方法 var racers = Formular1.GetChampions().OrderBy(r => r.Country) .ThenBy(r => r.LastName) .ThenBy(r => r.FirstName) .Take(10);
- 分组。
var countries = Formular1.GetChampions().GroupBy(r => r.Country) .OrderByDescending(g => g.Count()) .TheBy(g => g.Key) .Where(g => g.Count() >= 2) .Select(g => new { Country = g.Key, Count = g.Count() });
- 分区。
Take()
和Skip()
。用于分页。int pageSize = 5; var racers = Formular1.GetChampions().OrderBy(r => r.Country) .ThenBy(r => r.LastName) .ThenBy(r => r.FirstName) .Skip(pageNum * pageSize) .Take(pageSize);
- LINQ中的变量使用。使用
let
来声明变量。var query = from r in Formular1.GetChampions() let numberYears = r.Years.Count() where numberYears >= 3 orderby numberYears descending, r.LastName select new { Name = r.FirstName + " " + r.LastName, TimesChampion = numberYears };
Range()
,Empty()
和Repeat
。Empty()
是用于需要一个集合的参数,参数传值是空。var values = Enumerable.Range(1, 20).Select(n => n*3);
错误和异常
-
异常层次
-
System.Exception
属性。Data
,Message
,HelpLink
和InnerException
属性必须在抛出前填充。属性 说明 Data 这个属性可以给异常添加键/值语句,以提供关于异常的额外信息 HelpLink 连接到一个帮助文件,以提供关于该异常的更多信息 InnerException 如果异常是在 catch
块中跑出的,它就会包含把代码发送到catch
块中的异常对象Message 描述错误情况的文本 Source 导致异常的应用程序名或对象名 StackTrace 栈上方法调用的详细信息,它有助于跟踪跑出异常的方法 -
异常过滤器。在
catch
条件后添加when()
条件。try { string input = string.Empty; WriteLine("pls input the error code."); input = ReadLine(); if (string.IsNullOrEmpty(input)) break; int errorcode = Convert.ToInt32(input); throw new MyCustomException($"{input} MyCustomException") { ErrorCode = errorcode }; } catch (MyCustomException ex) when (ex.ErrorCode == 405) { WriteLine($"MyCustomException is thrown, {ex.Message}"); } catch (Exception ex) { WriteLine($"An exception was thrown, {ex.Message}"); } finally { WriteLine("Thank you! \n"); }
-
使用
throw
但不传递异常对象,会抛出当前异常。
异步编程
-
.Net 2.0以前使用委托实现异步。
-
基于事件的异步编程。
Async
方法调用完了后,会直接调用Completed
事件。但是Completed
委托必须实现在前。
-
Task.WhenAll
是同时执行两个任务。try { Task t1 = ThrowAfter(200, "first"); Task t2 = ThrowAfter(500, "second"); await Task.WhenAll(t1, t2); } catch (Exception e) { WriteLine("handled" + e.Message); }
-
lock
关键字。lock(this)
, 静态锁和非静态锁的区别(参照MultiThread例子)。lock(this)
:是锁住当前整个实例,所有方法等待。lock(object)
非静态锁:是锁住当前实例当前方法。lock(staticObkect)
静态锁:是锁住所有实例的当前方法。
Reference:
-
更多例子参照实例代码。