百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

DotNet9学习指南(中篇:高级特性与语言增强)

itomcoil 2025-02-17 12:30 41 浏览

一、委托、事件和 Lambda 表达式

1.1 委托的定义、使用和组合

1.1.1 委托的定义

委托是一种引用类型,它可以封装一个或多个方法,并且可以像调用方法一样调用委托。委托的定义语法如下:

delegate 返回类型 委托名(参数列表);

例如,定义一个委托用于封装一个接受两个整数并返回整数的方法:

delegate int MathOperation(int a, int b);

1.1.2 委托的使用

定义好委托后,就可以使用它来封装具体的方法。以下是一个完整的示例:

using System;

delegate int MathOperation(int a, int b);

class Program
{
    static int Add(int a, int b)
    {
        return a + b;
    }

    static int Subtract(int a, int b)
    {
        return a - b;
    }

    static void Main()
    {
        // 创建委托实例并关联 Add 方法
        MathOperation operation = Add;
        int result = operation(5, 3);
        Console.WriteLine($"5 + 3 = {result}");

        // 重新关联 Subtract 方法
        operation = Subtract;
        result = operation(5, 3);
        Console.WriteLine($"5 - 3 = {result}");
    }
}

1.1.3 委托的组合

委托支持多播,即一个委托实例可以封装多个方法。使用 += 运算符可以将多个方法添加到委托实例中,使用 -= 运算符可以移除某个方法。示例如下:

using System;

delegate void MessagePrinter();

class Program
{
    static void PrintHello()
    {
        Console.WriteLine("Hello");
    }

    static void PrintWorld()
    {
        Console.WriteLine("World");
    }

    static void Main()
    {
        // 创建委托实例并组合多个方法
        MessagePrinter printer = PrintHello;
        printer += PrintWorld;

        // 调用委托,依次执行封装的方法
        printer();

        // 移除一个方法
        printer -= PrintHello;
        printer();
    }
}

1.2 事件的声明、订阅和发布

1.2.1 事件的声明

事件是基于委托的一种特殊类型,用于实现对象间的消息传递。事件的声明通常使用 event 关键字,结合委托类型。示例如下:

using System;

// 定义事件委托
public delegate void EventHandler();

public class Publisher
{
    // 声明事件
    public event EventHandler MyEvent;

    public void RaiseEvent()
    {
        // 触发事件
        MyEvent?.Invoke();
    }
}

1.2.2 事件的订阅和发布

事件的订阅是指将一个方法关联到事件上,发布则是触发事件。以下是一个完整的示例:

using System;

// 定义事件委托
public delegate void EventHandler();

public class Publisher
{
    // 声明事件
    public event EventHandler MyEvent;

    public void RaiseEvent()
    {
        // 触发事件
        MyEvent?.Invoke();
    }
}

public class Subscriber
{
    public void HandleEvent()
    {
        Console.WriteLine("Event handled.");
    }
}

class Program
{
    static void Main()
    {
        Publisher publisher = new Publisher();
        Subscriber subscriber = new Subscriber();

        // 订阅事件
        publisher.MyEvent += subscriber.HandleEvent;

        // 发布事件
        publisher.RaiseEvent();
    }
}

1.3 Lambda 表达式和匿名方法

1.3.1 Lambda 表达式的语法

Lambda 表达式是一种简洁的创建匿名方法的方式。其基本语法为:

(参数列表) => 表达式或语句块

例如,使用 Lambda 表达式实现一个简单的加法操作:

using System;

class Program
{
    static void Main()
    {
        Func add = (a, b) => a + b;
        int result = add(5, 3);
        Console.WriteLine($"5 + 3 = {result}");
    }
}

1.3.2 Lambda 表达式的应用场景

Lambda 表达式常用于 LINQ 查询、事件处理等场景。例如,在 LINQ 查询中筛选集合元素:

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List numbers = new List { 1, 2, 3, 4, 5 };
        var evenNumbers = numbers.Where(n => n % 2 == 0);

        foreach (var number in evenNumbers)
        {
            Console.WriteLine(number);
        }
    }
}

1.3.3 匿名方法

匿名方法是在 C# 早期版本中用于创建临时方法的方式,Lambda 表达式是其更简洁的替代方案。示例如下:

using System;

delegate int MathOperation(int a, int b);

class Program
{
    static void Main()
    {
        MathOperation operation = delegate (int a, int b)
        {
            return a + b;
        };

        int result = operation(5, 3);
        Console.WriteLine($"5 + 3 = {result}");
    }
}

