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方法

  1. 在项目的配置文件(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>
  1. 编写示例代码
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. 常见异常类型

  1. System.Exception 这是最“基本”的异常,其他的所有异常类型都从它派生
  2. System.ArgumentException 传给方法的参数无效
  3. System.ArgumentNullException 不该为null的参数为null
  4. System.AplicationException 应用程序异常
  5. System.FormatException 实参类型不符合形参规范
  6. System.IndexOutOfRangeException 数组或其他集合访问越界
  7. System.InvalidCastException 无效的类型转换
  8. System.InvalidOperationException 发生非预期的情况,应用程序不再处于有效工作状态
  9. System.InvalidOperationException 虽然找到了对应的方法签名,但该方法尚未完全实现
  10. System.NullReferenceException 引用为空,没有指向一个实例
  11. System.ArithmeticException 发生被零除以外的无效数学运算
  12. System.ArrayTypeMismatchException 试图将类型有误的元素存储到数组中
  13. 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),有些属于前置条件有些属于后置条件:

  1. 前置条件
  • AllowNull 不可空输入参数可能为null
  • DisallowNull 可空输入参数永远不为null
  1. 后置条件
  • 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 主要内容

  1. 派生:转型、protected、单继承、密封类
  2. 类中方法的重写:virtual、new、sealed
  3. 抽象类
  4. System.Object (C#中的所有类都从Object派生)
  5. 模式匹配

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之间的关系

  1. new是对基类同名方法的隐藏,基类同名方法不需要任何修饰符【不推荐用new隐藏基类方法】
internal class Class1
{
public void SayHello()
{
Console.WriteLine("Hello Class1");
}
} internal class Class2 : Class1
{
public new void SayHello()
{
Console.WriteLine("Hello Class2");
}
}
  1. 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();
}
}
  1. 抽象成员必须定义在抽象类中
  2. 抽象成员在实例化前必须被重写,所以自动为虚,不再使用virtual关键词,应当使用abstract关键词定义一个抽象成员
  3. 抽象类无法为抽象成员提供默认实现 。如果一个类派生自抽象类,则需要 在派生类中显式实现抽象类中的所有抽象成员如果抽象类内存在构造函数,则该构造函数必须在派生类中显式实现 ;如果派生类不需要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的更多相关文章

  1. 《C#本质论》读书笔记(18)多线程处理

    .NET Framework 4.0 看(本质论第3版) .NET Framework 4.5 看(本质论第4版) .NET 4.0为多线程引入了两组新API:TPL(Task Parallel Li ...

  2. 【英语魔法俱乐部——读书笔记】 0 序&前沿

    [英语魔法俱乐部——读书笔记] 0 序&前沿   0.1 以编者自身的经历引入“不求甚解,以看完为目的”阅读方式,即所谓“泛读”.找到适合自己的文章开始“由浅入深”的阅读,在阅读过程中就会见到 ...

  3. 《The Linux Command Line》 读书笔记01 基本命令介绍

    <The Linux Command Line> 读书笔记01 基本命令介绍 1. What is the Shell? The Shell is a program that takes ...

  4. 《玩转Django2.0》读书笔记-探究视图

    <玩转Django2.0>读书笔记-探究视图 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 视图(View)是Django的MTV架构模式的V部分,主要负责处理用户请求 ...

  5. 《玩转Django2.0》读书笔记-编写URL规则

    <玩转Django2.0>读书笔记-编写URL规则 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. URL(Uniform Resource Locator,统一资源定位 ...

  6. 《玩转Django2.0》读书笔记-Django配置信息

    <玩转Django2.0>读书笔记-Django配置信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 项目配置是根据实际开发需求从而对整个Web框架编写相应配置信息. ...

  7. 《玩转Django2.0》读书笔记-Django建站基础

    <玩转Django2.0>读书笔记-Django建站基础 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.网站的定义及组成 网站(Website)是指在因特网上根据一 ...

  8. YDKJ 读书笔记 01 Function vs. Block Scope

    Introduction 本系列文章为You Don't Know JS的读书笔记. 书籍地址:https://github.com/getify/You-Dont-Know-JS Scope Fro ...

  9. 《C# 6.0 本质论》 阅读笔记

    <C# 6.0 本质论> 阅读笔记   阅读笔记不是讲述这本书的内容,只是提取了其中一部分我认为比较重要或者还没有掌握的知识,所以如果有错误或者模糊之处,请指正,谢谢! 对于C# 6.0才 ...

  10. 《鸟哥的Linux私房菜》读书笔记--第0章 计算机概论 硬件部分

    一个下午看了不少硬件层面的知识,看得太多太快容易忘记.于是在博客上写下读书笔记. 有关硬件 个人计算机架构&接口设备 主板芯片组为“南北桥”的统称,南北桥用于控制所有组件之间的通信. 北桥连接 ...

随机推荐

  1. Docker网络中篇-docker网络的四种类型

    通过上一篇学习,我们对docker网络有了初步的了解.本篇,咱们就来实战docker网络. docker网络实战 实战docker网络,我们将从以下几个案例来讲解 1:birdge是什么? 2:hos ...

  2. python 读取mysqlDB中指定的表名的DDL语句

    注意: 1. 此python文件不要起名为mysql,会跟import的包名重复,python mysql.py运行报错 2.如果需要带端口号,请加database后加上, port="33 ...

  3. C primer plus笔记之初识C语言

    初识C语言 --本文参考书籍:         Stephen Prata的<C Primer Plus> 前言 C 语言是一门抽象的.面向过程的语言,C 语言广泛应用于底层开发,C 语言 ...

  4. 使用kamailio进行分机注册及互拨

    操作系统版本:Debian 12.5_x64 kamailio版本:5.8.2 kamailio作为专业的SIP服务器,可承担注册服务器的角色.今天记录下kamailio作为注册服务器,承接分机注册, ...

  5. .net core 负载均衡取客户端真实IP

    一个网关代码(.net core 3.1),部署到负载均衡器有故障,发现获取到的客户端IP都是内网IP了,负载均衡用的是阿里云的SLB . 记录一下修改过程 在Strup.cs 中的 Configur ...

  6. 全网最适合入门的面向对象编程教程:46 Python函数方法与接口-函数与事件驱动框架

    全网最适合入门的面向对象编程教程:46 Python 函数方法与接口-函数与事件驱动框架 摘要: 函数是 Python 中的一等公民,是一种可重用的代码块,用于封装特定的逻辑:事件驱动框架是一种编程模 ...

  7. EF Core – ExecuteUpdate and ExecuteDelete (Bulk updates 批量更新和删除)

    前言 EF Core 在 SaveChanges 之后会一句一句的去更新和删除数据. 有时候这个效率是很差的. 而 SQL 本来就支持批量更新和删除, 所以是 EF Core 的缺失. 在 EF Co ...

  8. Asp.net Core – CSS Isolation

    前言 ASP.NET Core 6.0 Razor Pages 新功能, 我是用 webpack 做打包的, 所以这个对我没有什么帮助. 但是了解一下是可以的. 希望 .NET 会继续发展的更好, 多 ...

  9. Figma 学习笔记 – Variants

    参考 Create and use variants 定义与用途 Variants 是 Component 的扩展使用方式. 它就像 HTML 元素的属性一样, 通过修改属性, 元素就会变成相应的样式 ...

  10. 深入理解JNDI注入—RMI/LDAP攻击

    目录 前言 JNDI 注入简单理解 透过Weblogic漏洞深入理解 RMI与LDAP的区别 JNDI+RMI 漏洞代码触发链 lookup触发链 JNDI+LDAP 前言 本篇文章初衷是在研究log ...