太原那有网站设计公司,沙坪坝集团网站建设,深圳互联时空网站优化怎么样,综合类网站怎么做C# 二十年语法变迁之 C# 9参考自从 C# 于 2000 年推出以来#xff0c;该语言的规模已经大大增加#xff0c;我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此#xff0c;我想写一系列快速参考文章#xff0c;总结自 C# 2.0 以来所有主要的新语言…C# 二十年语法变迁之 C# 9参考自从 C# 于 2000 年推出以来该语言的规模已经大大增加我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此我想写一系列快速参考文章总结自 C# 2.0 以来所有主要的新语言特性。我不会详细介绍它们中的任何一个但我希望这个系列可以作为我自己希望你也是的参考我可以不时回过头来记住我使用的工具工具箱里有。:)开始之前的一个小提示我将跳过一些更基本的东西例如 C# 2.0 引入了泛型但它们的使用范围如此广泛以至于它们不值得包括在内而且我还可以将一些功能“粘合”在一起以使其更简洁。本系列并不打算成为该语言的权威或历史记录。相反它更像是可能派上用场的重要语言功能的“备忘单”。您可能会发现浏览左侧的目录以搜索您不认识或需要快速提醒的任何功能很有用。C# 9.0仅初始化设置器这种新语法允许创建可以使用对象初始化语法[1]设置的属性但再也不会public class User {public string Name { get; init; }public int Age { get; init; }
}// ...var user new User { Name Ben, Age 30 };
user.Name Seb; // Wont compile, Name is init-only全屏查看代码[2]• “Init-Only Setters” 仅初始化属性也可以应用访问修饰符就像常规设置器一样public class User {public string Name { get; init; }public int Age { get; internal init; } // Age can only be set from within this assembly
}全屏查看代码[3]• “Internal Init-Only Setter”Top-Level Statements顶级语句这个小功能可以让您在程序的入口点即Main()函数中省略“样板”。这是一个之前和之后的演示// Before
using System;
using System.Threading.Tasks;namespace TestNamespace {class Program {static async Taskint Main(string[] args) {if (args.Length 0 args[0] Do It) {var success await Database.DownloadData();if (success) return 0;else return 1;}Console.WriteLine(What should I do? Exiting...);return 100;}}
}全屏查看代码[4]• “顶级语句-之前”// After
using System;
using System.Threading.Tasks;
using TestNamespace;if (args.Length 0 args[0] Do It) {var success await Database.DownloadData();if (success) return 0;else return 1;
}Console.WriteLine(What should I do? Exiting...);
return 100;全屏查看代码[5]• “顶级语句-之后” 请注意不再有Main()声明或命名空间声明编译器为我们合成了它我们甚至仍然可以使用args数组。唯一需要注意的是因为我们不再在TestNamespace命名空间中我们必须通过using TestNamespace 导入它如果我们想使用TestNamespace.Database类。Native-Sized Integers本机大小的整数这个以性能为导向的功能增加了两个新的关键字/别名nint和nint。这些用于表示本机平台字长的有符号/无符号整数即 32 位平台上的 32 位64 位平台上的 64 位等。从技术上讲 nint是IntPtr的别名而nuint是UIntPtr的别名。但是当使用类型为原生大小整数的变量时编译器会提供一些额外的算术运算nint nativeIntegerOne 100;
nint nativeIntegerTwo 200;IntPtr intPtrOne new IntPtr(100);
IntPtr intPtrTwo new IntPtr(200);Console.WriteLine(nativeIntegerOne nativeIntegerTwo); // 300
Console.WriteLine(intPtrOne intPtrTwo); // Doesnt compile全屏查看代码[6]• “Native Integers vs IntPtrs”Record Types记录类型此功能使定义主要用于封装数据的类型变得更加容易与抽象/封装行为的类型相反。记录类型是常规类但编译器会自动在该类型上生成一些成员以便更轻松地将它们用作数据容器。public record User(string Name, int Age) { }全屏查看代码[7]• “简单记录类型定义” 公共记录行User(string Name, int Age) { }声明了一个新的User类类型有两个属性public string Name { get; 在里面; }和公共 int 年龄 { 获取在里面; }。有一个构造函数public User(string Name, int Age)将Name分配给this.Name并将Age分配给this.Age是的ctor 参数在 PascalCase 中。实现IEquatable如果other是特定的User而不是派生类型并且Name和Age的值相等则Equals(User other)的实现返回 true 。换句话说记录类型实现了值相等。重写ToString()以提供报告所有成员值的实现。提供一个Deconstruct()实现该实现具有与记录定义中定义的顺序相同的位置参数即字符串名称int Age。void Test() {// Constructorvar user new User(Ben, 30);// PropertiesConsole.WriteLine(user.Name); // BenConsole.WriteLine(user.Age); // 30// Equalityvar user2 new User(Ben, 30);var user3 new User(Seb, 27);Console.WriteLine(user user2); // TrueConsole.WriteLine(user user3); // False// ToStringConsole.WriteLine(user); // User { Name Ben, Age 30 }Console.WriteLine(user3); // User { Name Seb, Age 27 }// Deconstructorvar (userName, userAge) user;Console.WriteLine(userName); // BenConsole.WriteLine(userAge); // 30
}全屏查看代码[8]• “生成的成员示例” 默认情况下记录类型定义不可变仅限初始化属性。您可以使用with语句创建具有修改值的记录实例的副本。with语句返回相同记录类型但具有指定修改属性的新实例。所有未指定的属性保持不变var user new User(Ben, 30);
user user with { Age 31 };
Console.WriteLine(user); // User { Name Ben, Age 31 }全屏查看代码[9]• “使用语句示例记录”现代软件工程通常认为让您的数据类型不可变可以带来多种好处。将数据复制到具有所需修改的新对象实例中而不是直接修改现有实例带来了许多好处包括使并发更容易理解和不易出错以及更容易编写类本身如果没有什么可以改变的话您只需要在构造函数中验证一次输入并且您无需担心诸如实现GetHashCode()之类的可变性。可以在此处找到更多信息NDepend 博客C# 不可变类型了解吸引力[10]。记录类型名称旁边的位置参数是可选的。我们可以通过以更传统的方式声明属性来创建类似的记录类型public record User {public string Name { get; init; }public int Age { get; init; }
}全屏查看代码[11]• “没有位置属性的记录” 这声明了一个与以前具有相同属性的用户记录。由于这是一个记录声明而不是一个类编译器仍然会为我们生成一个ToString()方法和一个IEquatable实现并且仍然支持with语句。但是如果没有位置属性编译器将不会为我们创建构造函数或解构函数。我们还可以结合这两种方法来覆盖属性的默认实现。这是一个我们使自动生成的Name属性可变的示例public record User(string Name, int Age) {public string Name { get; set; } Name;
}全屏查看代码[12]• “覆盖自动生成的属性” 语法 Name { get; 放; } 名称 这里可能看起来有点令人惊讶。事实上这是一种仅支持记录类型的特殊语法它告诉编译器我们要将Name构造函数参数分配给Name属性。这可以与任何属性一起使用public record User(string Name, int Age) {public string Note { get; set; } ${Name}, aged {Age};
}void Test() {var user new User(Ben, 30);Console.WriteLine(user.Name); // BenConsole.WriteLine(user.Age); // 30Console.WriteLine(user.Note); // Ben, aged 30
}全屏查看代码[13]• “分配构造函数参数” 在为记录类型创建自己的构造函数时您必须调用编译器生成的构造函数通过this()调用public record User(string Name, int Age) {public string Note { get; set; } ${Name}, aged {Age};public User(string name, int age, string note) : this(name, age) { // Without the this(name, age), this ctor will not compileNote note;}
}void Test() {Console.WriteLine(new User(Ben, 30).Note); // Ben, aged 30Console.WriteLine(new User(Ben, 30, Custom user note).Note); // Custom user note
}全屏查看代码[14]• “其他记录构造函数” 必须调用编译器生成的构造函数的原因现在应该很明显了。就行了public string Note { get; 放; } ${Name}年龄 {Age}; 我们使用构造函数参数Name和Age为Note分配一个默认值。如果从不调用编译器生成的构造函数则这些参数将不可用并且不清楚Note的默认值应该是什么。增强模式匹配关系匹配允许使用、、和匹配值范围。类型匹配允许在纯粹匹配对象类型时省略丢弃。下面的示例在switch 表达式中使用属性模式但关系匹配也可以与大多数其他模式一起使用var salary user switch {Manager { YearsAtCompany: 5, DirectReports: { Count: 10 } } 120_000, // Managers who have worked at the company for at least 5 years and have at least 10 direct reports get 120,000Manager { YearsAtCompany: 5 } 100_000, // Managers who have worked at the company for at least 5 years get 100,000Manager 70_000, // All other managers get 70,000 (notice no discard _ variable required any more){ YearsAtCompany: 3, Age: 18 } 50_000, // Anyone else whos at least 18 and has worked _ 30_000 // Everyone else gets 30,000
};全屏查看代码[15]• “关系和类型模式匹配” Conjunctive、disjunctive和否定模式允许您以熟悉的方式组合模式/** The following code determines whether a player is eligible for an award.* If the player has a score 100, is not dead, and is NOT a MonsterPlayer, return true.* If the player is a hero who has slain 3 monsters, or is a monster who has chomped 5 or has 200 score, return true.* Else return false.
*/
var playerIsEligibleForMvpAward player switch {Player { Score: 100, IsDead: false } and not MonsterPlayer true,HeroPlayer { MonstersSlain: 3 } or MonsterPlayer { HeroesChomped: 5 } or MonsterPlayer { Score: 200 } true,_ false
};全屏查看代码[16]• “连接/分离/否定模式” 在检查变量是否不是给定类型时 否定模式特别有用// Revive the player if theyre not a monster
if (player is not MonsterPlayer) player.Revive();// Send the player to hell if theyre not a hero, otherwise send them to heaven
if (player is not HeroPlayer hero) player.SendToHell();
else hero.SendToHeaven();全屏查看代码[17]• “否定类型检查”Target-Typed Expressions目标类型表达式如果可以推断类型则目标类型的新表达式允许您在调用构造函数时省略类型名称// Field
Dictionarystring, List(int Age, string Name) _userLookupDict new(); // No need to re-iterate the type Dictionarystring, List(int Age, string Name)!void Test() {// LocalsListstring names new(_userLookupDict.Keys); // Can still pass constructor parameters as usualUser u new() { Name Ben, Age 31 }; // Can use object initialization syntax as usual
}全屏查看代码[18]• “目标类型的新表达式”由于显而易见的原因目标类型的新表达式与隐式类型的局部变量即var 不兼容。目标类型条件允许编译器更好地找到条件表达式的两个操作数之间的公共类型。这是一个使用三元条件运算符的示例// Assume selectManager is a bool, manager is a Manager (where Manager : User) and developer is a Developer (where Developer : User)
User u selectManager ? manager : developer;全屏查看代码[19]• “目标类型三元条件” 在 C# 9 之前该行无法编译因为manager和developer是不同类型的变量。但是现在我们可以将u声明为User类型的变量它是Manager和User的共享父/基类并编译该行。不幸的是此功能也与隐式类型的本地不兼容。Covariant Return Types协变返回类型此功能允许您在覆盖基类方法时指定更衍生的返回类型abstract class Player {public abstract IWeapon GetEquippedWeapon();
}class MonsterPlayer : Player {// Here we can specify that the weapon will always be a ClawsWeapon for a MonsterPlayer:public override ClawsWeapon GetEquippedWeapon() {// ...}
}全屏查看代码[20]• “协变返回类型覆盖”GetEnumerator 扩展该功能允许您通过扩展方法将foreach支持添加到任何类型// Add a GetEnumerator to UInt16 that iterates through every bit (from MSB to LSB)
public static class UInt16Extensions {public static IEnumeratorbool GetEnumerator(this UInt16 this) {for (var i (sizeof(UInt16) * 8) - 1; i 0; --i) {yield return (this (1 i)) ! 0;}}
}// Usage example:
// This program writes 1100001100001111 to the console
ushort u (ushort) 0b1100_0011_0000_1111U;
foreach (var bit in u) {Console.Write(bit ? 1 : 0);
}全屏查看代码[21]• “扩展 GetEnumerator() 示例”模块初始化器此功能允许我们在模块中的任何其他代码大多数情况下为 DLL/EXE之前执行代码。在编写带有入口点的程序时此功能可能看起来没那么有用但是当为其他人编写库以将其作为依赖项包含在他们自己的应用程序中时此功能可能会非常方便。假设我们需要在访问我们库中的任何类型之前解析一些定制的依赖关系图。模块初始化器将让我们确保在使用我们的库之前解析图static class TypeDependencyResolver {[ModuleInitializer]public static void ResolveGraph() {// Do stuff here}
}全屏查看代码[22]• “模块初始化程序示例” 在此示例中在模块中第一次使用任何类型/方法之前的任何时间运行时都会自动调用ResolveGraph() 。在初始化函数中可以做的事情没有限制包括调用其他函数、创建对象、使用 I/O 等但根据经验初始化程序不应花费太长时间来执行或有可能引发异常。模块初始化函数必须是public和static并返回void。但是它们可以是异步的即async void。可以有多个ModuleInitializers 在同一个模块中。多个方法的执行顺序是任意的取决于运行时。SkipLocalsInit这种面向性能的属性可以应用于方法、类型和模块。它指示编译器不要发出运行时标志该标志通常会告诉运行时在函数开始之前将所有本地声明的变量归零。通常编译器会在所有方法上发出这个标志因为它可以保护我们免受某些类型的错误。但是在某些情况下将内存归零可能会显着降低性能[23]。当我们应用[SkipLocalsInit]时我们可以要求编译器跳过这一步// Following code will only write Found a non-zero byte when [SkipLocalsInit] is applied[SkipLocalsInit]
public static void Main() {Spanbyte stackData stackalloc byte[4000];for (var i 0; i stackData.Length; i) {if (stackData[i] ! 0) {Console.WriteLine(Found a non-zero byte!);break;}}
}全屏查看代码[24]• “SkipLocalsInit 示例”函数指针、SuppressGCTransition 和 UnmanagedCallersOnly托管函数指针函数指针和[SuppressGCTransition]属性是面向性能的特性允许简化间接方法调用托管或非托管/本机。函数指针是使用delegate语法声明的并且必须在不安全的上下文中使用。第一个示例展示了如何使用指向托管函数的指针public class User {public string Name { get; set; }public int Age { get; set; }public void ClearUserDetails() {Name cleared;Age 0;}
}public static class Database {public static int ClearAllRecords(string idPrefix) idPrefix.Length;public static void ClearUserDetails(User u) u.ClearUserDetails();
}// ...unsafe {delegate* managedstring, int databaseClearFuncPtr Database.ClearAllRecords;Console.WriteLine(databaseClearFuncPtr(Testing)); // Prints 7 on consolevar user new User { Name Ben, Age 31 };delegate* managedUser, void userClearFuncPtr Database.ClearUserDetails;userClearFuncPtr(user);Console.WriteLine($User: {user.Name} / {user.Age}); // Prints User: cleared / 0 on console
}全屏查看代码[25]• “托管函数指针” 第一个指针 ( databaseClearFuncPtr ) 指向Database.ClearAllRecords。它被声明为一个托管函数指针它接受一个字符串输入并返回一个int。在下一行调用它类似于调用Funcstring, int。第二个指针userClearFuncPtr显示了如何通过解决对象实例的单一调度来调用非静态函数。[26]我们不能创建指向实例方法的指针即User.ClearUserDetails()但我们可以创建一个获取实例并为我们调用相关方法的静态方法。因此userClearFuncPtr指向Database.ClearUserDetails()。它被声明为一个托管函数指针它接受用户输入并且不返回任何内容void。在下一行调用它类似于调用Action。非托管函数指针非托管指针允许您直接存储指向非托管函数的指针。您可能会通过 P/Invoke 调用或其他方式收到此指针。想象一下我们有一个具有以下实现的 C 库static const wchar_t* GetHelloString() {return LHello;
}typedef const wchar_t* (*helloStrPtr)(void);extern C __declspec(dllexport) void GetFuncPtr(helloStrPtr* outFuncPtr) {*outFuncPtr GetHelloString;
}全屏查看代码[27]• “示例本机方法声明” GetFuncPtr() 的实现需要一个指向指针的指针以便它可以将我们的函数指针设置为指向GetHelloString()。GetFuncPtr()理论上可以只返回一个函数指针但我创建了这个示例来展示编组指针到指针的可能更困难的用例。在 C# 方面我们将像这样表示导出的GetFuncPtr()public static class NativeMethods {[DllImport(NativeLib.dll, CallingConvention CallingConvention.Cdecl)]public static unsafe extern void GetFuncPtr(delegate* unmanagedchar** outFuncPtr);
}全屏查看代码[28]• “用 C# 表示 GetFuncPtr()” 然后我们可以调用GetFuncPtr()并像这样使用函数指针unsafe {delegate* unmanagedchar* getHelloStrFuncPtr;NativeMethods.GetFuncPtr(getHelloStrFuncPtr);Console.WriteLine(new String(getHelloStrFuncPtr())); // Writes Hello to the console
}全屏查看代码[29]• “GetFuncPtr() 的使用” 最后在声明非托管指针时可以指定调用约定delegate* unmanagedint, int automaticConventionFuncPtr;
delegate* unmanaged[Cdecl]int, int cdeclConventionFuncPtr;
delegate* unmanaged[Fastcall]int, int fastcallConventionFuncPtr;
delegate* unmanaged[Stdcall]int, int stdcallConventionFuncPtr;
delegate* unmanaged[Thiscall]int, int thiscallConventionFuncPtr;全屏查看代码[30]• “声明非托管函数指针”抑制GCTransition请注意GetFuncPtr()的 C 实现非常简单。通常当通过 P/Invoke 调用本机方法时运行时将首先设置 GC 以处理向非托管代码的转换。但是在某些情况下这种转换可能会增加不必要的开销。当方法被调用时这是真的微不足道、完成非常快、不做任何 I/O、不使用任何同步/线程、不抛出异常 当我们知道方法满足上面列表中的所有条件时可以 将[SuppressGCTransition]属性应用于外部方法以告诉运行时不要感染此转换[DllImport(NativeLib.dll, CallingConvention CallingConvention.Cdecl), SuppressGCTransition]
public static unsafe extern void GetFuncPtr(delegate* unmanagedchar** outFuncPtr);全屏查看代码[31]•“应用了 SuppressGCTransition 的 GetFuncPtr()”UnmanagedCallersOnly我们现在可以编写只能通过本机代码中的函数指针调用的方法。与SuppressGCTransition类似将[UnmanagedCallersOnly]属性应用于方法有助于运行时/编译器减少本地到托管调用的开销。假设我们有一个 C 实现如下所示typedef int (*getIntPtr)(void);extern C __declspec(dllexport) void InvokeFuncPtr(getIntPtr funcPtr) {std::wcout funcPtr();
}全屏查看代码[32]• “UnmanagedCallersOnly 示例C 端” 我们可以使用以下 C# 签名方法来表示此方法[DllImport(NativeLib.dll, CallingConvention CallingConvention.Cdecl)]
public static unsafe extern void InvokeFuncPtr(delegate* unmanaged[Cdecl]int funcPtr);[UnmanagedCallersOnly(CallConvs new[] { typeof(CallConvCdecl) })]
public static int ReturnInt() 123;全屏查看代码[33]• “UnmanagedCallersOnly 示例C# 端” 尝试直接从 C# 调用ReturnInt()将发出编译器错误。相反我们可以将指向它的指针传递给我们的 C 方法unsafe {NativeMethods.InvokeFuncPtr(NativeMethods.ReturnInt); // Prints 123 to std::wcout (i.e. console)
}全屏查看代码[34]• “使用 UnmanagedCallersOnly 指针”注意将SuppressGCTransition添加到InvokeFuncPtr()声明会导致此程序在运行时崩溃并显示消息“致命错误。无效程序试图从托管代码调用 UnmanagedCallersOnly 方法。”. 这是因为 GC 转换实际上是允许运行时检测是否已从本机调用者调用的方法。SourceGenerator源生成器此功能允许您编写将在编译时生成更多代码的代码。此功能只能添加/覆盖代码不能修改现有代码。设置首先您必须创建一个新的 .NET Standard 类库项目并通过 NuGet将Microsoft.CodeAnalysis.Analyzers和Microsoft.CodeAnalysis.CSharp添加到您的项目中。这将是源生成器项目它将在目标项目中生成代码。源生成器项目必须完全以.NET Standard 2.0为目标在我的测试中甚至 2.1 都没有工作.NET 5 也没有。这似乎不太可能在不久的将来改变[35]。这个新项目将包含在目标项目中生成代码的代码。为此我们必须从目标项目中添加对生成器项目的特殊引用。打开目标项目的.csproj文件添加生成器引用!-- This ItemGroup should be added inside the Project node --ItemGroupProjectReference Include..\SourceGen\SourceGen.csprojOutputItemTypeAnalyzerReferenceOutputAssemblyfalse //ItemGroup全屏查看代码[36]• “目标项目 CSPROJ 文件” 现在当我们构建目标项目时SourceGen项目将在编译期间被编译并执行。SourceGen项目将有机会在编译之前将代码插入到我们的目标项目中。实现 ISourceGenerator向实现ISourceGenerator的生成器项目添加一个类。您需要导入Mircosoft.CodeAnalysis命名空间。用[Generator]注释这个类[Generator]
public class MySourceGenerator : ISourceGenerator {public void Execute(GeneratorExecutionContext context) {// TODO}public void Initialize(GeneratorInitializationContext context) {// TODO}
}全屏查看代码[37]• “生成器类存根在生成器项目中” Initialize函数可用于注册将创建ISyntaxReceiver[38] 的函数当编译器在源项目中移动时它将依次为源项目中的每个语法节点调用其OnVisitSyntaxNode函数。您还可以通过context.SyntaxReceiver从Execute方法访问实例化的ISyntaxReceiver。下面的示例展示了如何连接一个简单的ISyntaxReceiver它将所有节点打印到一个文本文件中class SyntaxPrinter : ISyntaxReceiver {readonly FileStream _fs;readonly TextWriter _tw;public SyntaxPrinter() {_fs File.OpenWrite(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), test.txt));_tw new StreamWriter(_fs);}public void OnVisitSyntaxNode(SyntaxNode syntaxNode) {_tw.WriteLine($Node received: {syntaxNode.Kind()} {syntaxNode});_fs.Flush();}
}[Generator]
public class MySourceGenerator : ISourceGenerator {public void Execute(GeneratorExecutionContext context) {// TODO}public void Initialize(GeneratorInitializationContext context) {context.RegisterForSyntaxNotifications(() new SyntaxPrinter());}
}全屏查看代码[39]• “生成器类语法接收器示例” 要添加源代码请执行 Execute函数。源代码生成的潜力可能会填满一篇全新的博客文章因此在这种情况下我将仅展示一个将新文件添加到编译中的示例[Generator]
public class MySourceGenerator : ISourceGenerator {const string ExampleSource namespace GeneratedNamespace {public static class GeneratedClass {public static void SayHello() System.Console.WriteLine(Hello);}};public void Execute(GeneratorExecutionContext context) {context.AddSource(Generated.cs, ExampleSource);}public void Initialize(GeneratorInitializationContext context) {/* do nothing */}
}全屏查看代码[40]• “生成器源添加示例”请注意此文件是在编译期间“虚拟”添加的没有将名为Generated.cs的实际文件添加到目标项目中。这个附加文件在命名空间GeneratedNamespace中的静态类GeneratedClass中声明了一个静态方法SayHello()。在我们的目标项目中我们可以直接调用这个方法using System;GeneratedNamespace.GeneratedClass.SayHello();全屏查看代码[41]• “生成器目标源” Intellisense 会抱怨GeneratedNamespace.GeneratedClass.SayHello()不存在但无论如何我们都可以继续编译它因为我们知道在这种情况下 Intellisense 不存在某些东西。运行目标项目将在控制台上打印“Hello”。删除了对部分方法的限制此新功能还包括对部分方法的一些更改。它消除了部分方法是私有的和返回void的必要性只要定义由编译时提供。这允许我们提前声明由目标应用程序调用的方法从而消除智能感知和可发现性问题但定义由生成器在编译时提供。我们可以声明一个我们想要通过生成器实现的方法using System;Console.WriteLine(GeneratorTarget.GeneratedClass.GetInt());namespace GeneratorTarget {public static partial class GeneratedClass {public static partial int GetInt();}
}全屏查看代码[42]• “带有部分方法的生成器目标源” 不幸的是我们仍然得到一个智能感知错误告诉我们我们没有在任何地方提供GetInt()的实现但至少该错误仅位于GetInt()上并且仍然允许我们发现将要实现的方法。此方法的实现如您所料[Generator]
public class MySourceGenerator : ISourceGenerator {const string ExampleSource namespace GeneratorTarget {public static partial class GeneratedClass {public static partial int GetInt() 123;}};public void Execute(GeneratorExecutionContext context) {context.AddSource(Generated.cs, ExampleSource);}public void Initialize(GeneratorInitializationContext context) {/* do nothing */}
}全屏查看代码[43]• “使用部分方法的生成器源项目” 运行我们的目标项目会在屏幕上 打印123 。References[1] 对象初始化语法: https://benbowen.blog/post/two_decades_of_csharp_i/#object_initializers[2] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/init-only_setters.html[3] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/internal_init-only_setter.html[4] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/top-level_statements;_before.html[5] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/top-level_statements;_after.html[6] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/native_integers_vs_intptrs.html[7] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/simple_record_type_definition.html[8] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generated_members_example.html[9] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/record_with_statement_example.html[10] NDepend 博客C# 不可变类型了解吸引力: https://blog.ndepend.com/c-sharp-immutable-types-understanding-attraction/[11] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/record_without_positional_properties.html[12] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/overriding_auto_generated_properties.html[13] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/assigning_constructor_parameters.html[14] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/additional_record_constructors.html[15] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/relational_and_type_pattern_matching.html[16] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/conjunctive-disjunctive-negative_patterns.html[17] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/negative_type_check.html[18] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/target-typed_new_expressions.html[19] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/target-typed_ternary_conditional.html[20] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/covariant_return_type_override.html[21] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/extension_getenumerator()_example.html[22] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/module_initializer_example.html[23] 在某些情况下将内存归零可能会显着降低性能: https://benbowen.blog/post/clearly_too_slow/[24] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/skiplocalsinit_example.html[25] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/managed_function_pointers.html[26] 单一调度来调用非静态函数。: https://en.wikipedia.org/wiki/Dynamic_dispatch#Single_and_multiple_dispatch[27] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/example_native_method_declaration.html[28] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/representing_getfuncptr()_in_csharp.html[29] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/usage_of_getfuncptr().html[30] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/declaring_unmanaged_function_pointers.html[31] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/getfuncptr()_with_suppressgctransition_applied.html[32] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/unmanagedcallersonly_example,_c_side.html[33] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/unmanagedcallersonly_example,_csharp_side.html[34] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/using_unmanagedcallersonly_pointer.html[35] 似乎不太可能在不久的将来改变: https://github.com/dotnet/roslyn/issues/49249#issuecomment-782516845[36] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/target_project_csproj_file.html[37] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_class_stub_(in_generator_project).html[38] ISyntaxReceiver: https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.isyntaxreceiver?viewroslyn-dotnet[39] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_class_syntax_receiver_example.html[40] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_source_addition_example.html[41] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_target_source.html[42] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_target_source_with_partial_method.html[43] 全屏查看代码: https://benbowen.blog/post/two_decades_of_csharp_v/generator_source_project_with_partial_method.html