二、泛型

2.1 泛型类、泛型方法和泛型接口

2.1.1 泛型类

泛型类允许在类的定义中使用类型参数,从而可以在实例化类时指定具体的类型。示例如下:

using System;

// 定义泛型类
public class GenericList
{
    private T[] items = new T[10];
    private int count = 0;

    public void Add(T item)
    {
        if (count < items.Length)
        {
            items[count++] = item;
        }
    }

    public T GetItem(int index)
    {
        if (index < count)
        {
            return items[index];
        }
        throw new IndexOutOfRangeException();
    }
}

class Program
{
    static void Main()
    {
        // 实例化泛型类,指定类型为 int
        GenericList intList = new GenericList();
        intList.Add(5);
        int item = intList.GetItem(0);
        Console.WriteLine(item);

        // 实例化泛型类,指定类型为 string
        GenericList stringList = new GenericList();
        stringList.Add("Hello");
        string str = stringList.GetItem(0);
        Console.WriteLine(str);
    }
}

2.1.2 泛型方法

泛型方法允许在方法的定义中使用类型参数。示例如下:

using System;

class Program
{
    static T Max(T a, T b) where T : IComparable
    {
        return a.CompareTo(b) >= 0 ? a : b;
    }

    static void Main()
    {
        int maxInt = Max(5, 3);
        Console.WriteLine($"Max of 5 and 3: {maxInt}");

        string maxString = Max("apple", "banana");
        Console.WriteLine($"Max of 'apple' and 'banana': {maxString}");
    }
}

2.1.3 泛型接口

泛型接口允许在接口的定义中使用类型参数。示例如下:

using System;

// 定义泛型接口
public interface IGenericInterface
{
    T GetValue();
    void SetValue(T value);
}

// 实现泛型接口
public class GenericClass : IGenericInterface
{
    private T value;

    public T GetValue()
    {
        return value;
    }

    public void SetValue(T value)
    {
        this.value = value;
    }
}

class Program
{
    static void Main()
    {
        IGenericInterface genericInt = new GenericClass();
        genericInt.SetValue(5);
        int result = genericInt.GetValue();
        Console.WriteLine(result);
    }
}

2.2 泛型约束

泛型约束用于限制泛型类型参数的范围,确保在泛型代码中可以安全地使用类型参数的成员。常见的泛型约束有以下几种:

2.2.1where T : struct

约束类型参数必须是值类型。示例如下:

using System;

class Program
{
    static void PrintValue(T value) where T : struct
    {
        Console.WriteLine(value);
    }

    static void Main()
    {
        int number = 5;
        PrintValue(number);

        // 以下代码会编译错误,因为 string 不是值类型
        // string str = "Hello";
        // PrintValue(str);
    }
}

2.2.2where T : class

约束类型参数必须是引用类型。示例如下:

using System;

class Program
{
    static void PrintObject(T obj) where T : class
    {
        if (obj != null)
        {
            Console.WriteLine(obj.ToString());
        }
    }

    static void Main()
    {
        string str = "Hello";
        PrintObject(str);

        // 以下代码会编译错误,因为 int 不是引用类型
        // int number = 5;
        // PrintObject(number);
    }
}

2.2.3where T : new()

约束类型参数必须有一个无参构造函数。示例如下:

using System;

class Program
{
    static T CreateInstance() where T : new()
    {
        return new T();
    }

    static void Main()
    {
        var instance = CreateInstance();
        Console.WriteLine(instance.GetType().Name);
    }
}

class MyClass
{
    public MyClass()
    {
    }
}

2.2.4where T : 基类名或where T : 接口名

约束类型参数必须是指定基类的派生类或实现了指定接口。示例如下:

using System;

public interface IMyInterface
{
    void DoSomething();
}

public class MyClass : IMyInterface
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something...");
    }
}

class Program
{
    static void CallDoSomething(T obj) where T : IMyInterface
    {
        obj.DoSomething();
    }

    static void Main()
    {
        MyClass myClass = new MyClass();
        CallDoSomething(myClass);
    }
}

三、异步编程

3.1 异步编程模型

异步编程是一种编程范式,用于处理可能会阻塞线程的操作,如网络请求、文件读写等,以提高程序的响应性和性能。在 .NET 中,异步编程主要通过 async 和 await 关键字来实现。

3.2Task和ValueTask

3.2.1Task

