读书笔记-C#8.0本质论-01
1. IL代码入门
1.1 示例1
namespace ConsoleApp1;
internal static class Program
{
internal static void Main(string[] args)
{
string hello = "hello World!";
Console.WriteLine(hello);
}
}
// .class 表示的是Program是一个类
// private 表示该类是私有的
// 虽然Program被声明为internal但CLR仍然认为包含程序入口点的类是私有的
// abstract 指示该类只能用来作为其他类的基类。这意味着不能直接创建该类的实例。
// sealed 指定该类不能被继承(用作基类)
// abstract和sealed连用,说明Program类既不能实例化又不能被继承,只能调用其中的静态成员【这就是static class在CLR层面上的实现】
// auto 表示程序加载时的内存布局由CLR决定,而不是由程序本身控制。
// 该设置可以在C#中用StructLayout这个特性控制,可选值有LayoutKind.Auto和LayoutKind.Sequential,详见《CLR via C#》
// ansi 表示类的编码为ansi编码
// beforefieldinit 表示CLR可以在第一次访问静态字段之前的任何时刻执行该类型的构造函数
// 该属性默认启用,使用该实现可提高性能
// extends 表示继承 ConsoleApp1.Program继承自System.Object
// [System.Runtime] 表示引用了System.Runtime程序集
.class private abstract sealed auto ansi beforefieldinit
ConsoleApp1.Program
extends [System.Runtime]System.Object
{
// .method 表示Main方法是一个方法
// assembly 限定了方法的可访问级别,只有在当前程序集中才能访问
// hidebysig 表示如果当前类作为父类,用该指令标记的方法将不会被子类继承
// cil managed 表示该方法体中的代码是托管代码,该代码运行在CLR运行库中
// .enterypoint 表示该方法时程序入口点
// .ctor 表示构造函数
// .maxstack 表明执行构造函数时,"评估栈"可容纳数据项的最大个数。
// 评估堆栈是保存方法中所需变量的值的一个内存区域,该区域在方法执行结束时会被清空,或者存储一个返回值,可以理解为内存缓冲区
// .locals init([0] string hello) 表示定义了一个名为hello的string类型的局部变量
// ldstr 将字符串“hello World!”压入"评估栈"
// stloc.0 从"评估栈"的顶部弹出当前值并存储到索引为0(也就是hello)的局部变量列表中,在此示例中,就是把字符串"Hello World"赋值给变量hello
// ldloc.0 将索引0处的局部变量加载到"评估栈"上,也就是将局部变量hello加载到"评估栈"上
// call 调用静态方法,这里是调用程序集System.Console中的System.Console::WriteLine(string)静态方法,该方法接收一个string类型的参数
.method assembly hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.maxstack 1
.locals init (
[0] string hello
)
// [6 5 - 6 6]
IL_0000: nop
// [7 9 - 7 39]
IL_0001: ldstr "hello World!"
IL_0006: stloc.0 // hello
// [8 9 - 8 34]
IL_0007: ldloc.0 // hello
IL_0008: call void [System.Console]System.Console::WriteLine(string)
IL_000d: nop
// [9 5 - 9 6]
IL_000e: ret
} // end of method Program::Main
} // end of class ConsoleApp1.Program
1.2 示例2
namespace ConsoleApp1;
internal static class Program
{
internal static void Main(string[] args)
{
var i = 2;
var j = 3;
int result = i + j;
Console.WriteLine(result);
}
}
.class private abstract sealed auto ansi beforefieldinit
ConsoleApp1.Program
extends [System.Runtime]System.Object
{
.method assembly hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
// "评估栈"可容纳数据项的最大个数
.maxstack 2
// 初始化三个int32局部变量
.locals init (
[0] int32 i,
[1] int32 j,
[2] int32 result
)
// [6 5 - 6 6]
IL_0000: nop
// [7 9 - 7 19]
// 将2以四字节整数(int32)的形式推送到“评估栈”上
IL_0001: ldc.i4.2
// 把"评估栈"栈顶的值弹出,并赋值给第0个局部变量,也就是令i=2
IL_0002: stloc.0 // i
// [8 9 - 8 19]
// 将3以四字节整数(int32)的形式推送到“评估栈”上
IL_0003: ldc.i4.3
// 把"评估栈"栈顶的值弹出,并赋值给第1个局部变量,也就是令j=2
IL_0004: stloc.1 // j
// [9 9 - 9 28]
// 将第0个变量压入"评估栈"
IL_0005: ldloc.0 // i
// 将第1个变量压入"评估栈"
IL_0006: ldloc.1 // j
// "评估栈"出栈并相加,检查结果是否溢出,将计算结果压回"评估栈"
IL_0007: add.ovf
// 将“评估栈”栈顶的值弹出,并赋给第二个局部变量result
IL_0008: stloc.2 // result
// [10 9 - 10 35]
// 将索引2处的计算变量压入"评估栈"
IL_0009: ldloc.2 // result
// 调用程序集[System.Console]中的静态方法WriteLine
IL_000a: call void [System.Console]System.Console::WriteLine(int32)
IL_000f: nop
// [11 5 - 11 6]
IL_0010: ret
} // end of method Program::Main
} // end of class ConsoleApp1.Program
1.3 示例3
namespace ConsoleApp1;
internal static class Program
{
internal static void Main(string[] args)
{
int i = 2;
if (i > 0)
{
Console.WriteLine("i为正数");
}
else
{
Console.WriteLine("i为0或负数");
}
}
}
.class private abstract sealed auto ansi beforefieldinit
ConsoleApp1.Program
extends [System.Runtime]System.Object
{
.method assembly hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.maxstack 2
// 初始化两个局部变量
.locals init (
[0] int32 i,
[1] bool V_1
)
// [6 5 - 6 6]
IL_0000: nop
// [7 9 - 7 19]
// 将int32类型的整数压入“评估栈”
IL_0001: ldc.i4.2
// 把“评估栈”的值弹出,赋值给局部变量i
IL_0002: stloc.0 // i
// [8 9 - 8 19]
// 将索引为0的局部变量i压入“评估栈”
IL_0003: ldloc.0 // i
// 将int32类型的整数压入“评估栈”
IL_0004: ldc.i4.0
// 执行整数比较指令,“评估栈”出栈两个元素,判断先出栈元素i是否大于后出栈元素0,并将运算结果压回评估栈
IL_0005: cgt
// 评估栈出栈将结果赋值给索引为1的局部变量V_1
IL_0007: stloc.1 // V_1
// 将V_1压入评估栈
IL_0008: ldloc.1 // V_1
// 如果V_1(评估栈栈顶元素)为false、空引用或0则跳转到IL_001a继续执行
IL_0009: brfalse.s IL_001a
// [9 9 - 9 10]
IL_000b: nop
// [10 13 - 10 39]
// 将字符串压入评估栈
IL_000c: ldstr "i为正数"
IL_0011: call void [System.Console]System.Console::WriteLine(string)
IL_0016: nop
// [11 9 - 11 10]
IL_0017: nop
// 无条件跳转指令
IL_0018: br.s IL_0027
// [13 9 - 13 10]
IL_001a: nop
// [14 13 - 14 41]
IL_001b: ldstr "i为0或负数"
IL_0020: call void [System.Console]System.Console::WriteLine(string)
IL_0025: nop
// [15 9 - 15 10]
IL_0026: nop
// [16 5 - 16 6]
IL_0027: ret
} // end of method Program::Main
} // end of class ConsoleApp1.Program
1.4 示例4
namespace ConsoleApp1;
internal static class Program
{
internal static void Main(string[] args)
{
Console.WriteLine(Person.num);
var person = new Person();
Console.WriteLine(person.age);
}
}
internal class Person
{
public const int num = 10;
public int age = 10;
}
.class private abstract sealed auto ansi beforefieldinit
ConsoleApp1.Program
extends [System.Runtime]System.Object
{
.method assembly hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.maxstack 1
.locals init (
[0] class ConsoleApp1.Person person
)
// [6 5 - 6 6]
IL_0000: nop
// [7 9 - 7 39]
// const值被CLR视为立即数
IL_0001: ldc.i4.s 10 // 0x0a
IL_0003: call void [System.Console]System.Console::WriteLine(int32)
IL_0008: nop
// [8 9 - 8 35]
// 实例化Person
IL_0009: newobj instance void ConsoleApp1.Person::.ctor()
// 将Person的实例给局部变量person
IL_000e: stloc.0 // person
// [9 9 - 9 39]
// 将person压入“评估栈”
IL_000f: ldloc.0 // person
// 从person中查找age字段的值(不会再将这个值复制一份了)
IL_0010: ldfld int32 ConsoleApp1.Person::age
IL_0015: call void [System.Console]System.Console::WriteLine(int32)
IL_001a: nop
// [10 5 - 10 6]
IL_001b: ret
} // end of method Program::Main
} // end of class ConsoleApp1.Program
.class private auto ansi beforefieldinit
ConsoleApp1.Person
extends [System.Runtime]System.Object
{
// 创建一个静态字段 该字段存储int32类型的字面值10
.field public static literal int32 num = int32(10) // 0x0000000a
// 创建一个public字段age,该字段存储int32类型的变量
.field public int32 age
// specialname表示此项的名称对CLI以外的工具具有特殊意义
// rtspecialname表示此项的名称对CLI具有特殊意义。
// 标记为rtspecialname的任何项目也应标记为specialname
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
// [16 5 - 16 25]
// 将当前类加载到评估栈上
IL_0000: ldarg.0 // this
// 将提供的int8类型的值当作int32类型加载到评估栈上
IL_0001: ldc.i4.s 10 // 0x0a
// 当前类和立即数均出栈,并用出栈的立即数替换Person中的age字段中存储的值
IL_0003: stfld int32 ConsoleApp1.Person::age
// 再将修改后的当前类加载回评估栈
IL_0008: ldarg.0 // this
// 调用默认构造函数
IL_0009: call instance void [System.Runtime]System.Object::.ctor()
IL_000e: nop
IL_000f: ret
} // end of method Person::.ctor
} // end of class ConsoleApp1.Person
1.5 示例5
namespace ConsoleApp1;
internal static class Program
{
private static void Main(string[] args)
{
var str = "Helius";
var num = 27;
Console.WriteLine(num.ToString());
Console.WriteLine(num + str);
}
}
.class private abstract sealed auto ansi beforefieldinit
ConsoleApp1.Program
extends [System.Runtime]System.Object
{
.method private hidebysig static void
Main(
string[] args
) cil managed
{
.entrypoint
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.maxstack 2
.locals init (
[0] string str,
[1] int32 num
)
// [6 5 - 6 6]
IL_0000: nop
// [7 9 - 7 28]
IL_0001: ldstr "Helius"
IL_0006: stloc.0 // str
// [8 9 - 8 22]
IL_0007: ldc.i4.s 27 // 0x1b
IL_0009: stloc.1 // num
// [9 9 - 9 43]
// 取出局部变量表中num的值并压入评估栈
IL_000a: ldloca.s num
// int32类型的num出栈,调用ToString方法后得到的返回值再压入评估栈
IL_000c: call instance string [System.Runtime]System.Int32::ToString()
// 出栈,调用WriteLine方法打印字符串
IL_0011: call void [System.Console]System.Console::WriteLine(string)
IL_0016: nop
// [10 9 - 10 38]
// 取出局部变量表中num的值并压入评估栈
IL_0017: ldloca.s num
// int32类型的num出栈,调用ToString方法后得到的返回值再压入评估栈
IL_0019: call instance string [System.Runtime]System.Int32::ToString()
// 取出局部变量表中第0个位置的元素值,并压入评估栈
IL_001e: ldloc.0 // str
// 两个string出栈,调用Concat方法合成一个string后再压入评估栈
IL_001f: call string [System.Runtime]System.String::Concat(string, string)
// 出栈,调用WriteLine方法打印字符串
IL_0024: call void [System.Console]System.Console::WriteLine(string)
IL_0029: nop
// [11 5 - 11 6]
IL_002a: ret
} // end of method Program::Main
} // end of class ConsoleApp1.Program
2. CSharp对字符串的处理
2.1 字符串不能原地修改
所有string类型的数据都是不可变的(不可修改),即不能修改变量最初引用的数据,只能重新赋值,让它指向内存中的新位置。
public static void Test2()
{
var newString = "new String";
var fixedString = newString + "fixed";
unsafe
{
fixed (char* p = newString)
{
// 64位操作系统,用位数匹配的常整型进行显示类型转换
Console.WriteLine("0x{0:x}", (long)p); // 0x2450016e204
}
fixed (char* p = fixedString)
{
Console.WriteLine("0x{0:x}", (long)p); // 0x2450016e27c
}
}
}
3. 浮点数造成的非预期不相等
如何避免浮点数造成的非预期不相等?
避免将二进制浮点类型用于相等性条件式,要么 **判断两个浮点数之差是否在容错范围之内** ,要么 **使用decimal类型**
public static void Test4()
{
decimal decimalNumber = 4.2M;
double doubleNumber1 = 0.1F * 42F;
double doubleNumber2 = 0.1D * 42D;
float floatNumber = 0.1F * 42F;
/* 输出结果全为false */
Console.WriteLine(decimalNumber == (decimal)doubleNumber1);
Console.WriteLine((float)decimalNumber == floatNumber);
Console.WriteLine(doubleNumber1 == doubleNumber2);
Console.WriteLine(doubleNumber2 == (double)floatNumber);
}
4. CSharp允许多个Main方法
- 在项目的配置文件(xxx.csproj)里使用StartupObject属性来指定当前项目的入口点(包含静态Main方法的类)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<StartupObject>Test.MultipleMainMethod</StartupObject>
</PropertyGroup>
</Project>
- 编写示例代码
namespace Test;
internal static class MultipleMainMethod
{
public static void Main()
{
Console.WriteLine("MultipleMainMethod中的Main方法");
MultipleMainMethod2.Main();
MultipleMainMethod3.Main();
}
}
internal static class MultipleMainMethod2
{
public static void Main()
{
Console.WriteLine("MultipleMainMethod2中的Main方法");
}
}
internal static class MultipleMainMethod3
{
public static void Main()
{
Console.WriteLine("MultipleMainMethod3中的Main方法");
}
}
5. 按值传递与按引用传递
string类型的特殊性
string类型按照 **引用传递** (形参和实参的地址相同),但是由于字符串不能修改只能重建, **从外在表现来看** string类型的行为 **与按值传递的类型一致** 。
示例如下:
public static void Test5()
{
// 字符串按照引用传递(形参和实参的地址相同),但是由于字符串不能修改只能重建,从外在表现来看和按值传递一样
string string1 = "string1";
Console.WriteLine(string1);
unsafe
{
fixed (char* p = string1)
{
Console.WriteLine($"string1的地址为: 0x{(long)p,0:x}");
}
}
GetStringFromTest5(string1);
Console.WriteLine(string1);
// int是按值传递(形参和实参的地址不同)
int int1;
unsafe
{
int* p = &int1;
Console.WriteLine($"int1的地址为: 0x{(long)p,0:x}");
}
GetIntegerFromTest5(int1);
// intArray是按引用传递的(形参和实参的地址相同),在下层函数中对数组中元素的修改可以作用于上层函数
var intArray = new int[] { 1, 3, 5, 7, 9 };
unsafe
{
fixed (int* p = intArray)
{
Console.WriteLine($"intArray的地址为: 0x{(long)p,0:x}");
}
}
PrintArray(intArray);
GetIntegerArrayFromTest5(intArray);
PrintArray(intArray);
}
public static void GetStringFromTest5(string string2)
{
unsafe
{
fixed (char* p = string2)
{
Console.WriteLine($"string2的地址为: 0x{(long)p,0:x}");
}
}
string2 = "string2";
}
public static void GetIntegerFromTest5(int int2)
{
unsafe
{
int* p = &int2;
Console.WriteLine($"int2的地址为: 0x{(long)p,0:x}");
}
}
public static void GetIntegerArrayFromTest5(int[] intArray2)
{
unsafe
{
fixed (int* p = intArray2)
{
Console.WriteLine($"intArray2的地址为: 0x{(long)p,0:x}");
}
}
intArray2[0] = 100;
}
public static void PrintArray(System.Collections.IEnumerable intArray)
{
var stringBuilder = new System.Text.StringBuilder();
foreach (var item in intArray)
{
stringBuilder.Append(item);
stringBuilder.Append(", ");
}
Console.WriteLine(stringBuilder);
}
6. 常见异常类型
- System.Exception 这是最“基本”的异常,其他的所有异常类型都从它派生
- System.ArgumentException 传给方法的参数无效
- System.ArgumentNullException 不该为null的参数为null
- System.AplicationException 应用程序异常
- System.FormatException 实参类型不符合形参规范
- System.IndexOutOfRangeException 数组或其他集合访问越界
- System.InvalidCastException 无效的类型转换
- System.InvalidOperationException 发生非预期的情况,应用程序不再处于有效工作状态
- System.InvalidOperationException 虽然找到了对应的方法签名,但该方法尚未完全实现
- System.NullReferenceException 引用为空,没有指向一个实例
- System.ArithmeticException 发生被零除以外的无效数学运算
- System.ArrayTypeMismatchException 试图将类型有误的元素存储到数组中
- System.StackOverflowException 发生非预期的深递归导致栈溢出
7. nameof 表达式
7.1 nameof 表达式概述
nameof表达式可生成变量、类型或成员的名称作为字符串常量:
Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic
Console.WriteLine(nameof(List<int>)); // output: List
Console.WriteLine(nameof(List<int>.Count)); // output: Count
Console.WriteLine(nameof(List<int>.Add)); // output: Add
var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(nameof(numbers)); // output: numbers
Console.WriteLine(nameof(numbers.Count)); // output: Count
Console.WriteLine(nameof(numbers.Add)); // output: Add
如前面的示例所示,对于类型和命名空间,生成的名称不是完全限定的名称。
7.2 nameof 在属性验证时的用处
在属性验证时如果判断出新赋值无效,就需要抛出ArgumentException()或ArgumentNullException()类型的异常。两个异常都获取string类型的实参paramName来标识无效的参数名称。nameof操作符获取一个标识符作为参数,返回该名称的字符串形式。若以后标识符名称发生改变,代码重构工具能自动修改nameof的实参,这样就保证了异常字符串中标识的无效参数名称始终是正确的。
internal class PropertyTest
{
private string? _lastName;
public string? LastName
{
get => _lastName;
set
{
// C# 7.0 增强了is操作符,现在is可以用于是否为null的判断了
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
else
{
// 删除字符串前后的空格
value = value.Trim();
if (value == "")
{
throw new ArgumentException("lastName cannot be blank.", nameof(value));
}
else
{
_lastName = value;
}
}
}
}
}
8. 可空特性(nullable attribute)
System.Diagnostics.CodeAnalysis中定义了7中不同的可空特性(attribute),有些属于前置条件有些属于后置条件:
- 前置条件
- AllowNull 不可空输入参数可能为null
- DisallowNull 可空输入参数永远不为null
- 后置条件
- MaybeNull 不可空返回值可能为null
- NotNull 可空返回值永远不为null
- MaybeNullWhen 当方法返回指定bool值时,一个不可空输入参数可能为null
- NotNullWhen 当方法返回指定bool值时,可空输入参数不为null
- NotNullIfNotNull 如果实参对应指定形参不为null,则返回值不为null
8.1 NotNullWhen和NotNullIfNotNull特性的应用示例
using System.Diagnostics.CodeAnalysis;
// 当方法返回true时,text不为空
public static bool TryGetDigitAsText(char number, [NotNullWhen(true)] out string? text1)
{
text1 = number switch
{
'1' => "one",
'2' => "two",
_ => null,
};
return text1 is string;
}
// 形参text接收的实参text1不为null时,那么返回值不为null
[return: NotNullIfNotNull("text")]
public static string? TryGetDigitsAsText(string? text)
{
if (text is null) return null;
string result = "";
foreach (char c in text)
{
if (TryGetDigitAsText(c, out string? digitText))
{
if (result != "") result += "-";
result += digitText.ToLower();
}
}
return result;
}
9. 继承
9.1 主要内容
- 派生:转型、protected、单继承、密封类
- 类中方法的重写:virtual、new、sealed
- 抽象类
- System.Object (C#中的所有类都从Object派生)
- 模式匹配
9.2 派生
9.2.1 基类和派生类之间的转型
派生类型能够隐式转化为它的基类。相反,基类向派生类的转换要求显式的转型操作符,因为可能转换失败,虽然编译器允许可能有效的显式转型,但"运行时"(Runtime)会坚持检查,无效的转型将会抛出异常。
9.2.2 自定义类型转换
完全不相关的类型也能相互转换,关键是要在两个类型之间提供转型操作符。C#允许类型包含显式或隐式转型操作符,但一个类型只能定义一种转型操作符,即:转型操作要不然是显式的要不然就是隐式的。建议始终采用显式转型操作符,这样可提醒别人只有在确定转型成功时才执行转换,否则要考虑转型失败后的异常处理工作。自定义显式类型转换的示例如下:
namespace Test;
internal static class Program
{
public static void Main()
{
var stoneWithMonkeySun = new Stone
{
Age = 5000,
};
// 显式转型操作
var monkeySun = (Monkey)stoneWithMonkeySun;
var detailAboutMonkeySun = new { monkeySun.Name, monkeySun.Age };
Console.WriteLine(detailAboutMonkeySun);
}
}
internal class Stone
{
public int Age { get; set; }
// 显式定义石头到猴子的转换,如果定义隐式类型转换,只需将explicit关键词更换为implicit即可
public static explicit operator Monkey(Stone stone)
{
var monkey = new Monkey
{
Age = stone.Age,
Name = "Monkey Sun"
};
return monkey;
}
}
internal class Monkey
{
public int Age { get; set; }
public string? Name { get; set; }
}
9.2.3 单继承与多继承
C#是单继承的编程语言,一个类不能同时从两个类派生,极少数需要多继承类结构时,一般的解决方案是使用聚合(aggregation),一个类包含另一个类的实例,这种多继承类结构在EntityFramework中常用,一个实体类包含了其他的实体类(一个数据表连接了其他几个数据表)。
9.2.4 密封类——sealed关键词在类中的作用
在类定义中使用sealed关键词可以阻止类的派生,示例如下:

9.3 类中方法的重写——virtual、new、override、sealed之间的关系
- new是对基类同名方法的隐藏,基类同名方法不需要任何修饰符【不推荐用new隐藏基类方法】
internal class Class1
{
public void SayHello()
{
Console.WriteLine("Hello Class1");
}
}
internal class Class2 : Class1
{
public new void SayHello()
{
Console.WriteLine("Hello Class2");
}
}
- virtual与override配套使用,只有用virtual关键词修饰过的基类方法才能在子类中用override方法覆写,如果override关键词与sealed联用则说明该方法不能再被覆写,示例如下:

9.4 抽象类与抽象成员
// 抽象类 抽象成员只能在抽象类中定义
internal abstract class PdaItem
{
public virtual string Name { get; set; }
public PdaItem(string name)
{
Name = name;
}
// 抽象方法
public abstract string GetSummary();
}
// 实现类
internal class RealPdaItem : PdaItem
{
public RealPdaItem(string name) : base(name)
{
}
public override string GetSummary()
{
throw new NotImplementedException();
}
}
- 抽象成员必须定义在抽象类中
- 抽象成员在实例化前必须被重写,所以自动为虚,不再使用virtual关键词,应当使用abstract关键词定义一个抽象成员
- 抽象类无法为抽象成员提供默认实现 。如果一个类派生自抽象类,则需要 在派生类中显式实现抽象类中的所有抽象成员 ; 如果抽象类内存在构造函数,则该构造函数必须在派生类中显式实现 ;如果派生类不需要override抽象类的构造函数,则可以使用base继承抽象类中的构造函数(见上例)。
9.5 使用is和switch进行模式匹配
9.5.1 type、const模式匹配
namespace ConsoleApp1;
internal static class Program
{
internal static void Main()
{
var guid = Guid.NewGuid();
var guidString = guid.ToString();
Console.WriteLine(guidString);
PatternMatching(1d, null, 3f, null, 4m, 5u, 7ul, 8, 9L, "", "string", true);
}
private static void PatternMatching(params object?[] objects)
{
foreach (var data in objects)
{
// 检查是否为空
// 注:不是所有类型都支持向null的隐式转换,这里使用可空元组让编译器自动推断data的类型,
// 如果data不能构成元组则data为空
if (data is not { })
{
Console.WriteLine("data is null");
}
// 类型模式匹配
else if (data is string)
{
Console.WriteLine("data is string");
// 常量模式匹配
if (data is "") Console.WriteLine("data is Empty String.");
}
else
{
Console.WriteLine($"data is {data.GetType()}");
}
}
}
}
9.5.2 元组模式匹配
namespace ConsoleApp1;
internal static class Program
{
private static readonly Index Action = 0;
private static readonly Index FileName = 1;
private static void TuplePatternMatch(params string[] args)
{
if ((args.Length, args[Action]) is (1, "show"))
{
Console.WriteLine("there are one argument which is 'show'");
}
// {} 表示非空匹配
else if ((args.Length, args[Action].ToLower(), args[FileName]) is (2, "encrypt", { } filename))
{
Console.WriteLine("there are one argument and the first one is 'encrypt'");
Console.WriteLine($"the filename is the second argument which is {filename} ");
}
}
internal static void Main()
{
TuplePatternMatch("show");
Console.WriteLine();
TuplePatternMatch("encrypt", "filename1");
Console.WriteLine();
var newStringArray = new string[] { "encrypt", "filename2" };
TuplePatternMatch(newStringArray);
Console.WriteLine();
}
}
注:由于元组实例会在is操作符被执行之前创建,所以不能把第一个和第二个条件分支调换,否则当args只有一个元素时args[FileName]访问越界
9.5.3 顺序模式匹配 与 属性模式匹配
注:在CSharp8.0之后可用,需要和CSharp7.0的 解构函数 配合使用
- 顺序模式匹配:按照参数顺序匹配,如果顺序不对就无法匹配
- 属性模式匹配:同时指定属性值和属性名称,与参数顺序无关
namespace ConsoleApp1;
internal static class Program
{
internal static void Main()
{
var person1 = new Person("sakura", "montoya");
// 顺序匹配: 按照传入参数顺序 FirstName LastName 匹配 FirstName LastName
PositionalPatternMatching(person1);
person1 = new Person("montoya", "sakura");
// 顺序匹配: 顺序调换后就无法匹配 LastName FirstName 无法匹配 FirstName LastName
PositionalPatternMatching(person1);
var person2 = new Person("sakura", "montoya");
// 属性匹配: 按照属性名称和值匹配
// FirstName: "sakura", LastName: "montoya" 匹配 LastName:"montoya", FirstName: "sakura"
PropertyPatternMatching(person2);
}
private static void PositionalPatternMatching(Person person)
{
// Positional Pattern Matching
switch (person)
{
// 常量顺序匹配
case ("sakura", { } lastname):
Console.WriteLine($"Match: the last name is {lastname}");
break;
// 如果两个参数非空
case ({ } firstName, { } lastName):
Console.WriteLine($"Mismatch: {firstName}, {lastName}");
break;
}
}
private static void PropertyPatternMatching(Person person)
{
// Property Pattern Matching
// 属性匹配: 需要指定 属性值和属性名称
switch (person)
{
// 由于需要同时匹配属性名称,所以不用考虑参数的顺序
case { LastName: { } lastName, FirstName: "sakura", }:
Console.WriteLine($"Match: the last name is {lastName}");
break;
case { LastName: { } lastName, FirstName: { } firstName, }:
Console.WriteLine($"Mismatch: {firstName}, {lastName}");
break;
}
}
}
internal class Person
{
internal string FirstName { get; }
internal string LastName { get; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
// C# 7.0 引入的解构函数
public void Deconstruct(out string firstName, out string lastName)
{
(firstName, lastName) = (FirstName, LastName);
}
}
9.5.4 尽量使用多态替代switch语句和模式匹配
在多态设计中,通过不断编写新的派生类,以及在派生类中重写虚方法,能够将程序的行为不断扩展和改变。虽然也可以使用switch语句,并用类型模式匹配来判断一个对象属于基类的哪一个子类,并执行对应操作。但这两者相比显然多态设计更好,多态设计允许从基类派生出任意多的子类而无需修改基类代码;而后者需要在基类中为每一个子类添加新的case语句,即:在添加子类时,基类总是会被修改。
读书笔记-C#8.0本质论-01的更多相关文章
- 《C#本质论》读书笔记(18)多线程处理
.NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...
- 【英语魔法俱乐部——读书笔记】 0 序&前沿
[英语魔法俱乐部——读书笔记] 0 序&前沿 0.1 以编者自身的经历引入“不求甚解,以看完为目的”阅读方式,即所谓“泛读”.找到适合自己的文章开始“由浅入深”的阅读,在阅读过程中就会见到 ...
- 《The Linux Command Line》 读书笔记01 基本命令介绍
<The Linux Command Line> 读书笔记01 基本命令介绍 1. What is the Shell? The Shell is a program that takes ...
- 《玩转Django2.0》读书笔记-探究视图
<玩转Django2.0>读书笔记-探究视图 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 视图(View)是Django的MTV架构模式的V部分,主要负责处理用户请求 ...
- 《玩转Django2.0》读书笔记-编写URL规则
<玩转Django2.0>读书笔记-编写URL规则 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. URL(Uniform Resource Locator,统一资源定位 ...
- 《玩转Django2.0》读书笔记-Django配置信息
<玩转Django2.0>读书笔记-Django配置信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 项目配置是根据实际开发需求从而对整个Web框架编写相应配置信息. ...
- 《玩转Django2.0》读书笔记-Django建站基础
<玩转Django2.0>读书笔记-Django建站基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.网站的定义及组成 网站(Website)是指在因特网上根据一 ...
- YDKJ 读书笔记 01 Function vs. Block Scope
Introduction 本系列文章为You Don't Know JS的读书笔记. 书籍地址:https://github.com/getify/You-Dont-Know-JS Scope Fro ...
- 《C# 6.0 本质论》 阅读笔记
<C# 6.0 本质论> 阅读笔记 阅读笔记不是讲述这本书的内容,只是提取了其中一部分我认为比较重要或者还没有掌握的知识,所以如果有错误或者模糊之处,请指正,谢谢! 对于C# 6.0才 ...
- 《鸟哥的Linux私房菜》读书笔记--第0章 计算机概论 硬件部分
一个下午看了不少硬件层面的知识,看得太多太快容易忘记.于是在博客上写下读书笔记. 有关硬件 个人计算机架构&接口设备 主板芯片组为“南北桥”的统称,南北桥用于控制所有组件之间的通信. 北桥连接 ...
随机推荐
- Docker网络中篇-docker网络的四种类型
通过上一篇学习,我们对docker网络有了初步的了解.本篇,咱们就来实战docker网络. docker网络实战 实战docker网络,我们将从以下几个案例来讲解 1:birdge是什么? 2:hos ...
- python 读取mysqlDB中指定的表名的DDL语句
注意: 1. 此python文件不要起名为mysql,会跟import的包名重复,python mysql.py运行报错 2.如果需要带端口号,请加database后加上, port="33 ...
- C primer plus笔记之初识C语言
初识C语言 --本文参考书籍: Stephen Prata的<C Primer Plus> 前言 C 语言是一门抽象的.面向过程的语言,C 语言广泛应用于底层开发,C 语言 ...
- 使用kamailio进行分机注册及互拨
操作系统版本:Debian 12.5_x64 kamailio版本:5.8.2 kamailio作为专业的SIP服务器,可承担注册服务器的角色.今天记录下kamailio作为注册服务器,承接分机注册, ...
- .net core 负载均衡取客户端真实IP
一个网关代码(.net core 3.1),部署到负载均衡器有故障,发现获取到的客户端IP都是内网IP了,负载均衡用的是阿里云的SLB . 记录一下修改过程 在Strup.cs 中的 Configur ...
- 全网最适合入门的面向对象编程教程:46 Python函数方法与接口-函数与事件驱动框架
全网最适合入门的面向对象编程教程:46 Python 函数方法与接口-函数与事件驱动框架 摘要: 函数是 Python 中的一等公民,是一种可重用的代码块,用于封装特定的逻辑:事件驱动框架是一种编程模 ...
- EF Core – ExecuteUpdate and ExecuteDelete (Bulk updates 批量更新和删除)
前言 EF Core 在 SaveChanges 之后会一句一句的去更新和删除数据. 有时候这个效率是很差的. 而 SQL 本来就支持批量更新和删除, 所以是 EF Core 的缺失. 在 EF Co ...
- Asp.net Core – CSS Isolation
前言 ASP.NET Core 6.0 Razor Pages 新功能, 我是用 webpack 做打包的, 所以这个对我没有什么帮助. 但是了解一下是可以的. 希望 .NET 会继续发展的更好, 多 ...
- Figma 学习笔记 – Variants
参考 Create and use variants 定义与用途 Variants 是 Component 的扩展使用方式. 它就像 HTML 元素的属性一样, 通过修改属性, 元素就会变成相应的样式 ...
- 深入理解JNDI注入—RMI/LDAP攻击
目录 前言 JNDI 注入简单理解 透过Weblogic漏洞深入理解 RMI与LDAP的区别 JNDI+RMI 漏洞代码触发链 lookup触发链 JNDI+LDAP 前言 本篇文章初衷是在研究log ...