第4章 高级特性(补充)
4.5 try语句和异常#
4.5.1 try、catch和finally语句#
class Test{ static int Calc(int x) => return 10/x; static void Main(){ try{ int y=Calc(0); Console.WriteLine(y); }catch(DivideByZeroException ex){ Console.WriteLine("x 不能为0。"); }finally{ Console.WriteLine("程序完成!"); } } }
4.5.2 Using 语句块:提供了自动回收#
using (xmlReader = XmlReader.Create(xmlPath)) while (xmlReader.Read()) { if (xmlReader.Name == labName) { string name = xmlReader.Name.ToString(); string value = xmlReader.GetAttribute(attName1); string tip = xmlReader.GetAttribute(attName2); Console.WriteLine($"{name}: {attName1}:{value} , {attName2}:{tip}"); } } // 这里读取了XML文件后,自动关闭文件
4.5.3 throw 抛出异常#
static void Display(string name){ if(name==null){ throw new ArgumentNullException(nameof(name)); } Console.WriteLine(name); }
4.6 可枚举类型和迭代器#
什么是枚举器?#
枚举器是一个只读并且只能在值序列上前移的游标。
- 实现了System.Collections.IEnumerator 或者System.Collections.Generic.IEnumerator<T>
- 任何具有MoveNext方法和Current属性的对象都会被当作枚举器。
集合的初始化器有什么用?#
简单的一个步骤就可以实例化并填充可枚举对象。
(该可枚举对象实现了System.Collections.IEnumerator接口,并且有Add()方法)
using System.Collections.Generic; List<int> list=new List<int> {1,2,3}; var dict=new Dictionary<int,string>(){ {5,"five"}, {10,"ten"}, } var dict2=new Dictionary<int,string>(){ [3]="three", [6]="six" }
什么是迭代器?#
迭代器是包含一个或者多个yield语句的方法、属性或者索引器。
foreach语句是枚举器的消费者,迭代器是枚举器的生产者。
迭代器语义#
class Test{ static void Main(){ foreach(string str in Foo()){ Console.Write(str+" ");//One Two } } static IEnumerable<string> Foo(){ yield return "One"; yield return "Two"; yield break;// 不再返回更多的元素,提前退出 yield return "Three"; } }
yield return 不能出现在带catch子句的try语句块,也不能出现在catch或者finally语句块中。
迭代器可以高度组合。
static IEnumerable<int> Fibs(int fibCount){ for(int i=0,prevFib=1,curFib=1;i<fibCount;i++){ yield return preFib; int newFib=preFib+CurFib; preFib=curFib; curFib=newFib; } } static IEnumerable<int> EvenNumbersOnly(IEnumerable<int> sequence){ foreach(int x in sequence){ if((x%2)==0){ yield return x; } } }
4.7 扩展方法#
什么是扩展方法?#
扩展方法就是在现有类的基础上扩展新的方法并且不用修改原始类型的定义。
(1)扩展方法是静态类的静态方法
(2)该方法第一个参数使用this修饰(这个参数就是要扩展的类型)
public static class StringHelper{ public static bool IsCapitalized(this string s){ if(string.IsNullOrEmpty(s)) return false; return char.IsUpper(s[0]); } } // 调用 Console.WriteLine("Alice".IsCapitalized()); Console.WriteLine(StringHelper.IsCapitalized("Alice"));
接口也可以扩展。
public static T First<T> (this IEnumerable<T> seq){ foreach(T element in seq){ return element; } throw new InvalidOperationException("No elements"); } // 调用 Console.WriteLine("Alice".First());// A
扩展方法链#
public static class StringHelper{ public static bool ExetenMethod1(this string s){} public static bool ExetenMethod2(this string s){} } // 调用 string x="abcde".ExetenMethod1().ExetenMethod2(); string y=StringHelper.ExetenMethod1(StringHelper.ExetenMethod2("abcde"));
使用扩展方法的注意事项#
(1)只有包含扩展方法的类在作用域是,才能访问扩展方法。 (导入命名空间)
(2)实例方法和扩展方法的名字相同时,实例方法的优先级高些。这样,只能通过调用静态方法的形式调用扩展方法。
(3)如果2个静态类中有相同签名的扩展方法,那么使用普通静态方法的调用形式来区分。
4.8 匿名类型#
匿名类型是一个由编译器临时创建来存储一组值得简单类。
var dude=new {Name="Alice",Age=20}; var dudes=new[] { new {Name="Alice",Age=20}, new {Name="Bob",Age=23} }
4.9 元组#
和匿名类型一样,元组也是存储一组值的便捷方式。
元组的主要目的是不使用out参数而从方法中返回多个值。(这是匿名类做不到的)
元组是值类型,并且是可读可写的。
var bob=("Bob",Age:23); Console.WriteLine(bob.Item1); // Bob Console.WriteLine(bob.Item2); // 23 Console.WriteLine(bob.Age); // 23 bob.Item2=20; Console.WriteLine(bob.Item2); // 20
static (string,int) GetPerson()=>("Bob",20); static void Main(){ (string,int) person=GetPerson(); // 不能使用var Console.WriteLine(person.Item1); // Bob Console.WriteLine(preson.Item2); // 20 }
(string Name,int Age) bob=("Bob",20); (string BB,int CC) tom=bob;// 元组按照对应元素的类型相同,可以兼容 (string,int) bob=("Bob",20);
string Name=bob.Item1; int Age=bob.Item2; var t1=("One",1); var t2=("One",1); Console.WriteLine(t1.Equals(t2));// true
4.10 特性#
什么是特性?#
特性是一种将自定义信息添加到代码元素(程序集、类型、成员、返回值、参数和泛型类型参数)的扩展机制。
特性的常见实例就是 序列号/反序列化过程。——就是将任意对象转换为一个特定格式或从特定格式生成对象的过程。
特性类#
特性是通过直接或间接继承抽象类System.Attribute的方式定义的。
使用方式:在需要添加特性的代码元素前,用 [指定特性的名称]
编译器可以识别该特性,并在编译时对引用该特性标记的类型或成员的行为产生警告。按照惯例,所有特性类型都以 Attribute结尾。
特性参数#
特性可以包含参数,一种是位置参数,一种是命名参数。
——位置参数 对应特性类型的公有构造器的参数
——命名参数 对应该特性类型的公有字段或者公有属性
示例:
XmlElementAttribute特性告诉XML序列化器一个对象如何转化为XML格式以及如何接受特性参数。
下面的特性将CustomerEntity类映射到一个名为Customer的XML元素上,且这个XML元素位于http://oreilly.com命名空间
[XmlElement ("Customer",Namespace="http://oreilly.com")] public class CustomerEntity{ //... }
当指定一个特性时,必须包含对应特性构造器中的位置参数,而命名参数是可选的。
指定多个特性#
一个代码元素可以指定多个特性。
特性可以列在同一对方括号中(用逗号分隔),也可以分隔在多个方括号之中
调用者信息特性#
在可选参数上添加三种调用者信息中的一种,它们可以让编译器从调用者源代码获取参数的默认值。
- [CallerMemberName]:表示调用者的成员名称
- [CallerFilePath]:表示调用者的源代码文件的路径
- [CallerLineNumber]:表示调用者源代码文件的行号
4.11 动态绑定#
什么是动态绑定?#
动态绑定将绑定(即解析类型、成员和操作的过程)从编译时延迟到运行时。 dynamic
静态绑定#
是在编译表达式时,将一个名称映射到一个具体的函数。
自定义绑定#
通过动态对象实现IDynamicMetaObjectProvider(IDMOP)接口实现。
RuntimeBinderException#
dynamic d=5; d.Hello(); // throw RuntimeBinderException //抛出异常原因:int类型没有Hello方法
动态转换#
动态类型可以隐式地从其他类型转换或转换为其他类型。小⇒大
int i=7; dynamic d=i; long j=d;
var和dynamic#
- var是由编译器确定类型
- dynamic使用运行时确定类型
无法动态调用的函数#
(1)扩展方法
(2)必须将类型转化为接口才能调用的接口成员
(3)基类中被子类隐藏的成员
4.12 运算符重载#
运算符函数#
运算符函数就是重载运算符的函数。有以下规则:
- 函数名为 operator 跟上 运算符符号
- 函数必须是 public static
- 函数的参数就是操作数
- 函数的返回类型表示表达式的结果
- 操作数中至少有一个类型和声明运算符的类型(类、结构体等)的类型名是一致的
public struct Note{ int value; public Note(int x){ value=x; } public static Note operator +(Note n,int x){ return new Note(n.value+x); } } Note A =new Note(2); Note B=A+2; // Note B+=2;
重载等号和比较运算符#
总结:
重载隐式和显示转换#
public struct Note{ int value; public Note(int x){ value=x; } public static Note operator +(Note n,int x){ return new Note(n.value+x); } // 重载隐式转换 public static implicit operator double(Note x){ //... return .... } // 重载显示转换 public static explicit operator Note(double x){ //... return .... } } Note n=Note(44.4); double x=n;
重载true和false#
true和false运算符只会在那些本身有布尔语义但无法转换为bool的类型中重载。
实例:
SqlBoolean a=SqlBoolean.Null; if(a) Console.WriteLine("True"); else if(!a) Console.WriteLine("False"); else Console.WriteLine("Null"); //输出为: Null
4.13 预处理指令#
什么是预处理指令?#
预处理指令向编译器提供关于一段代码的附加信息。
最常见的预处理指令是条件指令,提供了一种控制某一块代码是否编译的方法。
#define DEBUG class MyClass{ int x; void Foo(){ #if DEBUG Console.WriteLine("Testing:x={0}",x); #endif } } // 如果移除了 DEBUG符号,其中的语句就不会编译。
#if 和 #elif 指令可以使用 || && !对多个符号进行或、与、非得逻辑运算。
#error 和 #warning 可以避免条件指令的滥用。可以在出现不符合要求的编译符号是产生一条错误或警告信息。
Consitional 特性#
使用Consitional 特性修饰的特性只有在给定的预处理符号时才编译。
// file1.cs #define DEBUG using System; using System.Diagnostics; [Consitional("DEBUG")] public class TestAttribute:Attribute{ } // file2.cs #define DEBUG [Test] class Foo{ [Test] string s; } // 当DEBUG符号在file2.cs 范围内出现时,编译器才会将[Test]特性加入进来。
Pragma 警告#
使用#pragma warning指令有选择地避免一些警告。
public class Foo{ static void Main(){ #pragma warning disable 414 static string Message="Hello"; #pragma warning restore 414 } }
可以使用/warnaserror 开关让编译器将所有警告都显示为错误。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!