Task 是 .NET 中用于表示异步操作的类。可以使用 Task.Run 方法来启动一个异步操作。示例如下:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // 启动一个异步操作
        Task task = Task.Run(() =>
        {
            // 模拟耗时操作
            System.Threading.Thread.Sleep(2000);
            return 42;
        });

        // 等待任务完成并获取结果
        int result = await task;
        Console.WriteLine($"Result: {result}");
    }
}

3.2.2ValueTask

ValueTask 是 .NET Core 2.1 引入的一种轻量级的异步操作表示方式,它是一个结构体。ValueTask 适用于那些大多数情况下可以同步完成的异步操作,以减少内存分配。示例如下:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        ValueTask valueTask = GetValueAsync();
        int result = await valueTask;
        Console.WriteLine($"Result: {result}");
    }

    static ValueTask GetValueAsync()
    {
        // 模拟可以同步完成的操作
        return new ValueTask(42);
    }
}

3.3 异步流

在 .NET 9 中,异步流得到了进一步的支持,主要通过 IAsyncEnumerable 和 await foreach 来实现。异步流用于处理异步生成的数据流。示例如下:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        await foreach (var number in GenerateNumbersAsync())
        {
            Console.WriteLine(number);
        }
    }

    static async IAsyncEnumerable GenerateNumbersAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            // 模拟异步操作
            await Task.Delay(1000);
            yield return i;
        }
    }
}

四、.NET 9 新语言特性

4.1 模式匹配增强

在 .NET 9 中,模式匹配得到了进一步的增强,例如增强的类型模式和递归模式。

4.1.1 增强的类型模式

可以更方便地进行类型匹配和属性提取。示例如下:

using System;

class Program
{
    static void PrintInfo(object obj)
    {
        if (obj is string str)
        {
            Console.WriteLine($"String length: {str.Length}");
        }
        else if (obj is int num)
        {
            Console.WriteLine($"Number: {num}");
        }
    }

    static void Main()
    {
        PrintInfo("Hello");
        PrintInfo(42);
    }
}

4.1.2 递归模式

可以使用递归模式来匹配嵌套的数据结构。示例如下:

using System;

record Person(string Name, Person? Child);

class Program
{
    static void PrintChildNames(Person person)
    {
        if (person is { Child: { Name: var childName } })
        {
            Console.WriteLine($"Child name: {childName}");
            PrintChildNames(person.Child);
        }
    }

    static void Main()
    {
        var person = new Person("Alice", new Person("Bob", new Person("Charlie", null)));
        PrintChildNames(person);
    }
}

4.2 可空引用类型改进

.NET 9 对可空引用类型进行了进一步的改进,使得在处理可能为 null 的引用类型时更加安全。可以通过在项目文件中设置 enable 来启用可空引用类型检查。示例如下:

#nullable enable
using System;

class Program
{
    static void Main()
    {
        string? nullableString = null;
        if (nullableString is not null)
        {
            Console.WriteLine(nullableString.Length);
        }
    }
}

4.3 其他新特性

除了上述特性外,.NET 9 还引入了一些其他值得关注的语言特性,如记录类型的增强、静态抽象成员等。

4.3.1 记录类型的增强

记录类型在 .NET 9 中得到了进一步的优化,例如支持更灵活的构造函数和属性初始化。示例如下:

using System;

// 记录类型
record Person(string FirstName, string LastName)
{
    public string FullName => $"{FirstName} {LastName}";
}

class Program
{
    static void Main()
    {
        var person = new Person("John", "Doe");
        Console.WriteLine(person.FullName);
    }
}

4.3.2 静态抽象成员

静态抽象成员允许在接口中定义静态的抽象方法,要求实现接口的类型必须提供具体的实现。示例如下:

using System;

// 定义包含静态抽象成员的接口
public interface IStaticAbstract
{
    static abstract int GetValue();
}

// 实现接口
public class MyClass : IStaticAbstract
{
    public static int GetValue()
    {
        return 42;
    }
}

class Program
{
    static void Main()
    {
        int value = MyClass.GetValue();
        Console.WriteLine($"Value: {value}");
    }
}

总结

通过本篇的学习,你深入了解了 C# 的高级特性,包括委托、事件、Lambda 表达式、泛型、异步编程以及 .NET 9 带来的语言增强功能。这些知识能够帮助你编写更加灵活、高效和可维护的代码。

委托和事件为对象间的消息传递和回调机制提供了强大的支持,使得代码的解耦性得到提升。Lambda 表达式则以简洁的语法简化了委托的使用,尤其在 LINQ 查询等场景中发挥着重要作用。

泛型的引入让代码具有更好的复用性和类型安全性,通过泛型类、方法和接口,你可以编写出通用的代码逻辑,适用于不同的数据类型。同时,泛型约束进一步增强了泛型的灵活性和可靠性。

异步编程是现代软件开发中不可或缺的一部分,特别是在处理 I/O 密集型操作时,能够显著提高程序的性能和响应能力。Task 和 ValueTask 提供了不同场景下的异步操作解决方案,而异步流则让处理异步数据流变得更加方便。

.NET 9 的新语言特性,如模式匹配增强、可空引用类型改进、记录类型增强和静态抽象成员等,进一步提升了 C# 语言的表达能力和开发效率,使你能够更加优雅地解决复杂的编程问题。

在接下来的下篇中,我们将通过实际项目开发,综合运用这些知识,同时学习代码性能优化的技巧,进一步提升你的 .NET 9 开发能力。

实践案例:简单的异步文件读取与处理系统

下面我们通过一个简单的实践案例,综合运用本篇所学的异步编程、委托等知识,实现一个异步文件读取与处理系统。

using System;
using System.IO;
using System.Threading.Tasks;

// 定义一个委托,用于处理读取到的文件内容
delegate void FileContentProcessor(string content);

class FileProcessor
{
    // 异步读取文件内容的方法
    public async Task ReadFileAsync(string filePath, FileContentProcessor processor)
    {
        try
        {
            // 异步读取文件内容
            string content = await File.ReadAllTextAsync(filePath);
            // 调用委托处理文件内容
            processor(content);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error reading file: {ex.Message}");
        }
    }
}

class Program
{
    static async Task Main()
    {
        string filePath = "test.txt";
        FileProcessor fileProcessor = new FileProcessor();

        // 定义处理文件内容的方法
        FileContentProcessor processor = (content) =>
        {
            Console.WriteLine($"File content length: {content.Length}");
            // 这里可以添加更多的处理逻辑,例如统计单词数量等
        };

        // 异步读取文件并处理内容
        await fileProcessor.ReadFileAsync(filePath, processor);
    }
}

代码解释:

  1. 委托定义:FileContentProcessor 委托用于封装处理文件内容的方法,它接受一个 string 类型的参数,表示读取到的文件内容。
  2. FileProcessor 类:该类包含一个异步方法 ReadFileAsync,用于异步读取指定路径的文件内容。读取完成后,调用传入的委托来处理文件内容。
  3. Main 方法:在 Main 方法中,我们创建了 FileProcessor 类的实例,并定义了一个 Lambda 表达式作为 FileContentProcessor 委托的实现。然后调用 ReadFileAsync 方法异步读取文件并处理内容。

练习题

为了帮助你巩固所学知识,以下是一些练习题:

委托与事件相关

  1. 定义一个委托 Calculator,用于封装两个整数的计算操作(如加法、减法等),并编写一个方法,该方法接受两个整数和一个 Calculator 委托实例,返回计算结果。
  2. 创建一个 Publisher 类,该类包含一个事件 DataChanged,并提供一个方法 ChangeData 用于触发该事件。创建一个 Subscriber 类,该类订阅 Publisher 类的 DataChanged 事件,并在事件处理方法中输出一条消息。

泛型相关

  1. 实现一个泛型栈类 GenericStack,包含 Push、Pop 和 Peek 方法,分别用于入栈、出栈和查看栈顶元素。
  2. 编写一个泛型方法 Swap,用于交换两个变量的值。

异步编程相关

  1. 编写一个异步方法 DownloadFileAsync,用于模拟从网络下载文件的操作,下载完成后返回文件的大小(以字节为单位)。
  2. 使用 IAsyncEnumerable 实现一个异步生成斐波那契数列的方法,并在 Main 方法中使用 await foreach 遍历该数列。

.NET 9 新特性相关

  1. 使用模式匹配增强功能,编写一个方法,根据传入的对象类型进行不同的处理。如果是 string 类型,输出其长度;如果是 int 类型,输出其平方;如果是其他类型,输出默认消息。
  2. 定义一个包含静态抽象成员的接口 IMathOperation,该接口包含一个静态抽象方法 Calculate,然后实现一个类 AddOperation 实现该接口,并提供 Calculate 方法的具体实现,用于计算两个整数的和。

通过完成这些练习题,你可以更加深入地理解和掌握本篇所学的知识,为后续的学习和实践打下坚实的基础。

相关推荐

《Queendom》宣布冠军!女团MAMAMOO四人激动落泪

网易娱乐11月1日报道据台湾媒体报道,南韩女团竞争回归的生死斗《Queendom》昨(10/31)晚播出大决赛,并以直播方式进行,6组女团、女歌手皆演唱新歌,并加总前三轮的赛前赛、音源成绩与直播现场投...

正确复制、重写别人的代码,不算抄袭

我最近在一篇文章提到,工程师应该怎样避免使用大量的库、包以及其他依赖关系。我建议的另一种方案是,如果你没有达到重用第三方代码的阈值时,那么你就可以自己编写代码。在本文中,我将讨论一个在重用和从头开始编...

HTML DOM tr 对象_html event对象

tr对象tr对象代表了HTML表格的行。HTML文档中出现一个<tr>标签,就会创建一个tr对象。tr对象集合W3C:W3C标签。集合描述W3Ccells返回...

JS 打造动态表格_js如何动态改变表格内容

后台列表页最常见的需求:点击表头排序+一键全选。本文用原生js代码实现零依赖方案,涵盖DOM查询、排序算法、事件代理三大核心技能。效果速览一、核心思路事件入口:为每个<th>绑...

连肝7个晚上,总结了66条计算机网络的知识点

作者|哪吒来源|程序员小灰(ID:chengxuyuanxiaohui)计算机网络知识是面试常考的内容,在实际工作中经常涉及。最近,我总结了66条计算机网络相关的知识点。1、比较http0....

Vue 中 强制组件重新渲染的正确方法

作者:MichaelThiessen译者:前端小智来源:hackernoon有时候,依赖Vue响应方式来更新数据是不够的,相反,我们需要手动重新渲染组件来更新数据。或者,我们可能只想抛开当前的...

为什么100个前端只有1人能说清?浏览器重排/重绘深度解析

面试现场的"致命拷问""你的项目里做过哪些性能优化?能具体讲讲重排和重绘的区别吗?"作为面试官,我在秋招季连续面试过100多位前端候选人,这句提问几乎成了必考题。但令...

HTML DOM 介绍_dom4j html

HTMLDOM(文档对象模型)是一种基于文档的编程接口,它是HTML和XML文档的编程接口。它可以让开发人员通过JavaScript或其他脚本语言来访问和操作HTML和XML文档...

JavaScript 事件——“事件流和事件处理程序”的注意要点

事件流事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而NetscapeCommunicator的事件流是事件捕获流。事件冒泡即事件开始时由最具体的元素接收,然后逐级向上传播到较为不...

探秘 Web 水印技术_水印制作网页

作者:fransli,腾讯PCG前端开发工程师Web水印技术在信息安全和版权保护等领域有着广泛的应用,对防止信息泄露或知识产品被侵犯有重要意义。水印根据可见性可分为可见水印和不可见水印(盲水印)...

国外顶流网红为流量拍摄性侵女学生?仅被封杀三月,回归仍爆火

曾经的油管之王,顶流网红DavidDobrik复出了。一切似乎都跟他因和成员灌酒性侵女学生被骂到退网之前一样:住在950万美元的豪宅,开着20万美元的阿斯顿马丁,每条视频都有数百万观看...人们仿佛...

JavaScript 内存泄漏排查方法_js内存泄漏及解决方法

一、概述本文主要介绍了如何通过Devtools的Memory内存工具排查JavaScript内存泄漏问题。先介绍了一些相关概念,说明了Memory内存工具的使用方式,然后介绍了堆快照的...

外贸独立站,网站优化的具体内容_外贸独立站,网站优化的具体内容有哪些

Wordpress网站优化,是通过优化代码、数据库、缓存、CSS/JS等内容,提升网站加载速度、交互性和稳定性。网站加载速度,是Google搜索引擎的第一权重,也是SEO优化的前提。1.优化渲染阻塞。...

这8个CSS工具可以提升编程速度_css用什么编译器

下面为大家推荐的这8个CSS工具,有提供函数的,有提供类的,有提取代码的,还有收集CSS的统计数据的……请花费两分钟的时间看完这篇文章,或许你会找到意外的惊喜,并且为你的编程之路打开了一扇新的大门。1...

vue的理解-vue源码 历史 简介 核心特性 和jquery区别 和 react对比

一、从历史说起Web是WorldWideWeb的简称,中文译为万维网我们可以将它规划成如下的几个时代来进行理解石器时代文明时代工业革命时代百花齐放时代石器时代石器时代指的就是我们的静态网页,可以欣...