Category Archives: C#

解密 C# 中的记录类型:面向现代应用的不可变设计

解密 C# 中的记录类型:面向现代应用的不可变设计

C# 一直以来以其平衡的特性自豪——它本质上是面向对象的,但也稳步地融入了函数式编程的构造。随着 C# 9 引入记录类型(Record),并且在后续版本中(包括 C# 14)不断发展,开发者获得了一个强大的新工具,可以用最简洁的方式来建模不可变数据。但记录类型不仅仅是语法糖,它背后带来了对值语义、相等性和数据建模方式的深刻范式转变。

本文将带你深入了解 C# 中记录类型的使用,从其语义、功能到与传统类和结构体的区别。无论你是在处理 DTO(数据传输对象)、领域驱动设计(DDD)实体,还是构建函数式管道,记录类型都能提供一种简洁而表达力强的数据建模方式,简化并提升你的代码质量。

什么是记录类型?

从本质上讲,记录类型是一个引用类型,它采用基于值的相等语义。这意味着两个数据相同的记录实例被认为是相等的,而不管它们在内存中是否指向相同的引用。

传统上,C# 中的引用类型(类)默认使用引用相等。这意味着,只有当两个对象指向相同的内存地址时,它们才被认为是相等的。而记录类型改变了这一点,通过比较对象的值来进行相等性判断——这使得记录类型非常适合用于数据传输、状态建模以及表示不可变实体。

以下是一个最简单的示例:

public record User(string Name, int Age);

var u1 = new User("Alice", 30);
var u2 = new User("Alice", 30);

Console.WriteLine(u1 == u2); // True — 基于值的相等性

相比之下,如果 User 是一个类,比较结果会返回 False,因为它们是两个不同的引用。这种行为是有意为之,并且是一种强有力的转变,使 C# 更加贴近 F# 等函数式编程语言,在这些语言中,不可变性和结构相等性是默认的。

记录类型的分类:位置记录与名义记录

C# 支持两种主要的记录类型形式:

  1. 位置记录(Positional Records)
    位置记录使用简洁的构造函数语法直接在声明中定义。它们自动提供 Deconstruct()、ToString() 和 Equals() 等方法。
public record Product(string Name, decimal Price);

这种形式非常简洁,特别适用于数据驱动的应用程序,在这些应用中,重点是持有状态而非行为。它们非常适合用作 DTO、消息传递系统中的消息或跨层传递的值。

  1. 名义记录(Nominal Records,带显式属性)

你也可以声明带有手动定义属性的记录,这样可以让你更好地控制验证、格式化或自定义访问器等行为。

public record Order
{
    public string OrderId { get; init; }
    public DateTime Date { get; init; }
}

这种变体在需要更丰富的行为、注解或自定义数据模型时非常有用,例如在领域驱动设计(DDD)中的聚合根或视图模型中。

不可变性与 with 表达式

记录类型的一个最优雅的特性是通过 with 表达式进行无损的变更。你可以基于现有实例创建一个新实例,仅更改你指定的值,而不是修改原始实例。

var original = new User("Alice", 30);
var updated = original with { Age = 31 };

Console.WriteLine(original); // User { Name = Alice, Age = 30 }
Console.WriteLine(updated);  // User { Name = Alice, Age = 31 }

这种模式非常适合函数式编程、不可变状态管理和并发系统,因为共享的可变状态往往是 bug 的根源。with 表达式支持清晰的意图表达,使得编程风格更加简洁、安全。

值相等与引用相等

理解值相等(用于记录类型)与引用相等(用于类类型)之间的区别是避免意外行为的关键。例如:

public class Person(string Name);
public record Citizen(string Name);

var p1 = new Person("Bob");
var p2 = new Person("Bob");

var c1 = new Citizen("Bob");
var c2 = new Citizen("Bob");

Console.WriteLine(p1 == p2); // False
Console.WriteLine(c1 == c2); // True

这种区别不仅仅是理论性的,它影响到单元测试、字典查找、LINQ 查询和 UI 状态比较等方方面面。了解你的类型是通过结构还是通过身份进行比较,在大型代码库中至关重要。

继承与封闭性

记录类型支持继承,但默认情况下,记录类型是封闭的(sealed)。你可以使用 record class 或 record struct 语法创建记录类型层次结构,但重要的是要理解相等性比较是类型敏感的。

public record Animal(string Name);
public record Dog(string Name, string Breed) : Animal(Name);

Animal a = new Dog("Rex", "Labrador");
Animal b = new Dog("Rex", "Labrador");

Console.WriteLine(a == b); // True — 相同类型和相同值

当继承涉及到时,记录类型在多态和相等性方面引入了微妙的差异。基类记录无法轻易地覆盖派生类的行为,并且 with 表达式返回的是与原始记录相同的运行时类型——这样既保持了不可变性,也确保了类型安全。

记录类型在实际场景中的应用

以下是几个记录类型非常适用的实际场景:

  1. 建模不可变的领域事件
    在事件溯源系统中,每个状态变化都是一个独立的、不可变的事件。记录类型非常适合表示这些事件,既清晰又不可变。
public record OrderShipped(Guid OrderId, DateTime ShippedAt);
  1. 函数式管道
    当数据流经多个转换(例如,LINQ 或数据管道)时,你通常需要返回修改后的相同结构的版本。with 表达式能够优雅地实现这一点。
var transformed = inputData with { Status = "Processed" };
  1. 最小化 API 和数据契约
    ASP.NET Core 的最小 API 风格与记录类型非常契合,尤其在请求/响应类型中,能够减少冗余并提高表达性。

C# 14 中记录的新特性

C# 14 对记录的可用性进行了多项优化,特别是当与主构造函数和必需成员结合使用时。你现在可以:

  • 使用主构造函数,全面支持非记录类型,缩小记录与类之间的差距。
  • 将必需成员与记录声明混合使用,实现更安全的初始化。
  • 在组合基于记录的模型时依赖集合表达式和自然的 lambda 类型,尤其是在 API 中。

这些改进使得记录类型在 C# 中更加像一个一流的数据建模语言,它可以与 TypeScript 或 Kotlin 的简洁性相媲美,同时保持 C# 的强大鲁棒性。

结语:记录类型作为一种思维方式

理解记录类型不仅仅是学习一个语法特性——它更是改变我们设计和思考数据的方式。记录类型鼓励不可变性、清晰性和基于值的语义,这些都会带来更加可靠和可维护的代码。

无论你是在建模事件、构建 API 还是设计领域模型,记录类型提供了一种简洁而富有表现力的方式来封装状态,避免不必要的复杂性。它们不仅是类的替代品——它们代表了一种向更加声明式、意图明确的编程风格转变的根本性变化。

随着 C# 的不断发展,拥抱记录类型将帮助你编写出更短、更智能的代码。

注:转载文章,大家觉得上面文章如何?欢迎留言讨论。

本文使用chatgpt协助翻译。

作者:John Godel,版权归原作者John Godel所有

原文链接:c-sharpcorner.com/article/demystifying-records-in-c-sharp-immutable-design-for-modern-applications/

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650862550&idx=2&sn=858ec0f7327325c2d0347731a4ac9d75&poc_token=HBjlBWij7ispJs2VhF6ea60pbCr4oE7fDKieKkfc

C#中LINQ基础:101个常用LINQ操作

C#中LINQ基础:101个常用LINQ操作

LINQ(语言集成查询)是 C# 中的一个革命性特性,它彻底改变了开发人员处理数据的方式。无论你是刚刚开始学习 C#,还是希望提高编码技能,掌握 LINQ 都将显著提升你的工作效率和代码质量。

什么是 C# 中的 LINQ?

LINQ 将查询功能直接集成到 C# 语言中,提供了一种统一的方式来查询不同来源的数据——无论是数组、集合、数据库、XML 还是其他格式。通过在 C# 中使用 LINQ,你不再需要学习多种查询语言来处理不同的数据格式。

// 基本的 LINQ 查询示例
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
// 结果:2, 4

101 个 C# 中常用的 LINQ 操作

让我们来探讨一些最常用的 LINQ 操作:

class LinqTutorials
{
    static void Main(string[] args)
    {
        Console.WriteLine("=== C# 中的 LINQ 教程:101 个 LINQ 操作 ===");

        // 基本集合初始化
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
        List<string> fruits = new List<string> { "apple", "banana", "cherry", "date" };
        List<int> moreNumbers = new List<int> { 5, 6, 7, 8, 9 };

        Console.WriteLine("=== 使用 Where 进行基本的 LINQ 过滤 ===");

        // 1. 基本的 Where 过滤
        var evenNumbers = numbers.Where(x => x % 2 == 0);
        Console.WriteLine("偶数: " + string.Join(", ", evenNumbers));
        // SQL: SELECT Number FROM Numbers WHERE Number % 2 = 0;
        // 结果: 2, 4

        // 2. 复杂的 Where 过滤
        var evenAndGreaterThan3 = numbers.Where(x => x % 2 == 0 && x > 3);
        Console.WriteLine("偶数且大于 3: " + string.Join(", ", evenAndGreaterThan3));
        // SQL: SELECT Number FROM Numbers WHERE Number % 2 = 0 AND Number > 3;
        // 结果: 4

        Console.WriteLine("\n=== C# 中的 LINQ Select 操作 ===");

        // 3. 简单的 Select 操作
        var same = numbers.Select(n => n);
        Console.WriteLine("相同的数字: " + string.Join(", ", same));
        // SQL: SELECT Number FROM Numbers;
        // 结果: 1, 2, 3, 4, 5

        // 4. 带变换的 Select 操作
        var doubled = numbers.Select(x => x * 2);
        Console.WriteLine("倍增后的数字: " + string.Join(", ", doubled));
        // SQL: SELECT Number * 2 AS DoubledNumber FROM Numbers;
        // 结果: 2, 4, 6, 8, 10

        // 5. 带匿名类型的 Select 操作
        var squares = numbers.Select(x => new { Number = x, Square = x * x });
        Console.WriteLine("平方数: " + string.Join(", ", squares));
        // SQL: SELECT Number, Number * Number AS Square FROM Numbers;
        // 结果: { Number = 1, Square = 1 }, { Number = 2, Square = 4 }, 等等。

        Console.WriteLine("\n=== 使用 LINQ 的 OrderBy 操作 ===");

        // 6. 基本的 OrderBy
        var orderedNumbers = numbers.OrderBy(n => n);
        Console.WriteLine("升序排列的数字: " + string.Join(", ", orderedNumbers));
        // SQL: SELECT Number FROM Numbers ORDER BY Number ASC;
        // 结果: 1, 2, 3, 4, 5

        // 7. 使用 OrderByDescending 排序
        var descendingNumbers = numbers.OrderByDescending(n => n);
        Console.WriteLine("降序排列的数字: " + string.Join(", ", descendingNumbers));
        // SQL: SELECT Number FROM Numbers ORDER BY Number DESC;
        // 结果: 5, 4, 3, 2, 1

        // 8. 多重排序条件
        var orderedFruits = fruits.OrderBy(x => x.Length).ThenBy(x => x);
        Console.WriteLine("按长度和字母顺序排序的水果: " + string.Join(", ", orderedFruits));
        // SQL: SELECT Name FROM Fruits ORDER BY LENGTH(Name) ASC, Name ASC;
        // 结果: date, apple, banana, cherry

        Console.WriteLine("\n=== LINQ 的 GroupBy 操作 ===");

        // 9. GroupBy 操作
        var groupedByRemainder = numbers.GroupBy(x => x % 3);
        foreach (var group in groupedByRemainder)
        {
            Console.WriteLine($"余数为 {group.Key} 的数字: {string.Join(", ", group)}");
        }
        // SQL: SELECT Number % 3 AS Remainder, Number FROM Numbers GROUP BY Number % 3;
        // 结果:
        // 余数为 1 的数字: 1, 4
        // 余数为 2 的数字: 2, 5
        // 余数为 0 的数字: 3

        Console.WriteLine("\n=== 使用 LINQ 的 Join 操作 ===");

        List<Student> students = new List<Student>
    {
        new Student { ID = 1, Name = "Alice", Age = 21 },
        new Student { ID = 2, Name = "Bob", Age = 23 },
        new Student { ID = 3, Name = "Charlie", Age = 20 }
    };

        List<Course> courses = new List<Course>
    {
        new Course { StudentID = 1, CourseName = "Math" },
        new Course { StudentID = 1, CourseName = "Physics" },
        new Course { StudentID = 2, CourseName = "Chemistry" },
        new Course { StudentID = 3, CourseName = "Biology" }
    };

        // 10. 内连接操作(Inner Join)
        var studentCourses = students.Join(
            courses,
            student => student.ID,
            course => course.StudentID,
            (student, course) => new { student.Name, course.CourseName }
        );

        Console.WriteLine("学生与课程 (内连接):");
        foreach (var item in studentCourses)
        {
            Console.WriteLine($"{item.Name} 正在学习 {item.CourseName}");
        }
        // SQL: SELECT s.Name, c.CourseName FROM Students s INNER JOIN Courses c ON s.ID = c.StudentID;
        // 结果:
        // Alice 正在学习 Math
        // Alice 正在学习 Physics
        // Bob 正在学习 Chemistry
        // Charlie 正在学习 Biology

        Console.WriteLine("\n=== C# 中的 LINQ 集合操作 ===");

        // 11. 并集操作(Union)
        var union = numbers.Union(moreNumbers);
        Console.WriteLine("并集: " + string.Join(", ", union));
        // SQL: SELECT Number FROM Numbers UNION SELECT Number FROM MoreNumbers;
        // 结果: 1, 2, 3, 4, 5, 6, 7, 8, 9

        // 12. 交集操作(Intersect)
        var intersection = numbers.Intersect(moreNumbers);
        Console.WriteLine("交集: " + string.Join(", ", intersection));
        // SQL: SELECT Number FROM Numbers INTERSECT SELECT Number FROM MoreNumbers;
        // 结果: 5

        // 13. 差集操作(Except)
        var except = numbers.Except(moreNumbers);
        Console.WriteLine("差集: " + string.Join(", ", except));
        // SQL: SELECT Number FROM Numbers EXCEPT SELECT Number FROM MoreNumbers;
        // 结果: 1, 2, 3, 4

        Console.WriteLine("\n=== LINQ 元素操作教程 ===");

        // 14. First 操作
        var first = numbers.First();
        Console.WriteLine("第一个数字: " + first);
        // SQL: SELECT TOP 1 Number FROM Numbers;
        // 结果: 1

        // 15. 带条件的 First 操作
        var firstEven = numbers.First(n => n % 2 == 0);
        Console.WriteLine("第一个偶数: " + firstEven);
        // SQL: SELECT TOP 1 Number FROM Numbers WHERE Number % 2 = 0;
        // 结果: 2

        // 16. FirstOrDefault 操作
        var firstOver10 = numbers.FirstOrDefault(n => n > 10);
        Console.WriteLine("第一个大于 10 的数字 (或默认值): " + firstOver10);
        // SQL: SELECT TOP 1 Number FROM Numbers WHERE Number > 10;
        // 结果: 0(默认值)

        //

        // 17. Last 操作
        var last = numbers.Last();
        Console.WriteLine("最后一个数字: " + last);
        // SQL: SELECT TOP 1 Number FROM Numbers ORDER BY Number DESC;
        // 结果: 5

        // 18. LastOrDefault 操作
        var lastOver10 = numbers.LastOrDefault(n => n > 10);
        Console.WriteLine("最后一个大于 10 的数字 (或默认值): " + lastOver10);
        // SQL: SELECT TOP 1 Number FROM Numbers WHERE Number > 10 ORDER BY Number DESC;
        // 结果: 0(默认值)
        // 19. ElementAt
        var elementAt = numbers.ElementAt(2);
        Console.WriteLine("Element at index 2: " + elementAt);
        // SQL equivalent would require ROW_NUMBER() or similar
        // Result: 3

        Console.WriteLine("\n=== 101 LINQ Examples: Quantifier Operations ===");

        // 20. Any
        bool hasEven = numbers.Any(n => n % 2 == 0);
        Console.WriteLine("Has even numbers: " + hasEven);
        // SQL: SELECT CASE WHEN EXISTS (SELECT 1 FROM Numbers WHERE Number % 2 = 0) THEN 1 ELSE 0 END;
        // Result: True

        // 21. All
        bool allPositive = numbers.All(n => n > 0);
        Console.WriteLine("All numbers positive: " + allPositive);
        // SQL: SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM Numbers WHERE NOT (Number > 0)) THEN 1 ELSE 0 END;
        // Result: True

        // 22. Contains
        bool contains3 = numbers.Contains(3);
        Console.WriteLine("Contains 3: " + contains3);
        // SQL: SELECT CASE WHEN EXISTS (SELECT 1 FROM Numbers WHERE Number = 3) THEN 1 ELSE 0 END;
        // Result: True

        Console.WriteLine("\n=== LINQ Partitioning Tutorial ===");

        // 23. Take
        var firstThree = numbers.Take(3);
        Console.WriteLine("First 3 numbers: " + string.Join(", ", firstThree));
        // SQL: SELECT TOP 3 Number FROM Numbers;
        // Result: 1, 2, 3

        // 24. Skip
        var skipTwo = numbers.Skip(2);
        Console.WriteLine("Skip first 2 numbers: " + string.Join(", ", skipTwo));
        // SQL: SELECT Number FROM Numbers ORDER BY (SELECT NULL) OFFSET 2 ROWS;
        // Result: 3, 4, 5

        // 25. TakeWhile
        var takeWhileLessThan4 = numbers.TakeWhile(n => n < 4);
        Console.WriteLine("Take while < 4: " + string.Join(", ", takeWhileLessThan4));
        // SQL: SELECT Number FROM Numbers WHERE Number < 4;
        // Result: 1, 2, 3

        // 26. SkipWhile
        var skipWhileLessThan4 = numbers.SkipWhile(n => n < 4);
        Console.WriteLine("Skip while < 4: " + string.Join(", ", skipWhileLessThan4));
        // SQL: SELECT Number FROM Numbers WHERE Number >= 4;
        // Result: 4, 5
    }
}

// 学生类
class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

// 课程类
class Course
{
    public int StudentID { get; set; }
    public string CourseName { get; set; }
}

在 C# 中使用 LINQ:关键操作符解析

使用 Where 进行过滤
Where 操作符根据谓词过滤集合:

var evenNumbers = numbers.Where(n => n % 2 == 0);

使用 Select 进行转换
Select 操作符对每个元素进行转换:

var doubled = numbers.Select(n => n * 2);

使用 OrderBy 进行排序
OrderBy 操作符按升序排序元素:

var orderedNumbers = numbers.OrderBy(n => n);

使用 GroupBy 进行分组
GroupBy 操作符根据键将元素分组:

var groupedByRemainder = numbers.GroupBy(n => n % 3);

使用 Join 连接集合
Join 操作符将两个集合中的元素进行连接:

var studentCourses = students.Join(courses,
    student => student.ID,
    course => course.StudentID,
    (student, course) => new { student.Name, course.CourseName });

结论

我最喜欢 LINQ 的地方是,它让我更多地关注我需要什么数据,而不是怎么去获取它。像 Where、Select 和 OrderBy 这些操作符能够连贯地串联在一起,使用起来非常自然,并且清晰地表达了我的意图。当我在阅读包含 LINQ 的代码时,我能够快速理解它的目标。

虽然我仍在学习,但 LINQ 已经成为我写 C# 代码的一个重要部分。无论是处理简单的列表、复杂的对象,还是数据库查询,LINQ 都提供了一种一致的方式来表达我的数据需求,这对我来说非常有价值。我很期待继续探索更多 LINQ 操作,发现用这个强大的工具解决问题的新方法。

注:转载文章,大家觉得上面文章如何?欢迎留言讨论。

本文使用chatgpt协助翻译。

作者:Sridharan D,版权归原作者Sridharan D所有

原文链接:
c-sharpcorner.com/article/linq-in-c-sharp-tutorial-for-beginners-101-c-sharp-linq-operations/

https://mp.weixin.qq.com/s/NJsZcqfGlHwSpSSEsu7Rpw

Entity Framework Core增删改查

EntityFramework-Core(EF Core),是数据库操作的首选ORM(对象关系映射)工具,它支持多种数据库,并提供了流畅的API和代码优先的开发方式。

EntityFramework-Core优势
代码优先:允许开发者使用C#代码定义数据库模型,EF Core会自动生成数据库表结构。
数据库迁移:支持数据库的版本控制,可以轻松地更新数据库结构。
查询和更新:提供LINQ查询API,可以使用C#语法进行数据库操作,同时支持异步操作,提高应用响应速度。
多数据库支持:可以与多种数据库(如SQL Server、MySQL、PostgreSQL等)无缝集成。

数据库操作的重要性
在Web应用中,数据库操作是核心功能之一,它涉及到数据的存储、检索、更新和删除。高效、安全的数据库操作对于提升应用性能、保证数据完整性至关重要。使用ORM工具如EF Core,可以简化这些操作,减少SQL语句的编写,同时提供更好的数据模型管理和事务处理能力。

创建数据库模型

使用EF Core,首先需要定义数据库模型。模型通常由实体类表示,实体类中的属性对应数据库表中的列。

// 定义一个实体类

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

配置EF Core数据上下文

在项目中使用EF Core,需要在DbContext中定义数据上下文。

需要安装
Microsoft.EntityFrameworkCore和Microsoft.EntityFrameworkCore.SqlServer

using Microsoft.EntityFrameworkCore;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            @"Server=(localdb)\MSSQLLocalDB;Initial Catalog=mydb");
    }

    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

创建数据库

上面创建了模型,接下来使用迁移来创建数据库。这里需要借助一些工具,有关的两个工具集如下:

1).NET Core 命令行接口 (CLI) 工具可用于 Windows、Linux 或 macOS。 这些命令以 dotnet ef 开头。将 dotnet ef 安装为全局工具或本地工具。 大多数开发人员偏向于使用以下命令将 dotnet ef 安装为全局工具:
dotnet tool install --global dotnet-ef

2)包管理器控制台 (PMC) 工具在 Windows 上的 Visual Studio 中运行。 这些命令以动词开头,例如 Add-Migration、Update-Database。 获取适用于 EF Core 的包管理器控制台工具,需要安装 Microsoft.EntityFrameworkCore.Tools 包。
Install-Package Microsoft.EntityFrameworkCore.Tools

完整的迁移命令过程如下:
1.安装 dotnet ef

2.安装命令行 EF Core 开发的工具套件Microsoft.EntityFrameworkCore.Design。

3.添加第一次迁移

dotnet ef migrations add initialcreate
dotnet ef migrations add initialcreate
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

注:如果使用高版本的SQL SERVER或者SQL SERVER EXPRESS会提示错误:“证书链是由不受信任的颁发机构颁发的”,解决方法是添加 ;Trust Server Certificate=true 到连接字符串。 这将强制客户端在不进行验证的情况下信任证书。

4.将迁移应用到数据库

dotnet ef database update
Build started...
Build succeeded.
Applying migration '20250420124636_initialcreate'.
Done.

查询数据库,发现已经新建了数据库和表。

增删改查

using Microsoft.EntityFrameworkCore;

namespace ConsoleApp4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var dbContext = new ApplicationDbContext();
            var userinfo = dbContext.Users;

            Console.WriteLine("Inserting");
            dbContext.Users.Add(new User { Name = "Tom",  Email = "dese@123.com" });
            dbContext.SaveChanges();
            Console.WriteLine("Inserting complete.");

            Console.WriteLine("Querying");
            var user = dbContext.Users.OrderBy(b =>b.Id).FirstOrDefault();
            Console.WriteLine(user.Name);
            Console.WriteLine(user.Email);
            Console.WriteLine("Querying complete.");

            Console.WriteLine("Updating");
            user.Email = "234@gamil.com";
            dbContext.SaveChanges();
            Console.WriteLine(user.Name);
            Console.WriteLine(user.Email);
            Console.WriteLine("Updating complete.");

            Console.WriteLine("Delete");
            dbContext.Remove(user);
            dbContext.SaveChanges();
            Console.WriteLine("Delete complete.");

        }
    }
}

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            @"Server=(localdb)\MSSQLLocalDB;Initial Catalog=mydb");
    }

    public DbSet<User> Users { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

C# Net For Absolute Beginners

C# Net For Absolute Beginners
面向绝对初学者的C#

不知道哪里来的书,找自己需要的看看就行

Contents

目录

Chapter 1: C# and .NET
第 1 章:C# 和 .NET

Chapter 2: Setting Up Your Development Environment
第 2 章:设置开发环境

Chapter 3: Understanding the Basics of Programming
第 3 章:了解编程的基础知识

Chapter 4: Control Structures in C#
第 4 章:C# 中的控制结构

Chapter 5: Working with Data Structures in C#
第 5 章:在 C# 中使用数据结构

Chapter 6: Object-Oriented Programming in C#
第 6 章:C# 中的面向对象编程

Chapter 7: Exception Handling in C#
第 7 章:C# 中的异常处理

Chapter 8: Working with Collections in C#
第 8 章:在 C# 中使用集合

Chapter 9: Working with LINQ in C#
第 9 章:在 C# 中使用 LINQ

Chapter 10: Asynchronous Programming in C#
第 10 章:C# 中的异步编程

Chapter 11: Exception Handling in C#
第 11 章:C# 中的异常处理

Chapter 12: Working with Collections in C#
第 12 章:在 C# 中使用集合

Chapter 13: File I/O in C#
第 13 章:C# 中的文件 I/O

Chapter 14: Exception Handling in C#
第 14 章:C# 中的异常处理

Chapter 15: Working with Asynchronous Programming in C#
第 15 章:在 C# 中使用异步编程

Chapter 16: LINQ (Language Integrated Query)
第 16 章:LINQ(语言集成查询)

Chapter 17: Error Handling and Debugging in C#
第 17 章:C# 中的错误处理和调试

Chapter 1: C# and .NET

第 1 章:C# 和 .NET

Overview of C# and .NET

C# 和 .NET 概述

C# (pronounced "C-sharp") is a modern, versatile programming language developed by Microsoft as part of its .NET initiative. Released in the early 2000s, C# was designed with simplicity and efficiency in mind, making it a suitable choice for both novice programmers and experienced developers. Its syntax is influenced by C, C++, and Java, allowing for a smooth learning curve for those familiar with these languages.

C#(发音为“C-sharp”)是 Microsoft 开发的一种现代、多功能的编程语言,作为其 .NET 计划的一部分。C# 发布于 2000 年代初期,其设计考虑了简单性和效率,使其成为新手程序员和经验丰富的开发人员的合适选择。它的语法受 C、C++ 和 Java 的影响,为熟悉这些语言的人提供了平滑的学习曲线。

C# is primarily used for developing Windows applications, web applications, and game development. It supports various programming paradigms, including object-oriented programming (OOP), functional programming, and imperative programming. The language continues to evolve, with Microsoft frequently updating it to include new features and improvements, making it a relevant choice in today’s tech landscape.

C# 主要用于开发 Windows 应用程序、Web 应用程序和游戏开发。它支持各种编程范例,包括面向对象编程 (OOP)、函数式编程和命令式编程。该语言不断发展,Microsoft 经常对其进行更新以包含新功能和改进,使其成为当今技术领域的相关选择。

The .NET framework is a software development platform that provides a comprehensive ecosystem for building and running applications. It includes a large class library known as the Framework Class Library (FCL) and supports multiple programming languages, including C#. The .NET platform enables developers to create applications that can run on Windows, macOS, and Linux through .NET Core, a cross-platform implementation of .NET.

.NET Framework 是一个软件开发平台,它为构建和运行应用程序提供了一个全面的生态系统。它包括一个称为框架类库 (FCL) 的大型类库,并支持多种编程语言,包括 C#。.NET 平台使开发人员能够通过 .NET Core(.NET 的跨平台实现)创建可在 Windows、macOS 和 Linux 上运行的应用程序。

At its core, the .NET framework consists of two main components: the Common Language Runtime (CLR) and the FCL. The CLR is responsible for executing applications and providing services such as memory management, exception handling, and security. The FCL, on the other hand, offers a wide range of pre-built classes and methods that simplify common programming tasks, allowing developers to focus on creating unique application features.

.NET Framework 的核心由两个主要组件组成:公共语言运行时 (CLR) 和 FCL。CLR 负责执行应用程序并提供内存管理、异常处理和安全性等服务。另一方面,FCL 提供了广泛的预构建类和方法,可简化常见的编程任务,使开发人员能够专注于创建独特的应用程序功能。

C# and .NET work together seamlessly. C# serves as the language used to write applications, while .NET provides the necessary tools and libraries to run them. This integration enhances productivity by enabling developers to leverage a rich set of features and functionalities that are pre-built and ready to use.

C# 和 .NET 无缝协作。C# 用作编写应用程序的语言,而 .NET 提供运行它们所需的工具和库。这种集成使开发人员能够利用一组丰富的预构建且随时可用的特性和功能,从而提高了工作效率。

Importance and Applications of C

C# 的重要性和应用

C# has gained significant traction in various domains due to its versatility and robustness. Understanding its importance requires examining the contexts in which it is applied.

由于 C# 的多功能性和健壮性,它在各个领域都获得了巨大的关注。要了解它的重要性,需要检查它被应用的环境。

Desktop Applications

桌面应用程序

One of the most common applications of C# is in developing desktop applications. Using technologies like Windows Forms and Windows Presentation Foundation (WPF), developers can create rich user interfaces that interact with users effectively. C# provides the necessary tools to build applications with complex functionalities, such as data processing and real-time updates, making it ideal for software like inventory management systems, financial applications, and more.

C# 最常见的应用之一是开发桌面应用程序。使用 Windows 窗体和 Windows Presentation Foundation (WPF) 等技术,开发人员可以创建丰富的用户界面,以便有效地与用户交互。C# 提供了构建具有复杂功能(例如数据处理和实时更新)的应用程序所需的工具,使其成为库存管理系统、财务应用程序等软件的理想选择。

Web Development

Web 开发

C# is a popular choice for web development, particularly with the ASP.NET framework. ASP.NET allows developers to build dynamic web applications and services. It supports various architectural patterns, such as Model-View-Controller (MVC) and Web API, enabling the development of RESTful services. With the increasing demand for web applications, C# and ASP.NET have become vital in building scalable and secure web solutions.

C# 是 Web 开发的热门选择,尤其是 ASP.NET 框架。ASP.NET 允许开发人员构建动态 Web 应用程序和服务。它支持各种架构模式,例如模型-视图-控制器 (MVC) 和 Web API,从而支持 RESTful 服务的开发。随着对 Web 应用程序的需求不断增加,C# 和 ASP.NET 在构建可扩展且安全的 Web 解决方案方面变得至关重要。

Game Development

游戏开发

Another exciting application of C# is in game development. The Unity game engine, one of the most widely used platforms for game development, primarily utilizes C# for scripting. This has opened up opportunities for developers to create 2D and 3D games across multiple platforms, including consoles, PCs, and mobile devices. The ability to write scripts in C# within Unity allows for flexibility and creativity in game design.

C# 的另一个令人兴奋的应用是游戏开发。Unity 游戏引擎是使用最广泛的游戏开发平台之一,主要使用 C# 编写脚本。这为开发人员提供了跨多个平台(包括游戏机、PC 和移动设备)创建 2D 和 3D 游戏的机会。在 Unity 中使用 C# 编写脚本的能力为游戏设计提供了灵活性和创造力。

Mobile Development

移动开发

With the advent of Xamarin, C# has also made its way into mobile development. Xamarin is a cross-platform framework that allows developers to create native applications for iOS and Android using C#. This means that developers can share a significant amount of code between platforms, reducing development time and effort while ensuring a consistent user experience across devices.

随着 Xamarin 的出现,C# 也进入了移动开发领域。Xamarin 是一个跨平台框架,允许开发人员使用 C# 创建适用于 iOS 和 Android 的本机应用程序。这意味着开发人员可以在平台之间共享大量代码,从而减少开发时间和工作量,同时确保跨设备一致的用户体验。

Cloud-Based Applications

基于云的应用程序

As cloud computing continues to grow, C# is increasingly used for developing cloud-based applications. Microsoft Azure, the company’s cloud platform, provides a robust environment for building, deploying, and managing applications. C# integrates well with Azure services, allowing developers to create scalable and resilient applications that can handle varying loads and user demands.

随着云计算的不断发展,C# 越来越多地用于开发基于云的应用程序。Microsoft Azure 是该公司的云平台,为构建、部署和管理应用程序提供了强大的环境。C# 与 Azure 服务很好地集成,使开发人员能够创建可缩放且可复原的应用程序,这些应用程序可以处理不同的负载和用户需求。

Internet of Things (IoT)

物联网 (IoT)

C# is also finding its place in the IoT space. With the rise of connected devices, C# can be used to develop applications that interact with hardware and manage device communication. The .NET NanoFramework and .NET IoT libraries allow developers to build applications that can control and monitor IoT devices, contributing to the growth of smart homes, wearable technology, and industrial automation.

C# 也在 IoT 领域找到了自己的位置。随着互联设备的兴起,C# 可用于开发与硬件交互并管理设备通信的应用程序。.NET NanoFramework 和 .NET IoT 库允许开发人员构建可以控制和监控 IoT 设备的应用程序,从而为智能家居、可穿戴技术和工业自动化的发展做出贡献。

Machine Learning and Artificial Intelligence

机器学习和人工智能

With the increasing interest in machine learning and artificial intelligence, C# is being utilized in this domain as well. Libraries like ML.NET enable developers to build machine learning models directly within C#. This integration allows for the development of intelligent applications that can analyze data, make predictions, and improve decision-making processes.

随着人们对机器学习和人工智能的兴趣日益浓厚,C# 也被用于该领域。ML.NET 等库使开发人员能够直接在 C# 中构建机器学习模型。这种集成允许开发智能应用程序,这些应用程序可以分析数据、进行预测和改进决策过程。

Business Applications

业务应用程序

C# is widely used in the development of enterprise-level applications. Its strong type system and support for OOP principles make it a reliable choice for building large-scale systems that require maintainability and scalability. Businesses can leverage C# to create custom solutions tailored to their specific needs, whether it’s a customer relationship management (CRM) system, enterprise resource planning (ERP) software, or business intelligence applications.

C# 广泛用于企业级应用程序的开发。它强大的类型系统和对 OOP 原则的支持使其成为构建需要可维护性和可伸缩性的大型系统的可靠选择。企业可以利用 C# 创建针对其特定需求量身定制的自定义解决方案,无论是客户关系管理 (CRM) 系统、企业资源规划 (ERP) 软件还是商业智能应用程序。

Community and Ecosystem

社区和生态系统

Another significant advantage of C# is its active community and ecosystem. Microsoft and other organizations continuously contribute to the development of C# and .NET, ensuring regular updates, enhancements, and support. There are numerous resources available for learners, including documentation, tutorials, forums, and open-source projects. This vibrant community provides newcomers with the guidance and support necessary to navigate the learning curve associated with programming.

C# 的另一个显着优势是其活跃的社区和生态系统。Microsoft 和其他组织不断为 C# 和 .NET 的开发做出贡献,确保定期更新、增强和支持。有许多资源可供学习者使用,包括文档、教程、论坛和开源项目。这个充满活力的社区为新人提供必要的指导和支持,以驾驭与编程相关的学习曲线。

C# and .NET offer a powerful combination for developers, enabling them to create a wide variety of applications across multiple platforms. From desktop software to web applications, games, and cloud-based solutions, C# has established itself as a key player in the programming world. Its continued evolution and the support from Microsoft and the developer community make it an attractive option for both beginners and seasoned professionals. Understanding the basics of C# and .NET opens the door to numerous opportunities in the ever-evolving landscape of technology.

C# 和 .NET 为开发人员提供了强大的组合,使他们能够跨多个平台创建各种应用程序。从桌面软件到 Web 应用程序、游戏和基于云的解决方案,C# 已成为编程领域的关键参与者。它的持续发展以及 Microsoft 和开发人员社区的支持使其成为初学者和经验丰富的专业人士的有吸引力的选择。了解 C# 和 .NET 的基础知识为在不断发展的技术环境中打开了通往众多机会的大门。

Chapter 2: Setting Up Your Development Environment

第 2 章:设置开发环境

Installing Visual Studio

安装 Visual Studio

Setting up your development environment is the first crucial step in your journey to mastering C#. Visual Studio is Microsoft’s integrated development environment (IDE) designed for creating applications using C# and .NET. Its rich feature set provides everything you need to write, debug, and deploy applications efficiently. In this section, we will guide you through the installation process, including choosing the right edition of Visual Studio, the installation steps, and initial configuration.

设置开发环境是您掌握 C# 之旅中的关键第一步。Visual Studio 是 Microsoft 的集成开发环境 (IDE),专为使用 C# 和 .NET 创建应用程序而设计。其丰富的功能集提供了高效编写、调试和部署应用程序所需的一切。在本节中,我们将指导您完成安装过程,包括选择正确的 Visual Studio 版本、安装步骤和初始配置。

Choosing the Right Edition

选择合适的版本

Visual Studio is available in several editions, each catering to different user needs:

Visual Studio 有多个版本,每个版本都满足不同的用户需求:

Visual Studio Community: This is a free, fully-featured IDE that is suitable for individual developers, open-source projects, academic research, and small teams. It provides access to all essential tools and features, making it an excellent starting point for beginners.

Visual Studio Community:这是一个免费的、功能齐全的 IDE,适用于个人开发人员、开源项目、学术研究和小型团队。它提供对所有基本工具和功能的访问,使其成为初学者的绝佳起点。

Visual Studio Professional: This edition offers additional features and capabilities for professional developers and teams. It includes enhanced collaboration tools, more extensive support options, and features that facilitate development in larger projects.

Visual Studio Professional:此版本为专业开发人员和团队提供了额外的特性和功能。它包括增强的协作工具、更广泛的支持选项以及有助于在大型项目中进行开发的功能。

Visual Studio Enterprise: Designed for large organizations and enterprises, this edition includes advanced testing and debugging tools, architecture and modeling tools, and comprehensive DevOps capabilities. It is ideal for teams working on complex projects with significant demands.

Visual Studio Enterprise:此版本专为大型组织和大型企业而设计,包括高级测试和调试工具、体系结构和建模工具以及全面的 DevOps 功能。它非常适合从事具有大量需求的复杂项目的团队。

For beginners, the Visual Studio Community edition is recommended. It provides all the necessary tools and features without any cost, allowing you to learn and develop your skills without financial barriers.

对于初学者,建议使用 Visual Studio Community 版本。它免费提供所有必要的工具和功能,让您在没有经济障碍的情况下学习和发展您的技能。

Downloading Visual Studio

下载 Visual Studio

To download Visual Studio, follow these steps:

若要下载 Visual Studio,请执行以下步骤:

Visit the Visual Studio Website: Navigate to the official Visual Studio website.
访问 Visual Studio 网站:导航到 Visual Studio 官方网站。

Select Your Edition: Click on the “Download” button for the Community edition. This will redirect you to the download page.

选择您的版本:单击社区版的“下载”按钮。这会将您重定向到下载页面。

Download the Installer: Once on the download page, you will see a button labeled “Free download.” Click it to download the installer executable file.

下载安装程序:进入下载页面后,您将看到一个标有“免费下载”的按钮。单击它以下载安装程序可执行文件。

Run the Installer: After downloading, locate the installer file (usually in your Downloads folder) and double-click it to run.

运行安装程序:下载后,找到安装程序文件(通常在您的 Downloads 文件夹中)并双击它以运行。

Installation Process

安装过程

The installation process is straightforward, thanks to the installer’s user-friendly interface. Here’s how to proceed:
由于安装程序的用户友好界面,安装过程很简单。以下是如何进行:

Launch the Installer: Double-click the installer file you downloaded. A window will open, initializing the Visual Studio setup.

启动安装程序:双击您下载的安装程序文件。将打开一个窗口,初始化 Visual Studio 设置。

Choose Workloads: The installer will present a selection of workloads—grouped features designed for specific types of development. For a beginner learning C#, you should select the following workload:

选择 Workloads:安装程序将提供一系列工作负载,即专为特定开发类型设计的分组功能。对于学习 C# 的初学者,您应该选择以下工作负载:

.NET desktop development: This workload includes tools for building Windows applications using Windows Forms and WPF, which are essential for C# desktop application development.

.NET 桌面开发:此工作负载包括用于使用 Windows 窗体和 WPF 构建 Windows 应用程序的工具,这些工具对于 C# 桌面应用程序开发至关重要。

You can also explore additional workloads, such as ASP.NET and web development or mobile development with .NET, based on your interests.

您还可以根据自己的兴趣探索其他工作负载,例如 ASP.NET 和 Web 开发或使用 .NET 进行移动开发。

Select Optional Components: After selecting a workload, you may see options for optional components related to the chosen workload. You can leave these at their defaults for now or customize them according to your needs.

选择可选组件:选择工作负载后,您可能会看到与所选工作负载相关的可选组件选项。您可以暂时将这些值保留为默认值,也可以根据您的需要对其进行自定义。

Choose Installation Location: The installer will prompt you to select the installation location. By default, it will suggest a location on your main drive (usually C:). If you have sufficient space, it is advisable to stick with the default location.

选择安装位置:安装程序将提示您选择安装位置。默认情况下,它会建议主驱动器上的位置(通常为 C:)。如果您有足够的空间,建议坚持使用默认位置。

Start Installation: Once you’ve made your selections, click on the “Install” button. The installer will begin downloading the necessary files and installing Visual Studio on your machine. This process may take some time, depending on your internet speed and system performance.

开始安装:做出选择后,单击“安装”按钮。安装程序将开始下载必要的文件并在您的计算机上安装 Visual Studio。此过程可能需要一些时间,具体取决于您的互联网速度和系统性能。

Launch Visual Studio: After the installation completes, you will see a “Launch” button. Click it to open Visual Studio for the first time.

启动 Visual Studio:安装完成后,您将看到一个“启动”按钮。单击它可首次打开 Visual Studio。

Initial Configuration

初始配置

When you first launch Visual Studio, a setup wizard will appear, guiding you through the initial configuration. Here’s what you can expect:

首次启动 Visual Studio 时,将出现一个安装向导,指导您完成初始配置。以下是您可以期待的内容:

Sign in to Your Microsoft Account: If you have a Microsoft account, it’s beneficial to sign in. This allows you to sync settings across devices, access additional features, and receive updates. If you don’t have an account, you can create one for free during this process.

登录您的 Microsoft 帐户: 如果您有 Microsoft 帐户,登录是有益的。这允许您跨设备同步设置、访问其他功能并接收更新。如果您没有帐户,则可以在此过程中免费创建一个。

Choose Your Development Settings: Visual Studio will ask you to choose your preferred development settings. For C# development, you may select “General” or “C#” from the dropdown menu. This will configure the IDE with relevant preferences, such as window layouts and keyboard shortcuts.

选择您的开发设置:Visual Studio 将要求您选择首选的开发设置。对于 C# 开发,您可以从下拉菜单中选择“常规”或“C#”。这将使用相关的首选项 (如窗口布局和键盘快捷键) 配置 IDE。

Select a Color Theme: Visual Studio offers several color themes, including Light, Dark, and Blue. Choose the one that is most comfortable for your eyes. You can change this setting later if desired.

选择颜色主题:Visual Studio 提供了多种颜色主题,包括 浅色、深色 和 蓝色。选择最舒适的一种。如果需要,您可以稍后更改此设置。

Complete the Setup: After making your selections, click the “Start Visual Studio” button. The IDE will open, and you are now ready to begin your journey into C# programming.

完成设置:做出选择后,单击“启动 Visual Studio”按钮。IDE 将打开,您现在可以开始您的 C# 编程之旅了。

Familiarizing Yourself with Visual Studio

熟悉 Visual Studio

Once Visual Studio is open, it’s essential to familiarize yourself with the layout and key features of the IDE:

打开 Visual Studio 后,必须熟悉 IDE 的布局和主要功能:

The Menu Bar: Located at the top of the window, the menu bar contains options for file management, editing, debugging, and more.

菜单栏:菜单栏位于窗口顶部,包含用于文件管理、编辑、调试等的选项。

Solution Explorer: This pane displays your project files and folders. You can create, open, and manage your projects from here. It’s an essential tool for navigating your code.

解决方案资源管理器:此窗格显示您的项目文件和文件夹。您可以从此处创建、打开和管理您的项目。它是导航代码的重要工具。

Editor Window: The central area where you write and edit your code. Visual Studio provides syntax highlighting, IntelliSense (code suggestions), and error checking within the editor.

Editor Window (编辑器窗口):编写和编辑代码的中心区域。Visual Studio 在编辑器中提供语法突出显示、IntelliSense(代码建议)和错误检查。

Toolbox: The toolbox contains controls and components you can drag and drop onto your forms when designing graphical user interfaces (GUIs).

工具箱:工具箱包含控件和组件,您可以在设计图形用户界面 (GUI) 时将其拖放到表单上。

Output Window: This pane displays messages from the build process, debug information, and other notifications, allowing you to monitor the progress of your application.

Output Window (输出窗口):此窗格显示来自构建过程的消息、调试信息和其他通知,允许您监控应用程序的进度。

Error List: The error list shows warnings and errors in your code. It’s a helpful tool for identifying issues that need to be resolved.

错误列表:错误列表显示代码中的警告和错误。它是识别需要解决的问题的有用工具。

Properties Window: When you select an item in your project (like a form or control), the Properties window will display its properties, allowing you to modify them easily.

Properties Window(属性窗口):当您选择项目中的某个项(如表单或控件)时,Properties (属性) 窗口将显示其属性,以便您轻松修改它们。

Familiarizing yourself with these components will enhance your productivity as you begin writing C# code. Take some time to explore the IDE, try creating a simple project, and get comfortable with the environment.

熟悉这些组件将提高您开始编写 C# 代码时的工作效率。花一些时间探索 IDE,尝试创建一个简单的项目,并熟悉环境。

Creating Your First Project

创建您的第一个项目

Now that Visual Studio is installed and configured, let’s walk through the steps to create your first C# project. This will help you apply what you’ve learned about the IDE and get a taste of programming in C#.

现在,Visual Studio 已安装并配置完毕,让我们演练创建第一个 C# 项目的步骤。这将帮助您应用所学的有关 IDE 的知识,并体验 C# 编程。

Starting a New Project

开始一个新项目

Open Visual Studio: Launch Visual Studio if it’s not already open.

打开 Visual Studio:如果 Visual Studio 尚未打开,请启动 Visual Studio。

Create a New Project: From the start window, click on “Create a new project.” This will open a new dialog where you can choose the project type.

创建新项目:在开始窗口中,单击“创建新项目”。这将打开一个新对话框,您可以在其中选择项目类型。

Select a Project Template: In the new project dialog, you will see a variety of templates. For your first project, select the “Console App” template under C#. This template is ideal for beginners as it allows you to create a simple command-line application without the complexities of GUI design.

Select a Project Template(选择项目模板):在 New project (新建项目) 对话框中,您将看到各种模板。对于您的第一个项目,请选择 C# 下的“Console App”模板。此模板非常适合初学者,因为它允许您创建简单的命令行应用程序,而无需复杂的 GUI 设计。

Configure Your Project: After selecting the Console App template, click “Next.” You will be prompted to provide details for your project:

配置您的项目:选择 Console App 模板后,单击 “Next”(下一步)。系统将提示您提供项目的详细信息:

Project Name: Give your project a meaningful name, such as “HelloWorld.”

项目名称:为项目指定一个有意义的名称,例如“HelloWorld”。

Location: Choose a directory where you want to save your project files.

位置:选择要保存项目文件的目录。

Solution Name: By default, this will be the same as your project name. You can leave it as is or customize it.

解决方案名称:默认情况下,这将与您的项目名称相同。您可以保持原样或对其进行自定义。

Framework: Select the .NET version you want to use. The latest stable version is typically recommended.

框架:选择要使用的 .NET 版本。通常建议使用最新的稳定版本。

Create the Project: Once you have filled in the necessary information, click “Create.” Visual Studio will generate the project files and open the main code editor.

创建项目:填写必要信息后,单击“创建”。Visual Studio 将生成项目文件并打开主代码编辑器。

Writing Your First C# Code

编写您的第一个 C# 代码

With your new project created, it’s time to write some C# code. Let’s create a simple program that prints “Hello, World!” to the console:

创建新项目后,可以编写一些 C# 代码了。让我们创建一个简单的程序,将 “Hello, World!” 打印到控制台:

Locate the Program.cs File: In the Solution Explorer, find the “Program.cs” file. This file contains the entry point of your application.

找到 Program.cs 文件:在解决方案资源管理器中,找到“Program.cs”文件。此文件包含应用程序的入口点。

Edit the Code: Replace the existing code with the following:

编辑代码:将现有代码替换为以下内容:

using System;

namespace HelloWorld

{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

In this code, we:
在此代码中,我们:

Imported the System namespace, which provides basic functionality like input and output.

导入了 System 命名空间,该命名空间提供输入和输出等基本功能。

Defined a class called Program.

定义了一个名为 Program 的类。

Created a static method called Main, which serves as the entry point of the application.

创建了一个名为 Main 的静态方法,作为应用程序的入口点。

Used Console.WriteLine to print “Hello, World!” to the console.

使用 Console.WriteLine 将 “Hello, World!” 打印到控制台。

Save Your Changes: Press Ctrl + S to save your changes.

保存更改:按 Ctrl + S 保存更改。

Running Your Application

运行应用程序

Now that you’ve written your code, it’s time to run your application:

现在您已经编写了代码,是时候运行您的应用程序了:

Run the Project: You can run your application by clicking the green “Start” button in the toolbar, or by pressing F5 on your keyboard. Visual Studio will build your project and start the console application.

运行项目:您可以通过单击工具栏中绿色的“开始”按钮或按键盘上的 F5 来运行您的应用程序。Visual Studio 将生成您的项目并启动控制台应用程序。

View the Output: A console window will appear displaying “Hello, World!” Congratulations! You’ve successfully created and run your first C# application.

View the Output(查看输出):将出现一个控制台窗口,显示 “Hello, World!”祝贺!您已成功创建并运行您的第一个 C# 应用程序。

Debugging Your Code

调试代码

As you progress in your programming journey, debugging will become an essential skill. Visual Studio offers powerful debugging tools to help you identify and fix issues in your code. Let’s explore the basics of debugging in Visual Studio.

随着您在编程过程中的进步,调试将成为一项基本技能。Visual Studio 提供了强大的调试工具,可帮助你识别和修复代码中的问题。让我们探索一下在 Visual Studio 中进行调试的基础知识。

Setting Breakpoints: A breakpoint allows you to pause the execution of

设置断点:断点允许您暂停执行

Debugging Your Code

调试代码

As you progress in your programming journey, debugging will become an essential skill. Visual Studio offers powerful debugging tools to help you identify and fix issues in your code. In this section, we will explore the basics of debugging in Visual Studio, including setting breakpoints, stepping through code, and using the watch window.

随着您在编程过程中的进步,调试将成为一项基本技能。Visual Studio 提供了强大的调试工具,可帮助你识别和修复代码中的问题。在本节中,我们将探讨在 Visual Studio 中进行调试的基础知识,包括设置断点、单步执行代码和使用监视窗口。

Setting Breakpoints

设置断点

A breakpoint allows you to pause the execution of your program at a specific line of code. This is particularly useful for examining the state of your application at that point, including variable values and the flow of control. To set a breakpoint, follow these steps:

断点允许您在特定代码行处暂停程序的执行。这对于检查应用程序在该点的状态(包括变量值和控制流)特别有用。要设置断点,请执行以下步骤:

Open Your Code: Open the Program.cs file containing your code.

打开您的代码:打开包含您的代码的 Program.cs 文件。

Locate the Line: Find the line of code where you want to set the breakpoint. For example, you might want to set a breakpoint at the line that writes to the console.

找到 Line:找到要设置断点的代码行。例如,您可能希望在写入控制台的行处设置断点。

Set the Breakpoint: Click in the left margin next to the line number or press F9 while the cursor is on that line. A red dot will appear, indicating that a breakpoint has been set.

设置断点:单击行号旁边的左旁注,或在光标位于该行上时按 F9。将出现一个红点,表示已设置断点。

Run Your Application: Start your application again by clicking the green “Start” button or pressing F5. The execution will pause when it reaches the breakpoint.

运行您的应用程序:通过单击绿色的“开始”按钮或按 F5 再次启动您的应用程序。当执行到达断点时,执行将暂停。

Stepping Through Code

单步执行代码

Once your application is paused at a breakpoint, you can step through the code line by line. This allows you to observe the flow of execution and understand how data changes over time. Here are the primary commands for stepping through code:

一旦应用程序在断点处暂停,您就可以逐行单步执行代码。这使您可以观察执行流程并了解数据如何随时间变化。以下是用于单步执行代码的主要命令:

Step Over (F10): This command executes the current line of code and moves to the next line. If the current line contains a method call, the entire method will be executed, and the debugger will move to the next line in the calling method.

Step Over (F10):此命令执行当前代码行并移至下一行。如果当前行包含方法调用,则将执行整个方法,并且调试器将移动到调用方法中的下一行。

Step Into (F11): This command is used when you want to dive deeper into a method that is being called. If the current line contains a method call, the debugger will move into that method, allowing you to inspect its internal logic.

Step Into (F11):当您想要更深入地了解正在调用的方法时,使用此命令。如果当前行包含方法调用,则调试器将移动到该方法中,从而允许您检查其内部逻辑。

Step Out (Shift + F11): If you are inside a method and want to return to the calling method, you can use this command. It executes the remaining lines of the current method and pauses execution at the line after the method call.

跳出 (Shift + F11):如果您在方法内部并希望返回到调用方法,则可以使用此命令。它执行当前方法的剩余行,并在方法调用后的行处暂停执行。

Continue (F5): This command resumes execution of the application until it hits the next breakpoint or completes.

Continue (F5):此命令继续执行应用程序,直到它遇到下一个断点或完成。

As you step through your code, you can observe the state of variables in the current scope, which can help identify logical errors or unexpected behavior.

在单步执行代码时,您可以观察当前范围内变量的状态,这有助于识别逻辑错误或意外行为。

Using the Watch Window

使用 Watch 窗口

The Watch window is a powerful tool that allows you to monitor specific variables and expressions while debugging. Here’s how to use it effectively:

Watch (监视) 窗口是一个强大的工具,允许您在调试时监视特定的变量和表达式。以下是有效使用它的方法:

Open the Watch Window: If it’s not already visible, go to Debug in the menu bar, select Windows, and then click on Watch. You can choose one of the available Watch windows (e.g., Watch 1).

打开监视窗口:如果尚未显示,请转到菜单栏中的 Debug,选择 Windows,然后单击 Watch。您可以选择一个可用的 Watch (监视) 窗口(例如,Watch 1 (监视 1))。

Add Variables to Watch: While your application is paused at a breakpoint, you can add variables to the Watch window. Simply type the variable name or an expression you want to evaluate in one of the available slots.

Add Variables to Watch(添加要监视的变量):当应用程序在断点处暂停时,您可以将变量添加到 Watch (监视) 窗口。只需在其中一个可用槽中键入要计算的变量名称或表达式即可。

Monitor Values: As you step through your code, the Watch window will display the current value of the variables you added. This allows you to track changes over time and understand how data flows through your application.

Monitor Values:当您逐步执行代码时,Watch 窗口将显示您添加的变量的当前值。这样,您就可以跟踪随时间的变化,并了解数据如何流经应用程序。

Remove or Edit Watches: If you want to remove a variable from the Watch window, right-click on it and select Delete. You can also edit the expression in the Watch window to evaluate different variables or expressions.

删除或编辑监视:如果要从 Watch (监视) 窗口中删除变量,请右键单击该变量并选择 Delete。您还可以在 Watch (监视) 窗口中编辑表达式以计算不同的变量或表达式。

Common Debugging Scenarios

常见调试场景

As a beginner, you might encounter several common debugging scenarios. Here are a few examples and how to approach them:

作为初学者,您可能会遇到几种常见的调试场景。以下是一些示例以及如何处理它们:

Null Reference Exception: This occurs when you try to access a member of an object that is null. If you encounter this exception, set a breakpoint at the line causing the issue and inspect the object. Ensure it has been initialized before accessing its properties or methods.

Null 引用异常:当您尝试访问为 null 的对象的成员时,会发生这种情况。如果遇到此异常,请在导致问题的行处设置断点并检查对象。确保在访问其属性或方法之前已对其进行初始化。

Out of Range Exception: This exception happens when you attempt to access an element in an array or collection using an index that is outside its bounds. Use the Watch window to check the length of the array or collection and the index being accessed to identify the problem.

超出范围异常:当您尝试使用超出其边界的索引访问数组或集合中的元素时,会发生此异常。使用 Watch (监视) 窗口检查数组或集合的长度以及正在访问的索引,以确定问题。

Logic Errors: These are more subtle and may not produce exceptions but result in incorrect output. Set breakpoints and step through the code to verify that each line behaves as expected. Use the Watch window to monitor variable values and understand how they influence the application’s logic.

逻辑错误:这些错误更微妙,可能不会产生异常,但会导致不正确的输出。设置断点并单步执行代码,以验证每行的行为是否符合预期。使用 Watch 窗口可以监视变量值并了解它们如何影响应用程序的逻辑。

Infinite Loops: If your application seems to hang, you may be stuck in an infinite loop. You can pause the execution using the Debug menu, inspect the loop conditions, and understand why the loop is not terminating as expected.

无限循环:如果您的应用程序似乎挂起,则您可能陷入了无限循环。您可以使用 Debug (调试) 菜单暂停执行,检查循环条件,并了解循环未按预期终止的原因。

Debugging is an essential skill in programming. The more you practice, the better you will become at identifying and resolving issues in your code.

调试是编程中的一项基本技能。您练习的越多,您就越擅长识别和解决代码中的问题。

Configuring Visual Studio Settings

配置 Visual Studio 设置

To enhance your development experience, you may want to configure Visual Studio settings to suit your preferences. Customizing settings can improve your productivity and make it easier to write code. Here are some settings you can adjust:

为了增强您的开发体验,您可能需要配置 Visual Studio 设置以符合您的偏好。自定义设置可以提高您的工作效率,并简化代码编写过程。以下是您可以调整的一些设置:

Customizing the Editor

自定义编辑器

Font and Colors: Go to Tools in the menu bar and select Options. In the Options dialog, navigate to Environment > Fonts and Colors. Here, you can customize the font size, style, and color scheme for various text types, such as keywords, comments, and strings.

字体和颜色:转到菜单栏中的 工具 ,然后选择 选项。在“选项”对话框中,导航到 Environment > Fonts and Colors。在这里,您可以自定义各种文本类型(如关键字、注释和字符串)的字体大小、样式和配色方案。

Code Formatting: Under Text Editor, select C# and then Code Style. You can configure formatting rules such as indentation, spacing, and line breaks. Consistent code formatting enhances readability and maintains a professional appearance.

代码格式设置:在 Text Editor 下,选择 C#,然后选择 Code Style。您可以配置格式规则,例如缩进、间距和换行符。一致的代码格式增强了可读性并保持了专业的外观。

IntelliSense: IntelliSense is a powerful feature that provides code suggestions and documentation as you type. In the Options dialog, you can adjust settings for IntelliSense under Text Editor > C# > IntelliSense. You can control when suggestions appear, the number of items displayed, and more.

IntelliSense:IntelliSense 是一项强大的功能,可在您键入时提供代码建议和文档。在“选项”对话框中,您可以在 Text Editor > C# > IntelliSense 下调整 IntelliSense 的设置。您可以控制何时显示建议、显示的项目数等。

Setting Up Shortcuts

设置快捷方式

Customizing keyboard shortcuts can significantly speed up your workflow. To configure shortcuts:

自定义键盘快捷键可以显著加快您的工作流程。要配置快捷方式:

Go to Tools > Options, then navigate to Environment > Keyboard.

转到 Tools > Options,然后导航到 Environment > Keyboard。

In the “Show commands containing” box, type the command you want to customize (e.g., “Edit.FormatDocument”).

在“Show commands containing(显示包含的命令)”框中,键入要自定义的命令(例如,“Edit.FormatDocument”)。

Assign a new shortcut by placing your cursor in the “Press shortcut keys” box and pressing the desired key combination. Click Assign to save the changes.

通过将光标置于 “Press shortcut keys” 框中并按下所需的组合键来分配新的快捷键。单击 Assign 以保存更改。

You can also reset all shortcuts to the default settings if needed.

如果需要,您还可以将所有快捷键重置为默认设置。

Source Control Integration

源代码控制集成

Visual Studio integrates seamlessly with version control systems, allowing you to manage your codebase effectively. If you plan to collaborate on projects or want to track changes, consider setting up a source control system like Git:

Visual Studio 与版本控制系统无缝集成,使您能够有效地管理代码库。如果您计划在项目上进行协作或想要跟踪更改,请考虑设置一个源代码控制系统,例如 Git:

Install Git: Download and install Git from the official Git website.

安装 Git:从 Git 官方网站下载并安装 Git。

Set Up a Repository: Create a local Git repository for your project by right-clicking on your project in Solution Explorer and selecting Git > Create Git Repository.

设置存储库:通过在解决方案资源管理器中右键单击您的项目并选择 Git > Create Git Repository,为您的项目创建本地 Git 存储库。

Connect to Remote Repositories: If you want to collaborate on GitHub, create a repository on GitHub, then connect your local repository to the remote one using the command line or Visual Studio's Git integration.

连接到远程存储库:如果要在 GitHub 上协作,请在 GitHub 上创建存储库,然后使用命令行或 Visual Studio 的 Git 集成将本地存储库连接到远程存储库。

Commit and Push Changes: Use Visual Studio’s built-in Git features to commit your changes and push them to your remote repository. This ensures your code is backed up and accessible to collaborators.

提交和推送更改:使用 Visual Studio 的内置 Git 功能提交更改并将其推送到远程存储库。这可确保您的代码得到备份并可供协作者访问。

Themes and Layouts

主题和布局

Visual Studio allows you to customize the layout of windows and panels according to your preferences:

Visual Studio 允许您根据自己的喜好自定义窗口和面板的布局:

Adjust Window Layout: You can drag and dock windows (like Solution Explorer, Properties, and Output) to different positions in the IDE. This flexibility enables you to create a workspace that feels comfortable and efficient.

调整窗口布局:您可以将窗口(如解决方案资源管理器、属性和输出)拖动并停靠到 IDE 中的不同位置。这种灵活性使您能够创建一个感觉舒适和高效的工作空间。

Save Layouts: After customizing your layout, you can save it by going to Window > Save Window Layout. This is particularly useful if you work on multiple projects and want to switch between different layouts.

保存布局: 自定义布局后,您可以通过转到 Window > Save Window Layout 来保存它。如果您处理多个项目并希望在不同布局之间切换,这将特别有用。

Use Themes: Besides color themes, you can explore Visual Studio extensions that offer additional themes or customize the IDE’s appearance further. The Visual Studio Marketplace has numerous options available.

使用主题:除了颜色主题之外,您还可以探索提供其他主题或进一步自定义 IDE 外观的 Visual Studio 扩展。Visual Studio Marketplace 有许多可用选项。

Summary of the Development Environment

开发环境总结

In summary, setting up your development environment is a foundational step in your C# programming journey. Installing Visual Studio, configuring it to your preferences, and familiarizing yourself with its features will significantly enhance your productivity and learning experience.

总之,设置开发环境是 C# 编程之旅的基础步骤。安装 Visual Studio,根据您的喜好对其进行配置,并熟悉其功能将显着提高您的工作效率和学习体验。

As you create your first projects and begin to write code, remember that the IDE is a powerful ally that can help you through debugging, source control, and code management. Embrace the tools available to you, and leverage the resources in the Visual Studio community to maximize your learning potential.

当您创建第一个项目并开始编写代码时,请记住,IDE 是一个强大的盟友,可以帮助您进行调试、源代码管理和代码管理。利用可用的工具,并利用 Visual Studio 社区中的资源来最大限度地发挥你的学习潜力。

Now that your development environment is fully set up, you’re ready to dive into the exciting world of C# programming. As you progress through this book, each chapter will build upon the knowledge you gain, allowing you to grow as a developer and tackle increasingly complex challenges.

现在,您的开发环境已完全设置完毕,您可以深入探索令人兴奋的 C# 编程世界。随着您阅读本书的进度,每一章都将以您获得的知识为基础,让您成长为一名开发人员并应对日益复杂的挑战。

Chapter 3: Understanding the Basics of Programming

第 3 章:了解编程的基础知识

What is Programming?

什么是编程?

Programming is the process of creating a set of instructions that a computer can execute to perform specific tasks. These instructions, known as code, are written in programming languages, which provide the syntax and semantics necessary to communicate with computers. At its core, programming involves problem-solving and logical thinking, where developers must break down complex problems into smaller, manageable components that can be translated into code.

编程是创建一组指令的过程,计算机可以执行这些指令来执行特定任务。这些指令(称为代码)是用编程语言编写的,这些语言提供与计算机通信所需的语法和语义。编程的核心涉及解决问题和逻辑思维,开发人员必须将复杂问题分解为更小、可管理的组件,这些组件可以转换为代码。

Programming serves various purposes, from automating repetitive tasks to developing complex software applications, games, websites, and more. It enables the manipulation of data, controls hardware components, and allows for the interaction between users and machines. As technology continues to evolve, the role of programming becomes increasingly vital across different industries, driving innovation and efficiency.

编程有多种用途,从自动化重复性任务到开发复杂的软件应用程序、游戏、网站等。它支持数据处理、控制硬件组件,并允许用户和机器之间的交互。随着技术的不断发展,编程在不同行业中的作用变得越来越重要,从而推动创新和效率。

Data Types and Variables in C

C# 语言中的数据类型和变量

In C#, data types and variables are fundamental concepts that allow developers to store and manipulate data. Understanding these concepts is crucial for writing effective code. Let’s delve into what data types and variables are and how they function in C#.

在 C# 中,数据类型和变量是允许开发人员存储和作数据的基本概念。理解这些概念对于编写有效的代码至关重要。让我们深入研究什么是数据类型和变量,以及它们在 C# 中是如何工作的。

Data Types

数据类型

A data type defines the kind of data a variable can hold. In C#, data types can be broadly categorized into two groups: value types and reference types.

数据类型定义变量可以保存的数据类型。在 C# 中,数据类型可以大致分为两组:值类型和引用类型。

Value Types

值类型

Value types directly contain their data. When a value type is assigned to another variable, a copy of the value is made. Common value types in C# include:

值类型直接包含其数据。将值类型分配给另一个变量时,将创建该值的副本。C# 中的常见值类型包括:

Integer Types: These represent whole numbers. Examples include:

整数类型:这些表示整数。示例包括:

int: 32-bit signed integer.
32 位有符号整数。

long: 64-bit signed integer.
64 位有符号整数。

short: 16-bit signed integer.
16 位有符号整数。

byte: 8-bit unsigned integer.
8 位无符号整数。

Floating Point Types: These represent numbers with decimal points. Examples include:

浮点类型:这些类型表示带小数点的数字。示例包括:

float: 32-bit single-precision floating point.
32 位单精度浮点。

double: 64-bit double-precision floating point.
64 位双精度浮点。

Boolean: The bool type represents true or false values.

布尔值:bool 类型表示 true 或 false 值。

Character: The char type represents a single 16-bit Unicode character.

字符:char 类型表示单个 16 位 Unicode 字符。

Structs and Enums: These are user-defined value types that allow for grouping related values and defining enumerations, respectively.

结构体和枚举:这些是用户定义的值类型,分别允许对相关值进行分组和定义枚举。

Reference Types

引用类型

Reference types store a reference to the data rather than the data itself. When a reference type is assigned to another variable, both variables point to the same memory location. Common reference types in C# include:
引用类型存储对数据的引用,而不是数据本身。将引用类型分配给另一个变量时,两个变量都指向相同的内存位置。C# 中的常见引用类型包括:

Strings: The string type represents sequences of characters. Strings are immutable, meaning their content cannot be changed after creation.

字符串:string 类型表示字符序列。字符串是不可变的,这意味着它们的内容在创建后无法更改。

Arrays: Arrays are collections of elements of the same type, stored in a contiguous block of memory.

数组:数组是相同类型的元素的集合,存储在连续的内存块中。

Classes: Classes are user-defined types that encapsulate data and behavior. They allow for the creation of complex objects.

类:类是封装数据和行为的用户定义类型。它们允许创建复杂的对象。

Interfaces: Interfaces define contracts that classes can implement, allowing for polymorphism.

接口:接口定义类可以实现的协定,允许多态性。

Declaring Variables

声明变量

A variable is a named storage location in memory that holds a value. To declare a variable in C#, you specify the data type followed by the variable name. Here’s the general syntax:

变量是内存中保存值的命名存储位置。要在 C# 中声明变量,请指定数据类型,后跟变量名称。以下是一般语法:

dataType variableName;
//数据类型 variableName;

For example:

int age;
double salary;
string name;

You can also initialize a variable at the time of declaration:

您还可以在声明时初始化变量:

int age = 25;
double salary = 50000.50;
string name = "Alice";

Variable Naming Conventions

变量命名约定

When naming variables, it’s essential to follow certain conventions to enhance code readability and maintainability:

命名变量时,必须遵循某些约定以提高代码的可读性和可维护性:

Meaningful Names: Choose descriptive names that indicate the purpose of the variable (e.g., customerAge, totalPrice).

有意义的名称:选择指示变量用途的描述性名称(例如,customerAge、totalPrice)。

Camel Case: Use camel case for variable names, where the first word is lowercase, and subsequent words start with uppercase letters (e.g., firstName, itemCount).

驼峰式大小写:对变量名称使用驼峰式大小写,其中第一个单词为小写,后续单词以大写字母开头(例如,firstName、itemCount)。

Avoid Reserved Keywords: Do not use reserved keywords (e.g., class, int, void) as variable names.

避免使用保留关键字:不要使用保留关键字(例如,class, int, void)作为变量名称。

Start with a Letter: Variable names must begin with a letter or an underscore, not a digit.

以字母开头:变量名称必须以字母或下划线开头,而不是数字。

No Spaces or Special Characters: Variable names should not contain spaces or special characters (except underscores).

无空格或特殊字符:变量名称不应包含空格或特殊字符(下划线除外)。

Constant Variables

常量变量

In addition to regular variables, C# allows you to define constants—variables whose values cannot be changed once set. To declare a constant, use the const keyword:

除了常规变量之外,C# 还允许您定义常量,即一旦设置其值就无法更改的变量。要声明常量,请使用 const 关键字:

const double PI = 3.14;

Constants are useful for defining values that remain unchanged throughout the execution of the program, enhancing code clarity.

常量可用于定义在整个程序执行过程中保持不变的值,从而提高代码的清晰度。

Type Inference with var

使用 var 进行类型推理

C# supports type inference through the var keyword, allowing the compiler to determine the variable’s type based on the assigned value. For example:

C# 通过 var 关键字支持类型推理,从而允许编译器根据分配的值确定变量的类型。例如:

var temperature = 36.6; 
// The compiler infers that temperature is of type double
// 编译器推断 temperature 的类型为 double

Using var can simplify code and improve readability, but it’s important to use it judiciously, as it may obscure the actual data type.

使用 var 可以简化代码并提高可读性,但明智地使用它很重要,因为它可能会掩盖实际的数据类型。

Type Conversion

类型转换

Sometimes, you may need to convert between different data types. C# provides several ways to perform type conversions:

有时,您可能需要在不同数据类型之间进行转换。C# 提供了几种执行类型转换的方法:

Implicit Conversion: The compiler automatically converts a smaller type to a larger type (e.g., int to double).

隐式转换:编译器会自动将较小的类型转换为较大的类型(例如,在t 中转换为 double)。

int number = 42;

double decimalNumber = number; 
// Implicit conversion
// 隐式转换

Explicit Conversion (Casting): This requires a cast operator to convert a larger type to a smaller type.

显式转换 (Casting):这需要强制转换运算符将较大的类型转换为较小的类型。

double pi = 3.14;

int wholeNumber = (int)pi; 
// Explicit conversion (casting)
// 显式转换 (强制转换)

Convert Class: The Convert class provides methods for converting between different types safely.

Convert 类:Convert 类提供了在不同类型之间安全转换的方法。

string numberString = "123";

int number = Convert.ToInt32(numberString); 
// Convert string to int
// 将字符串转换为 int

Parsing: For converting strings to other types, you can use the parsing methods provided by the respective types.

解析:要将字符串转换为其他类型,您可以使用相应类型提供的解析方法。

string numberString = "123";

int number = int.Parse(numberString); 
// Parsing string to int
// 将字符串解析为 int

Handling type conversion carefully is essential to avoid runtime errors, especially when converting between incompatible types.

仔细处理类型转换对于避免运行时错误至关重要,尤其是在不兼容的类型之间进行转换时。

Scope of Variables

变量范围

The scope of a variable determines its visibility and lifespan within the program. C# has different scopes for variables:

变量的范围决定了它在程序中的可见性和生命周期。C# 具有不同的变量范围:

Local Variables: Declared within a method or block, local variables are only accessible within that method or block.

局部变量:在方法或块中声明,局部变量只能在该方法或块中访问。

void MyMethod()
{
    int localVariable = 10; 
    // Accessible only within MyMethod
    // 只能在 MyMethod 中访问
}

Instance Variables: Declared within a class but outside any method, instance variables are accessible to all methods within the class. Each instance of the class has its own copy of these variables.

实例变量:在类中声明,但在任何方法之外,实例变量可供类中的所有方法访问。该类的每个实例都有自己的这些变量副本。

class MyClass
{
    int instanceVariable; 
    // Accessible within all methods of MyClass
    // 可在 MyClass 的所有方法中访问
}

Static Variables: Declared with the static keyword, static variables are shared across all instances of a class. They are accessible without creating an instance of the class.

静态变量:使用 static 关键字声明,静态变量在类的所有实例之间共享。无需创建 class.

class MyClass
{
    static int staticVariable; 
    // Shared across all instances
    // 在所有实例之间共享
}

Understanding variable scope is crucial for managing data effectively and preventing conflicts in your code.

了解变量范围对于有效管理数据和防止代码中的冲突至关重要。

Best Practices for Variable Management

变量管理的最佳实践

To write clean and maintainable code, consider the following best practices for managing variables:

要编写干净且可维护的代码,请考虑以下管理变量的最佳实践:

Limit Variable Scope: Declare variables as close as possible to their first use. This minimizes the chance of accidental misuse and keeps your code cleaner.

Limit Variable Scope:在变量首次使用时尽可能声明变量。这样可以最大程度地减少意外误用的可能性,并使代码更简洁。

Initialize Variables: Always initialize variables before use to avoid unpredictable behavior and runtime errors.

初始化变量:始终在使用前初始化变量,以避免不可预知的行为和运行时错误。

Use Meaningful Names: Choose variable names that accurately describe their purpose to enhance code readability.

使用有意义的名称:选择准确描述其用途的变量名称,以增强代码可读性。

Group Related Variables: Consider using arrays or classes to group related variables, which can make your code more organized.

对相关变量进行分组:考虑使用数组或类对相关变量进行分组,这可以使您的代码更有条理。

Avoid Magic Numbers: Instead of using hard-coded values in your code, define them as constants with descriptive names to improve maintainability.

避免使用幻数:不要在代码中使用硬编码值,而是将它们定义为具有描述性名称的常量,以提高可维护性。

By understanding data types and variables in C#, you will have a solid foundation for writing effective code. These concepts are essential for managing data, controlling program flow, and achieving the desired functionality in your applications. As you progress through this book, you will see how these principles are applied in various programming scenarios, allowing you to develop your skills and confidence as a C# programmer.

通过了解 C# 中的数据类型和变量,您将为编写有效的代码打下坚实的基础。这些概念对于管理数据、控制程序流和在应用程序中实现所需的功能至关重要。随着本书的学习,您将看到如何将这些原则应用于各种编程方案,从而使您能够培养作为 C# 程序员的技能和信心。

Chapter 4: Control Structures in C

第 4 章:C# 语言中的控制结构

Control Structures

控制结构

Control structures are essential components of programming that dictate the flow of execution in your code. They allow you to make decisions, execute certain blocks of code conditionally, and repeat tasks, thereby enabling you to implement logic and functionality in your programs. In C#, control structures can be broadly categorized into three main types: decision-making structures, looping structures, and branching structures. Understanding these control structures is crucial for building robust applications that can handle various scenarios and inputs.

控制结构是编程的重要组成部分,它决定了代码中的执行流程。它们允许您做出决策、有条件地执行某些代码块以及重复任务,从而使您能够在程序中实现逻辑和功能。在 C# 中,控制结构大致可分为三种主要类型:决策结构、循环结构和分支结构。了解这些控制结构对于构建能够处理各种场景和输入的健壮应用程序至关重要

Decision-Making Structures

决策结构

Decision-making structures enable the execution of certain blocks of code based on specific conditions. In C#, the primary decision-making structures are if, else if, else, and switch statements.

决策结构支持根据特定条件执行某些代码块。在 C# 中,主要决策结构是 if、else if、else 和 switch 语句。

The if Statement

if 声明

The if statement evaluates a condition and executes a block of code if the condition is true. Here’s the basic syntax:

if 语句计算条件,如果条件为 true,则执行代码块。以下是基本语法:

if (condition)
{
    // Code to execute if the condition is true
    // 如果条件为 true,则要执行的代码
}

Example:
示例:

int age = 20;
if (age >= 18)
{
    Console.WriteLine("You are an adult.");
}

In this example, the message "You are an adult." is displayed only if the age variable is 18 or older.

在此示例中,仅当 age 变量年满 18 岁时,才会显示消息 “You are an adult.”。

The else Statement

else 声明

The else statement provides an alternative block of code to execute if the condition in the if statement is false. Here’s the syntax:

else 语句提供了在 if 语句中的条件为 false 时要执行的替代代码块。语法如下:

if (condition)
{
    // Code if condition is true
    // 如果 condition 为 true的代码
}
else
{
    // Code if condition is false
    // 如果 condition 为 false的代码
}

Example:
示例:

int age = 16;
if (age >= 18)
{
    Console.WriteLine("You are an adult.");
}
else
{
    Console.WriteLine("You are not an adult.");
}

In this example, the program checks the age and displays the appropriate message based on whether the condition is true or false.

在此示例中,程序检查存在时间,并根据条件是 true 还是 false 显示相应的消息。

The else if Statement

else if 语句

You can chain multiple conditions together using the else if statement. This allows you to evaluate additional conditions if the previous ones are false.

您可以使用 else if 语句将多个条件链接在一起。这允许您评估其他条件(如果前面的条件为 false)。

Syntax:
语法:

if (condition1)
{
    // Code if condition1 is true
    // 如果 condition1 为 true的代码
}
else if (condition2)
{
    // Code if condition2 is true
    // 如果 condition2 为 true的代码
}
else
{
    // Code if none of the conditions are true
    // 如果所有条件都不成立的代码
}

Example:
示例:

int score = 85;
if (score >= 90)
{
    Console.WriteLine("Grade: A");
}
else if (score >= 80)
{
    Console.WriteLine("Grade: B");
}
else if (score >= 70)
{
    Console.WriteLine("Grade: C");
}
else
{
    Console.WriteLine("Grade: D");
}

In this example, the program evaluates the score and assigns a grade based on the conditions.
在此示例中,程序将评估分数并根据条件指定成绩。

The switch Statement

switch 语句

The switch statement provides a way to select one of many blocks of code to execute based on the value of a variable. It can be more readable than multiple if-else statements when dealing with numerous conditions.

switch 语句提供了一种根据变量的值选择要执行的多个代码块之一的方法。在处理众多条件时,它可能比多个 if-else 语句更具可读性。

Syntax:
语法:

switch (expression)
{
    case value1:
    // Code to execute if expression equals value1
    // 如果 expression 等于 value1,则要执行的代码
    break;
    case value2:
    // Code to execute if expression equals value2
    // 如果 expression 等于 value2,则要执行的代码
    break;
    default:
    // Code to execute if no case matches
    //在没有大小写匹配时要执行的代码
    break;
}

Example:
示例:

int dayOfWeek = 3;

switch (dayOfWeek)
{
    case 1:
    Console.WriteLine("Monday");
    break;
    case 2:
    Console.WriteLine("Tuesday");
    break;
    case 3:
    Console.WriteLine("Wednesday");
    break;
    case 4:
    Console.WriteLine("Thursday");
    break;
    case 5:
    Console.WriteLine("Friday");
    break;
    case 6:
    Console.WriteLine("Saturday");
    break;
    case 7:
    Console.WriteLine("Sunday");
    break;
    default:
    Console.WriteLine("Invalid day");
    break;
}

In this example, the program prints the name of the day corresponding to the dayOfWeek variable.

在此示例中,程序打印与 dayOfWeek 变量对应的日期名称。

Looping Structures

循环结构

Looping structures allow you to repeat a block of code multiple times, which is particularly useful for tasks that require iteration over collections or performing repetitive calculations. In C#, the primary looping structures are the for, while, and do-while loops.

循环结构允许您多次重复一个代码块,这对于需要迭代集合或执行重复计算的任务特别有用。在 C# 中,主要的循环结构是 for、while 和 do-while 循环。

The for Loop

for 循环

The for loop is used when the number of iterations is known beforehand. It consists of three components: initialization, condition, and iteration.

当事先知道迭代次数时,使用 for 循环。它由三个部分组成:初始化、条件和迭代。

Syntax:
语法:

for (initialization; condition; iteration)
{
    // Code to execute in each iteration
    // 在每次迭代中要执行的代码
}

Example:
示例:

for (int i = 0; i < 5; i++)
{
    Console.WriteLine("Iteration: " + i);
}

In this example, the loop will execute five times, printing the current iteration number each time.

在此示例中,循环将执行 5 次,每次打印当前迭代编号。

The while Loop

while 循环

The while loop continues executing as long as the specified condition is true. This loop is useful when the number of iterations is not known beforehand.

只要指定的条件为 true,while 循环就会继续执行。当事先不知道迭代次数时,此循环非常有用。

Syntax:
语法:

while (condition)
{
    // Code to execute while the condition is true
    // 在条件为 true 时要执行的代码
}

Example:
示例:

int count = 0;

while (count < 5)
{
    Console.WriteLine("Count: " + count);
    count++;
}

In this example, the loop continues until count reaches 5, printing the current count value in each iteration.

在此示例中,循环一直持续到 count 达到 5,并在每次迭代中打印当前计数值。

The do-while Loop

do-while 循环

The do-while loop is similar to the while loop, but it guarantees that the block of code will be executed at least once, as the condition is evaluated after the code block.

do-while 循环类似于 while 循环,但它保证代码块至少执行一次,因为条件是在代码块之后计算的。

Syntax:
语法:

do
{
    // Code to execute
    // 要执行的代码
} while (condition);

Example:
示例:

int number = 0;

do
{
    Console.WriteLine("Number: " + number);
    number++;
} while (number < 5);

In this example, the loop executes and prints the current number, then increments it until it reaches 5.
在此示例中,循环执行并打印当前数字,然后递增它直到达到 5。

Branching Structures

分支结构

Branching structures allow you to alter the flow of control in your program based on certain conditions. They enable you to skip certain parts of your code or exit loops prematurely.

分支结构允许您根据特定条件更改程序中的控制流。它们使您能够跳过代码的某些部分或提前退出循环。

The break Statement

break 声明

The break statement is used to exit a loop or a switch statement prematurely. When the break statement is encountered, the control exits the loop or switch, and execution continues with the next statement following the loop or switch.

break 语句用于提前退出 loop 或 switch 语句。 当遇到 break 语句时,控制器退出 loop 或 switch,并继续执行 loop 或 switch 之后的下一条语句。

Example:

for (int i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break; 
        // Exit the loop when i is 5
        // 当i是5时退出循环
    }
    Console.WriteLine("i: " + i);
}

In this example, the loop will stop executing once i reaches 5.

在此示例中,一旦 i 达到 5,循环将停止执行。

The continue Statement

continue 语句

The continue statement skips the current iteration of a loop and moves to the next iteration. When continue is encountered, the rest of the code inside the loop for that iteration is skipped.

continue 语句跳过循环的当前迭代并移动到下一个迭代。遇到 continue 时,将跳过该迭代的循环内的其余代码。

Example:
示例:

for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0)
    {
        continue; 
        // Skip even numbers
        // 跳过偶数
    }
    Console.WriteLine("i: " + i);
}

In this example, only odd numbers will be printed, as the continue statement skips the even numbers.

在此示例中,将只打印奇数,因为 continue 语句会跳过偶数。

Nested Control Structures

嵌套控制结构

Control structures can be nested within one another, allowing for complex logic to be implemented. For example, you can have a for loop inside an if statement or a while loop inside a switch statement.

控制结构可以相互嵌套,从而允许实现复杂的逻辑。例如,您可以在 if 语句中有一个 for 循环,或者在 switch 语句中有一个 while 循环。

Example:
示例:

for (int i = 1; i <= 3; i++)
{
    Console.WriteLine("Outer loop iteration: " + i);
    for (int j = 1; j <= 2; j++)
    {
        Console.WriteLine("  Inner loop iteration: " + j);
    }
}

In this example, the inner loop executes twice for each iteration of the outer loop, demonstrating how nesting works.

在此示例中,内部循环对外部循环的每次迭代执行两次,演示了嵌套的工作原理。

Best Practices for Control Structures

控制结构的最佳实践

To write clear and maintainable code, consider the following best practices when using control structures:

要编写清晰且可维护的代码,请在使用控制结构时考虑以下最佳实践:

Keep Conditions Simple: Aim for readability in your conditions. Complex conditions can make the code difficult to understand.

保持条件简单:以您的条件的可读性为目标。复杂的条件会使代码难以理解。

Limit Nesting Levels: Excessive nesting can make your code hard to read. If you find yourself nesting multiple levels deep, consider refactoring your code into separate methods.

限制嵌套级别:过多的嵌套会使代码难以阅读。如果您发现自己嵌套了多个级别,请考虑将代码重构为单独的方法。

Use Comments: Document complex logic with comments to explain the purpose of control structures, especially when the logic may not be immediately obvious to others (or yourself in the future).

使用注释:用注释记录复杂的逻辑,以解释控制结构的目的,特别是当逻辑对其他人(或将来的自己)来说可能不会立即显而易见时。

Consistent Indentation: Use consistent indentation and formatting for control structures to improve readability. This helps you and others quickly grasp the structure and flow of your code.

一致的缩进:对控制结构使用一致的缩进和格式以提高可读性。这有助于您和其他人快速掌握代码的结构和流程。

Avoid Magic Numbers: Instead of using literal numbers in conditions, define them as constants with meaningful names. This improves readability and maintainability.

避免使用幻数:不要在条件中使用文字数字,而是将它们定义为具有有意义名称的常量。这提高了可读性和可维护性。

In summary, control structures are fundamental to programming in C#. They allow you to implement decision-making logic, iterate over data, and manage the flow of execution in your applications. Mastering control structures is essential for writing efficient and effective code.

总之,控制结构是 C# 编程的基础。它们允许您实施决策逻辑、迭代数据并管理应用程序中的执行流程。掌握控制结构对于编写高效和有效的代码至关重要。

Chapter 5: Working with Data Structures in C

第 5 章:在 C# 语言中使用数据结构

Data Structures

数据结构

Data structures are fundamental concepts in programming that allow you to organize, manage, and store data efficiently. Choosing the right data structure for a particular task can significantly affect the performance and clarity of your code. In C#, various built-in data structures are available, each designed to serve different needs and use cases. This chapter will explore the most commonly used data structures in C#, including arrays, lists, dictionaries, and more, as well as their advantages, disadvantages, and appropriate use cases.

数据结构是编程中的基本概念,可用于高效组织、管理和存储数据。为特定任务选择正确的数据结构会显著影响代码的性能和清晰度。在 C# 中,可以使用各种内置数据结构,每种结构都旨在满足不同的需求和用例。本章将探讨 C# 中最常用的数据结构,包括数组、列表、字典等,以及它们的优点、缺点和适当的用例。

Arrays

数组

Arrays are one of the simplest and most commonly used data structures in C#. They are collections of items stored at contiguous memory locations, allowing you to store multiple values of the same type in a single variable. Each item in an array can be accessed using its index, which starts from zero.

数组是 C# 中最简单和最常用的数据结构之一。它们是存储在连续内存位置的项的集合,允许您在单个变量中存储相同类型的多个值。数组中的每个项目都可以使用其索引(从 0 开始)进行访问。

Declaring and Initializing Arrays

声明和初始化数组

To declare an array, specify the type of elements it will hold, followed by square brackets. You can initialize an array either at the time of declaration or later.

要声明一个数组,请指定它将容纳的元素类型,后跟方括号。您可以在声明时或以后初始化数组。

Syntax:
语法:

dataType[] arrayName;
// dataType[] 数组名称;

Example:
示例:

int[] numbers = new int[5]; 
// Declares an array of integers with 5 elements
// 声明一个包含 5 个元素的整数数组

You can also initialize an array with values:
您还可以使用 values 初始化数组:

int[] numbers = { 1, 2, 3, 4, 5 }; 
// Declares and initializes an array
// 声明并初始化一个数组

Accessing Array Elements

访问 Array 元素

You can access individual elements in an array using their index:

您可以使用数组中的索引访问数组中的各个元素:

int firstNumber = numbers[0]; 
// Accesses the first element (1)
// 访问第一个元素 (1)

int secondNumber = numbers[1]; 
// Accesses the second element (2)
// 访问第二个元素 (2)

Modifying Array Elements

修改数组元素

Array elements can be modified by assigning a new value to a specific index:

可以通过为特定索引分配新值来修改数组元素:

numbers[0] = 10; 
// Changes the first element to 10
// 将第一个元素更改为 10

Limitations of Arrays

数组的限制

While arrays are useful, they come with certain limitations:
虽然数组很有用,但它们也有一些限制:

Fixed Size: Once an array is created, its size cannot be changed. This can lead to wasted space or the need to create new arrays if the size needs to grow.

固定大小:数组一旦创建,就无法更改其大小。这可能会导致空间浪费,或者如果大小需要增加,则需要创建新数组。

Homogeneous Elements: Arrays can only store elements of the same type, which may not be suitable for more complex data storage needs.

同构元素:数组只能存储相同类型的元素,这可能不适合更复杂的数据存储需求。

Lists

列表

Lists are more flexible than arrays and are part of the System.Collections.Generic namespace. They allow for dynamic resizing and can store elements of the same type. The List class provides many useful methods for managing collections of data.

列表比数组更灵活,并且是 System.Collections.Generic 命名空间的一部分。它们允许动态调整大小,并且可以存储相同类型的元素。 List 类提供了许多用于管理数据集合的有用方法。

Declaring and Initializing Lists

声明和初始化列表

To use lists, you must include the System.Collections.Generic namespace. You can declare and initialize a list as follows:

若要使用列表,必须包含 System.Collections.Generic 命名空间。您可以按如下方式声明和初始化列表:

Example:
示例:

using System.Collections.Generic;

List<int> numbers = new List<int>(); 
// Declares an empty list of integers
// 声明一个空的整数列表

You can also initialize a list with values:

您还可以使用 values 初始化列表:

List<string> names = new List<string> { "Alice", "Bob", "Charlie" }; 
// Declares and initializes a list
// 声明并初始化一个列表

Adding and Removing Elements

添加和删除元素

Lists provide methods for adding and removing elements:
列表提供了添加和删除元素的方法:

numbers.Add(10); 
// Adds 10 to the list
// 将 10 添加到列表中

numbers.Add(20); 
// Adds 20 to the list
// 将 20 添加到列表中

numbers.Remove(10); 
// Removes the first occurrence of 10 from the list
// 从列表中删除第一个出现的 10

Accessing List Elements

访问列表元素

Accessing elements in a list is similar to arrays, using the index:

访问列表中的元素类似于数组,使用索引:

string firstName = names[0]; 
// Accesses the first element ("Alice")
// 访问第一个元素 (“Alice”)

Iterating Through a List

遍历列表

You can easily iterate through a list using a foreach loop:

您可以使用 foreach 循环轻松迭代列表:

foreach (string name in names)
{
    Console.WriteLine(name);
}

Advantages of Lists

列表的优点

Dynamic Sizing: Lists can grow or shrink dynamically, making them more flexible than arrays.

动态大小调整:列表可以动态增长或缩小,使其比数组更灵活。

Rich Functionality: The List class provides a wide range of methods for searching, sorting, and manipulating data.

功能丰富:List 类提供了多种用于搜索、排序和作数据的方法。

Dictionaries

字典

Dictionaries are collections that store key-value pairs, allowing for fast retrieval of values based on their keys. They are part of the System.Collections.Generic namespace and are particularly useful for situations where you need to associate unique keys with values.

字典是存储键值对的集合,允许根据键快速检索值。它们是 System.Collections.Generic 命名空间的一部分,在需要将唯一键与值关联的情况下特别有用。

Declaring and Initializing Dictionaries

声明和初始化字典

You can declare a dictionary by specifying the types for the key and value:

您可以通过指定 key 和 value 的类型来声明字典:

Example:
示例:

using System.Collections.Generic;

Dictionary<string, int> ages = new Dictionary<string, int>(); 
// Declares an empty dictionary
// 声明一个空字典

You can also initialize a dictionary with values:

您还可以使用值初始化字典:

Dictionary<string, int> ages = new Dictionary<string, int>
{
    { "Alice", 25 },
    { "Bob", 30 },
    { "Charlie", 35 }
};

Adding and Accessing Elements

添加和访问元素

You can add elements to a dictionary using the key:

您可以使用 key 将元素添加到字典中:

ages["David"] = 40; 
// Adds a new key-value pair
// 添加新的键值对

To access a value based on its key:

要根据值的键访问值,请执行以下操作:

int aliceAge = ages["Alice"]; 
// Retrieves the value associated with "Alice"
// 检索与 “Alice” 关联的值

Checking for Keys

检查键

Before accessing a key, it’s good practice to check if it exists:

在访问密钥之前,最好检查它是否存在:

if (ages.ContainsKey("Bob"))
{
    Console.WriteLine("Bob's age: " + ages["Bob"]);
}

Iterating Through a Dictionary

遍历字典

You can iterate through a dictionary using a foreach loop:

您可以使用 foreach 循环遍历字典:

foreach (KeyValuePair<string, int> entry in ages)
{
    Console.WriteLine($"{entry.Key}: {entry.Value}");
}

Advantages of Dictionaries

词典的优点

Fast Lookups: Dictionaries provide fast access to values based on keys, making them suitable for applications that require quick data retrieval.

快速查找:字典提供对基于键的值的快速访问,使其适用于需要快速数据检索的应用程序。

Flexible Key Types: You can use any data type as a key, as long as it is unique.

灵活的密钥类型: 您可以使用任何数据类型作为密钥,只要它是唯一的。

Other Data Structures

其他数据结构

Besides arrays, lists, and dictionaries, C# provides other data structures that serve specific needs:

除了数组、列表和字典之外,C# 还提供了满足特定需求的其他数据结构:

Queues: Queues are first-in, first-out (FIFO) collections, useful for scenarios where you want to process items in the order they were added.

队列:队列是先进先出 (FIFO) 集合,适用于您希望按添加顺序处理项目的场景。

Example:
示例:

Queue<string> queue = new Queue<string>();

queue.Enqueue("First");
queue.Enqueue("Second");

string firstInLine = queue.Dequeue(); 
// Removes and retrieves the first item
// 删除并检索第一项

Stacks: Stacks are last-in, first-out (LIFO) collections, ideal for scenarios where you need to keep track of the most recently added items.

堆栈:堆栈是后进先出 (LIFO) 集合,非常适合需要跟踪最近添加的项目的情况。

Example:
示例:

Stack<string> stack = new Stack<string>();

stack.Push("First");
stack.Push("Second");

string lastAdded = stack.Pop(); 
// Removes and retrieves the most recently added item
// 删除并检索最近添加的项目

HashSets: HashSets are collections that store unique elements and provide fast lookups. They are useful when you need to eliminate duplicates.

HashSets:HashSet 是存储唯一元素并提供快速查找的集合。当您需要消除重复项时,它们非常有用。

Example:
示例:

HashSet<int> numbers = new HashSet<int> { 1, 2, 3, 4 };

numbers.Add(2); 
// Will not add a duplicate
// 不会添加重复项

Choosing the Right Data Structure

选择正确的数据结构

When deciding which data structure to use, consider the following factors:

在决定使用哪种数据结构时,请考虑以下因素:

Data Type: Consider the type of data you need to store. Some structures are better suited for specific types of data.

数据类型:考虑您需要存储的数据类型。某些结构更适合特定类型的数据。

Access Patterns: Analyze how you will access and manipulate the data. If you need fast lookups, consider using a dictionary. If you require ordered access, lists or arrays might be better.

访问模式:分析您将如何访问和作数据。如果您需要快速查找,请考虑使用字典。如果需要有序访问,列表或数组可能会更好。

Performance: Different data structures have different performance characteristics. For example, accessing elements in an array is generally faster than in a list, but lists provide more flexibility.

性能:不同的数据结构具有不同的性能特征。例如,访问数组中的元素通常比访问列表中的元素更快,但列表提供了更大的灵活性。

Memory Usage: Consider the memory overhead associated with each data structure. Some structures may consume more memory due to their internal implementation.

内存使用情况:考虑与每个数据结构关联的内存开销。由于内部实现,某些结构可能会消耗更多内存。

Data structures are fundamental to effective programming in C#. Understanding the various data structures available, such as arrays, lists, and dictionaries, enables you to choose the right tools for your programming tasks. By leveraging the strengths of different data structures, you can create efficient, clear, and maintainable code that meets the needs of your applications. As you continue your journey in C#, mastering data structures will empower you to tackle increasingly complex programming challenges and build robust software solutions.

数据结构是在 C# 中进行有效编程的基础。了解各种可用的数据结构(例如数组、列表和字典)使您能够为编程任务选择合适的工具。通过利用不同数据结构的优势,您可以创建满足应用程序需求的高效、清晰且可维护的代码。随着您继续 C# 之旅,掌握数据结构将使您能够应对日益复杂的编程挑战并构建强大的软件解决方案。

Chapter 6: Object-Oriented Programming in C

第 6 章:C# 语言中的面向对象编程

Object-Oriented Programming

面向对象编程

Object-Oriented Programming (OOP) is a programming paradigm centered around the concept of objects, which are instances of classes. OOP promotes organized and modular code, making it easier to manage complexity, enhance reusability, and facilitate maintenance. C# is a fully object-oriented language, and understanding its principles is essential for building robust applications. This chapter delves into the core concepts of OOP, including classes, objects, inheritance, polymorphism, encapsulation, and abstraction.

面向对象编程 (OOP) 是一种以对象概念为中心的编程范式,对象是类的实例。OOP 促进了有组织和模块化的代码,使其更容易管理复杂性、增强可重用性并促进维护。C# 是一种完全面向对象的语言,了解其原理对于构建健壮的应用程序至关重要。本章深入探讨了 OOP 的核心概念,包括类、对象、继承、多态性、封装和抽象。

Understanding Classes and Objects

了解类和对象

At the heart of OOP are classes and objects. A class serves as a blueprint for creating objects, defining their properties and behaviors. An object is an instance of a class and represents a specific entity in your application.

OOP 的核心是类和对象。类是创建对象、定义其属性和行为的蓝图。对象是类的实例,表示应用程序中的特定实体。

Defining a Class

定义类

In C#, a class is defined using the class keyword, followed by the class name and a body enclosed in curly braces. Inside the class, you can define fields (attributes) and methods (functions).

在 C# 中,类是使用 class 关键字定义的,后跟类名和用大括号括起来的主体。在类中,您可以定义字段 (属性) 和方法 (函数)。

Example:
示例:

public class Car
{
    // Fields
    // 字段
    public string Make;
    public string Model;
    public int Year;

    // Method
    // 方法
    public void DisplayInfo()
    {
        Console.WriteLine($"Car: {Year} {Make} {Model}");
    }

}

In this example, the Car class has three fields and a method that displays the car's information.

在此示例中,Car类具有三个字段和一个显示汽车信息的方法。

Creating Objects

创建对象

Once a class is defined, you can create objects (instances) of that class using the new keyword.

定义类后,可以使用 new 关键字创建该类的对象 (实例)。

Example:
示例:

Car myCar = new Car(); 
// Creates a new Car object
// 创建新的 Car 对象

myCar.Make = "Toyota";

myCar.Model = "Corolla";

myCar.Year = 2020;

myCar.DisplayInfo(); 
// Outputs: Car: 2020 Toyota Corolla
// 输出:汽车:2020 丰田卡罗拉

Here, we create an instance of the Car class, set its properties, and call its method.

在这里,我们创建 Car 类的实例,设置其属性,并调用其方法。

Encapsulation

封装

Encapsulation is the concept of restricting access to certain components of an object and bundling the data (fields) and methods that operate on that data within a single unit. This protects the internal state of an object from unintended interference and misuse.

封装是限制对对象某些组件的访问并将对该数据进行作的数据(字段)和方法捆绑在单个单元中的概念。这可以保护对象的内部状态免受意外干扰和误用。

Access Modifiers

访问修饰符

C# provides access modifiers to control the visibility of class members:

C# 提供了访问修饰符来控制类成员的可见性:

public: Accessible from anywhere.
public:可从任何位置访问。

private: Accessible only within the same class.
private:只能在同一个类中访问。

protected: Accessible within the same class and by derived classes.
protected:可在同一类中访问,也可由派生类访问。

internal: Accessible within the same assembly.
internal:可在同一程序集中访问。

Example:
示例:

public class BankAccount
{
    private decimal balance; 
    // Encapsulated field
    // 封装字段

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        return balance;
    }
}

In this example, the balance field is private, ensuring it can only be modified through the Deposit method, thus maintaining control over how it is accessed and modified.

在此示例中,balance 字段是私有的,确保它只能通过 Deposit 方法进行修改,从而保持对访问和修改方式的控制。

Inheritance

继承

Inheritance allows a class to inherit properties and methods from another class, promoting code reuse and establishing a hierarchical relationship between classes. The class that is inherited from is called the base class (or parent class), while the class that inherits is called the derived class (or child class).

继承允许一个类从另一个类继承属性和方法,从而促进代码重用并在类之间建立分层关系。继承自的类称为基类(或父类),而继承的类称为派生类(或子类)。

Creating a Derived Class

创建派生类

In C#, you define a derived class using the : syntax, followed by the base class name.

在 C# 中,使用 : 语法定义派生类,后跟基类名称。

Example:
示例:

public class ElectricCar : Car
{
    public int BatteryCapacity; 
    // Additional field for derived class
    // 派生类的附加字段

    public void DisplayBatteryInfo()
    {
        Console.WriteLine($"Battery Capacity: {BatteryCapacity} kWh");
    }
}

In this example, ElectricCar inherits from the Car class, gaining access to its fields and methods while also adding new functionality.

在此示例中,ElectricCar 继承自 Car 类,可以访问其字段和方法,同时还添加了新功能。

Using Inheritance

使用继承

You can create an instance of the derived class and access members from both the derived and base classes:

可以创建派生类的实例,并从派生类和基类访问成员:

ElectricCar myElectricCar = new ElectricCar();

myElectricCar.Make = "Tesla";
myElectricCar.Model = "Model S";
myElectricCar.Year = 2021;
myElectricCar.BatteryCapacity = 100;

myElectricCar.DisplayInfo(); 
// Outputs: Car: 2021 Tesla Model S
// 输出:汽车:2021 年特斯拉 Model S

myElectricCar.DisplayBatteryInfo(); 
// Outputs: Battery Capacity: 100 kWh
// 输出:电池容量:100 kWh

Polymorphism

多态性

Polymorphism is the ability for different classes to be treated as instances of the same class through a common interface. It allows methods to be defined in a base class and overridden in derived classes, enabling dynamic method resolution at runtime.

多态性是指通过公共接口将不同类视为同一类的实例的能力。它允许在基类中定义方法并在派生类中重写方法,从而在运行时实现动态方法解析。

Method Overriding

方法覆盖

To enable polymorphism, you can override methods in a derived class using the virtual keyword in the base class and the override keyword in the derived class.

若要启用多态性,可以使用基类中的 virtual 关键字和派生类中的 override 关键字覆盖派生类中的方法。

Example:
示例:

public class Vehicle
{
    public virtual void Start()
    {
        Console.WriteLine("Vehicle starting...");
    }
}

public class Motorcycle : Vehicle
{
    public override void Start()
    {
        Console.WriteLine("Motorcycle starting with a roar!");
    }
}

In this example, the Start method is defined in the Vehicle class and overridden in the Motorcycle class.

在此示例中,Start 方法在 Vehicle 类中定义,并在 Motorcycle 类中重写。

Using Polymorphism

使用多态性

You can reference derived class objects as their base class type, allowing for dynamic behavior:

您可以将派生类对象引用为其基类类型,从而允许动态行为:

Vehicle myVehicle = new Motorcycle();

myVehicle.Start(); 
// Outputs: Motorcycle starting with a roar!
// 输出:摩托车在轰鸣声中发动!

This demonstrates polymorphism, where myVehicle behaves according to the actual object type it references at runtime.

这演示了多态性,其中 myVehicle 的行为取决于它在运行时引用的实际对象类型。

Abstraction

抽象化

Abstraction is the concept of exposing only the relevant details of an object while hiding its complex implementation. This simplifies the interaction with objects and reduces complexity by focusing on high-level operations.

抽象是仅公开对象的相关细节,同时隐藏其复杂实现的概念。这简化了与对象的交互,并通过专注于高级作来降低复杂性。

Abstract Classes

抽象类

You can create abstract classes that cannot be instantiated directly but can contain abstract methods (methods without an implementation). Derived classes must implement these methods.

您可以创建不能直接实例化但可以包含抽象方法(没有实现的方法)的抽象类。派生类必须实现这些方法。

Example:
示例:

public abstract class Shape
{
    public abstract double CalculateArea(); 
    // Abstract method
    // 抽象方法
}

public class Rectangle : Shape
{
    public double Width;
    public double Height;

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

In this example, Shape is an abstract class, and Rectangle implements the abstract method CalculateArea.

在此示例中,Shape 是一个抽象类,而 Rectangle 实现抽象方法 CalculateArea。

Interfaces

接口

Interfaces define a contract that classes can implement, specifying methods and properties without providing implementations. They allow for greater flexibility and promote the use of multiple inheritance.

接口定义类可以实现的协定,指定方法和属性,而不提供实现。它们允许更大的灵活性,并促进了多重继承的使用。

Example:
示例:

public interface IDriveable
{
    void Drive();
}

public class Truck : IDriveable
{
    public void Drive()
    {
        Console.WriteLine("Truck is driving.");
    }
}

In this example, the IDriveable interface defines a method that the Truck class implements.

在此示例中,IDriveable 接口定义 Truck 类实现的方法。

Summary of OOP Principles

OOP 原则总结

Encapsulation: Bundles data and methods, restricting access to internal state.

封装:捆绑数据和方法,限制对内部状态的访问。

Inheritance: Enables classes to inherit characteristics from other classes.

继承:使类能够从其他类继承特征。

Polymorphism: Allows methods to be overridden in derived classes, enabling dynamic behavior.

多态性:允许在派生类中重写方法,从而实现动态行为。

Abstraction: Hides complex implementation details and exposes only relevant aspects.

抽象:隐藏复杂的实现细节,只公开相关方面。

Best Practices for OOP in C

C# 语言中 OOP 的最佳实践

Use Meaningful Class and Method Names: Choose names that clearly represent the purpose and functionality of your classes and methods.

使用有意义的类和方法名称:选择能够清楚地表示类和方法的用途和功能的名称。

Keep Classes Focused: Follow the Single Responsibility Principle by ensuring each class has a specific purpose and responsibility.

保持类专注:遵循单一责任原则,确保每个类都有特定的目的和责任。

Encapsulate Fields: Use properties to control access to class fields, providing getter and setter methods for encapsulation.

封装字段:使用属性控制对类字段的访问,提供用于封装的 getter 和 setter 方法。

Favor Composition over Inheritance: Consider using composition (building classes with other classes) instead of relying solely on inheritance to reduce complexity.

支持组合而不是继承:考虑使用组合(与其他类一起构建类)而不是仅依赖继承来降低复杂性。

Document Your Code: Use comments and documentation to explain the purpose and usage of classes, methods, and properties.

记录您的代码:使用注释和文档来解释类、方法和属性的用途和用法。

Object-Oriented Programming is a powerful paradigm that enables developers to create modular, maintainable, and reusable code. By understanding and applying the principles of OOP in C#, you can design robust applications that effectively model real-world entities and behaviors. Mastering these concepts will not only enhance your coding skills but also prepare you for more advanced programming challenges as you continue your journey in software development.

面向对象编程是一种强大的范例,使开发人员能够创建模块化、可维护和可重用的代码。通过了解和应用 C# 中的 OOP 原则,您可以设计出强大的应用程序,以有效地对现实世界的实体和行为进行建模。掌握这些概念不仅可以提高您的编码技能,还可以在您继续软件开发之旅时为更高级的编程挑战做好准备。

Chapter 7: Exception Handling in C

第 7 章:C# 语言中的异常处理

Exception Handling

异常处理

In any software application, errors and unexpected situations can occur at runtime. Exception handling is a mechanism in programming that allows developers to gracefully manage these errors, ensuring that the application remains stable and continues to operate as intended. In C#, exceptions are represented as objects and are part of a structured hierarchy that allows developers to categorize and manage errors effectively. This chapter covers the fundamental concepts of exception handling in C#, including types of exceptions, the use of try-catch blocks, finally clauses, and best practices for handling exceptions.

在任何软件应用程序中,运行时都可能发生错误和意外情况。异常处理是编程中的一种机制,它允许开发人员正常管理这些错误,确保应用程序保持稳定并继续按预期运行。在 C# 中,异常表示为对象,并且是结构化层次结构的一部分,允许开发人员有效地对错误进行分类和管理。本章介绍 C# 中异常处理的基本概念,包括异常类型、try-catch 块的使用、finally 子句以及处理异常的最佳实践。

Understanding Exceptions

了解异常

An exception is an event that disrupts the normal flow of a program's execution. It can occur due to various reasons, such as invalid user input, file not found errors, network failures, or arithmetic errors like division by zero. When an exception occurs, it can lead to application crashes if not handled properly.

异常是中断程序执行的正常流程的事件。这可能是由于各种原因造成的,例如无效的用户输入、找不到文件错误、网络故障或算术错误(如除以零)。发生异常时,如果处理不当,可能会导致应用程序崩溃。

Types of Exceptions

异常类型

In C#, exceptions are derived from the System.Exception class. There are two main types of exceptions:

在 C# 中,异常派生自 System.Exception 类。异常主要有两种类型:

System Exceptions: These are predefined exceptions in the .NET Framework that occur due to runtime errors. Examples include:
系统异常:这些是由于运行时错误而发生的 .NET Framework 中的预定义异常。示例包括:

NullReferenceException: Occurs when trying to access an object that is null.
NullReferenceException:尝试访问为 null 的对象时发生。

IndexOutOfRangeException: Occurs when trying to access an array with an index that is outside its bounds.
IndexOutOfRangeException:尝试访问索引超出其边界的数组时发生。

DivideByZeroException: Occurs when trying to divide a number by zero.
DivideByZeroException:尝试将数字除以零时发生。

Application Exceptions: These are exceptions defined by developers. They can be created by deriving from the ApplicationException class or directly from the System.Exception class. These exceptions allow developers to signal application-specific errors.
应用程序异常:这些是开发人员定义的异常。可以通过从 ApplicationException 类派生或直接从 System.Exception 类创建它们。这些异常允许开发人员发出特定于应用程序的错误信号。

Using Try-Catch Blocks

使用 Try-Catch 块

The primary mechanism for handling exceptions in C# is the try-catch block. A try block contains code that might throw an exception, while the catch block contains code that handles the exception.

在 C# 中处理异常的主要机制是 try-catch 块。 try 块包含可能引发异常的代码,而 catch 块包含处理异常的代码。

Basic Try-Catch Structure

基本 try-catch 结构

Here is a simple example demonstrating the use of a try-catch block:

下面是一个演示 try-catch 块用法的简单示例:

try
{
    int numerator = 10;
    int denominator = 0;

    int result = numerator / denominator; 
    // This will throw an exception
    // 这将引发异常
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

In this example, if the code in the try block throws a DivideByZeroException, the program will not crash. Instead, control will transfer to the catch block, where the exception is handled, and a message is displayed.

在此示例中,如果 try 块中的代码引发 DivideByZeroException,则程序不会崩溃。相反,控制权将转移到 catch 块,在那里处理异常,并显示一条消息。

Catching Multiple Exceptions

捕获多个异常

You can catch multiple exceptions by adding additional catch blocks. This allows for different handling strategies depending on the type of exception.

您可以通过添加其他 catch 块来捕获多个异常。这允许根据异常的类型使用不同的处理策略。

try
{
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[5]); 
    // This will throw an exception
    // 这将引发异常
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Index Error: {ex.Message}");
}
catch (Exception ex) 
    // This will catch any other exceptions
    // 这将捕获任何其他异常
{
    Console.WriteLine($"General Error: {ex.Message}");
}

Using Finally Blocks

使用 Finally 块

A finally block can be added after the catch block to execute code that must run regardless of whether an exception occurred or not. This is useful for cleaning up resources, such as closing file handles or database connections.

可以在 catch 块之后添加一个 finall y 块,以执行无论是否发生异常都必须运行的代码。这对于清理资源(例如关闭文件句柄或数据库连接)非常有用。

Example:
示例:

FileStream fileStream = null;

try
{
    fileStream = new FileStream("data.txt", FileMode.Open);
    // Perform file operations
    // 执行文件作
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
finally
{
    if (fileStream != null)
    {
        fileStream.Close(); 
        // Ensure the file is closed
        // 确保文件已关闭
    }
}

In this example, the finally block ensures that the file stream is closed regardless of whether an exception occurred during file operations.

在此示例中,finally 块确保文件流关闭,无论文件作期间是否发生异常。

Throwing Exceptions

引发异常

You can throw exceptions explicitly using the throw keyword. This is useful for signaling errors in your code or when you want to enforce certain conditions.

可以使用 throw 关键字显式引发异常。这对于在代码中发出错误信号或要强制实施某些条件非常有用。

Example:
示例:

public void ValidateAge(int age)
{
    if (age < 0)
    {
        throw new ArgumentOutOfRangeException("Age cannot be negative.");
    }
}

In this example, if the age parameter is negative, an ArgumentOutOfRangeException is thrown, which can be caught and handled by the calling code.

在此示例中,如果 age 参数为负数,则会引发 ArgumentOutOfRangeException,调用代码可以捕获并处理。

Creating Custom Exceptions

创建自定义例外

Sometimes, built-in exceptions do not provide sufficient context for application-specific errors. In such cases, you can create custom exceptions by deriving from the System.Exception class.

有时,内置异常无法为特定于应用程序的错误提供足够的上下文。在这种情况下,您可以通过从 System.Exception 类派生来创建自定义异常。

Example:
示例:

public class InvalidTransactionException : Exception
{
    public InvalidTransactionException() { }
    public InvalidTransactionException(string message)
    : base(message) { }

    public InvalidTransactionException(string message, Exception inner)
    : base(message, inner) { }
}

This custom exception class can then be used in your application to signal specific errors related to transactions.

然后,可以在应用程序中使用此自定义异常类来指示与事务相关的特定错误。

Best Practices for Exception Handling

异常处理的最佳实践

Catch Specific Exceptions: Always catch specific exceptions before catching more general exceptions. This helps in diagnosing issues more accurately.
捕获特定异常:在捕获更常规的异常之前,始终捕获特定异常。这有助于更准确地诊断问题。

Avoid Swallowing Exceptions: Do not catch exceptions without handling them or logging their details. Swallowing exceptions can make debugging difficult.
避免吞噬异常:请勿在未处理异常或未记录其详细信息的情况下捕获异常。吞噬异常可能会使调试变得困难。

Use Finally Blocks for Resource Cleanup: Use finally blocks to ensure resources are released appropriately, even in the event of an exception.
使用 Finally 块进行资源清理:使用 finally 块来确保资源得到适当释放,即使在发生异常时也是如此。

Log Exceptions: Implement logging mechanisms to log exception details, including stack traces and error messages. This can greatly assist in troubleshooting.
记录异常:实施日志记录机制以记录异常详细信息,包括堆栈跟踪和错误消息。这可以极大地帮助进行故障排除。

Re-throw Exceptions When Necessary: If you catch an exception but cannot handle it adequately, consider re-throwing it using the throw; statement to propagate it up the call stack.
必要时重新引发异常:如果捕获到异常但无法充分处理它,请考虑使用 throw; 语句重新引发它,以将其向上传播到调用堆栈中。

Use Custom Exceptions Judiciously: Create custom exceptions only when necessary. Ensure they add value to your error-handling strategy.
明智地使用自定义例外:仅在必要时创建自定义例外。确保它们为您的错误处理策略增加价值。

Avoid Exception-Based Control Flow: Exceptions should be reserved for exceptional conditions, not for regular control flow. This helps maintain performance and clarity in your code.
避免基于异常的控制流:异常应保留给异常情况,而不是常规控制流。这有助于保持代码的性能和清晰度。

Summary of Exception Handling Concepts

异常处理概念摘要

Exception Types: Understand the difference between system and application exceptions.
异常类型:了解系统异常和应用程序异常之间的区别。

Try-Catch Blocks: Use try-catch blocks to handle exceptions gracefully.
try-catch 块:使用 try-catch 块正常处理异常。

Finally Blocks: Ensure code execution with finally blocks, particularly for resource cleanup.
Finally 块:确保使用 finally 块执行代码,特别是对于资源清理。

Throwing Exceptions: Use the throw keyword to signal errors explicitly.
引发异常:使用 throw 关键字显式发出错误信号。

Custom Exceptions: Create custom exceptions to handle application-specific errors effectively.
自定义异常:创建自定义异常以有效处理特定于应用程序的错误。

Exception handling is a critical aspect of robust software development in C#. By understanding the mechanics of exceptions and employing best practices, you can ensure that your applications handle errors gracefully and maintain a high level of stability. As you continue to develop your skills in C#, mastering exception handling will empower you to write more resilient and maintainable code, ultimately leading to better user experiences and more successful software solutions.

异常处理是 C# 中可靠软件开发的一个关键方面。通过了解异常的机制并采用最佳实践,您可以确保您的应用程序能够正常处理错误并保持高水平的稳定性。随着您不断提高 C# 技能,掌握异常处理将使您能够编写更具弹性和可维护性的代码,最终带来更好的用户体验和更成功的软件解决方案。

Chapter 8: Working with Collections in C

第 8 章:在 C# 语言中使用集合

Collections

集合

In C#, collections are specialized data structures that allow you to store, manage, and manipulate groups of related objects. Collections simplify the management of data by providing powerful built-in methods for adding, removing, and accessing elements. C# provides several collection types, including arrays, lists, dictionaries, queues, and stacks, each tailored for specific scenarios. This chapter explores the various types of collections available in C#, their use cases, and how to effectively leverage them in your applications.

在 C# 中,集合是专用的数据结构,可用于存储、管理和作相关对象组。集合通过提供强大的内置方法来添加、删除和访问元素,从而简化了数据管理。C# 提供了多种集合类型,包括数组、列表、字典、队列和堆栈,每种类型都针对特定方案进行了定制。本章探讨了 C# 中可用的各种类型的集合、它们的用例,以及如何在应用程序中有效地利用它们。

Understanding Collections

了解集合

Collections in C# are part of the System.Collections and System.Collections.Generic namespaces. The primary distinction between these namespaces is that the generic collections provide type safety and better performance by allowing you to specify the type of elements they will contain.

C# 中的集合是 System.Collection和 System.Collections.Generic 命名空间的一部分。这些命名空间之间的主要区别在于,泛型集合允许您指定它们将包含的元素类型,从而提供类型安全性和更好的性能。

Types of Collections

集合类型

Arrays: The simplest form of collection, arrays are fixed-size data structures that store a sequence of elements of the same type.
数组:最简单的集合形式,数组是固定大小的数据结构,用于存储一系列相同类型的元素。

Lists: The List class represents a dynamically sized collection of objects that can be accessed by index. It is part of the System.Collections.Generic namespace and offers flexibility in terms of resizing.
列表:List 类表示可通过索引访问的动态大小的对象集合。它是 System.Collections.Generic 命名空间的一部分,在调整大小方面提供了灵活性。

Dictionaries: The Dictionary<TKey, TValue> class is a collection of key-value pairs, allowing you to efficiently look up values based on their keys. It is also part of the generic collections.
字典:Dictionary<TKey, TValue> 类是键值对的集合,允许您根据键有效地查找值。它也是通用集合的一部分。

Queues: The Queue class represents a first-in, first-out (FIFO) collection of objects. It is useful when you need to process elements in the order they were added.
队列:Queue 类表示对象的先进先出 (FIFO) 集合。当您需要按元素的添加顺序处理元素时,它非常有用。

Stacks: The Stack class represents a last-in, first-out (LIFO) collection of objects, which is useful for situations where you need to retrieve the most recently added element first.
Stacks:Stack 类表示对象的后进先出 (LIFO) 集合,这在需要首先检索最近添加的元素的情况下非常有用。

Working with Arrays

使用数组

Arrays are the foundational collection type in C#. They have a fixed size that must be specified at the time of creation.

数组是 C# 中的基础集合类型。它们具有固定大小,必须在创建时指定。

Declaring and Initializing Arrays

声明和初始化数组

You can declare and initialize an array in several ways:
您可以通过多种方式声明和初始化数组:

Example:
示例:

// Declaring an array
// 声明数组
int[] numbers = new int[5]; // Array of size 5

// Initializing an array
// 初始化数组
string[] fruits = { "Apple", "Banana", "Cherry" };

// Accessing array elements
// 访问数组元素
Console.WriteLine(fruits[0]); // Outputs: Apple

Manipulating Arrays

操作数组

Arrays can be manipulated using various methods, such as sorting, searching, and iterating over elements.

可以使用各种方法作数组,例如排序、搜索和迭代元素。

Example:
示例:

Array.Sort(fruits); // Sorts the array in ascending order

foreach (string fruit in fruits)
{
    Console.WriteLine(fruit); 
    // Outputs each fruit
    // 输出每个水果
}

Working with Lists

使用列表

The List class is a more flexible alternative to arrays. It allows dynamic resizing and provides many useful methods.

List 类是数组的更灵活的替代方法。它允许动态调整大小并提供许多有用的方法。

Creating and Initializing a List

创建和初始化列表

You can create a list and add elements to it easily.

您可以轻松创建列表并向其添加元素。

Example:
示例:

List<int> numbersList = new List<int>();

numbersList.Add(1);
numbersList.Add(2);
numbersList.Add(3);

// Initializing a list with values
// 使用值初始化列表
List<string> cities = new List<string> { "New York", "London", "Tokyo" };

Accessing and Modifying List Elements

访问和修改列表元素

Lists allow you to access elements by index and modify them as needed.
列表允许您按索引访问元素并根据需要修改它们。

Example:
示例:


Console.WriteLine(cities[1]); 
// Outputs: London

cities[2] = "Paris"; 
// Modifying an element
// 修改元素

Common List Operations

常见操作

The List class provides several useful methods for managing the collection:
List 类提供了几种用于管理集合的有用方法:

Removing Elements:

删除元素:

cities.Remove("New York"); 
// Removes the specified city

Finding Elements:

查找元素:

int index = cities.IndexOf("Tokyo"); // Returns the index of the specified city

Sorting a List:

对列表进行排序:

cities.Sort(); 
// Sorts the list alphabetically

Working with Dictionaries

使用词典

Dictionaries are collections that store key-value pairs, allowing for efficient retrieval based on a key.
字典是存储键值对的集合,允许基于键进行高效检索。

Creating and Initializing a Dictionary

创建和初始化字典

You can create a dictionary and populate it with key-value pairs.
您可以创建字典并使用键值对填充它。

Example:
示例:

Dictionary<string, int> ages = new Dictionary<string, int>
{
    { "Alice", 30 },
    { "Bob", 25 },
    { "Charlie", 35 }
};

Accessing Dictionary Elements

访问 Dictionary 元素

You can access values using their associated keys.
您可以使用值的关联键访问值。

Example:
示例:

int aliceAge = ages["Alice"]; 
// Retrieves Alice's age

Console.WriteLine($"Alice's age: {aliceAge}"); 
// Outputs: Alice's age: 30

Common Dictionary Operations

常用操作

Dictionaries offer various methods to manipulate the collection:
词典提供了多种方法来作集合:

Adding Key-Value Pairs:

添加键值对:

ages.Add("David", 28); 
// Adds a new entry

Removing Entries:

删除条目:

ages.Remove("Bob"); 
// Removes Bob from the dictionary

Checking for Keys:

检查键:

bool hasCharlie = ages.ContainsKey("Charlie"); 
// Checks if Charlie exists

Working with Queues

使用队列

Queues are useful for scenarios that require processing items in the order they were added (FIFO).

队列对于需要按添加顺序 (FIFO) 处理项目的方案非常有用。

Creating and Initializing a Queue

创建和初始化队列

You can create a queue and add elements to it.
您可以创建队列并向其添加元素。

Example:
示例:

Queue<string> queue = new Queue<string>();

queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");

Accessing Queue Elements

访问 Queue 元素

You can retrieve elements from a queue using the Dequeue method, which removes and returns the oldest element.
您可以使用 Dequeue 方法从队列中检索元素,该方法删除并返回最早的元素。

Example:
示例:

string firstItem = queue.Dequeue(); 
// Retrieves and removes the first item

Console.WriteLine(firstItem); 
// Outputs: First

Checking the Queue

检查队列

You can also check the front item without removing it using the Peek method:

您还可以使用 Peek 方法检查前项而不将其删除:

string nextItem = queue.Peek(); 
// Retrieves the next item without removing it

Console.WriteLine(nextItem); 
// Outputs: Second

Working with Stacks

使用堆栈

Stacks are collections that follow the LIFO principle, where the last element added is the first one retrieved.
堆栈是遵循 LIFO 原则的集合,其中最后添加的元素是检索的第一个元素。

Creating and Initializing a Stack

创建和初始化堆栈

You can create a stack and add elements to it using the Push method.
您可以使用 Push 方法创建堆栈并向其添加元素。

Example:
示例:

Stack<string> stack = new Stack<string>();

stack.Push("First");
stack.Push("Second");
stack.Push("Third");

Accessing Stack Elements

访问 Stack Elements

You can retrieve the most recently added element using the Pop method, which removes and returns it.

您可以使用 Pop 方法检索最近添加的元素,该方法会删除并返回该元素。

Example:
示例:

string lastItem = stack.Pop(); 
// Retrieves and removes the last item

Console.WriteLine(lastItem); 
// Outputs: Third

Checking the Stack

检查堆栈

You can check the top item without removing it using the Peek method:
您可以使用 Peek 方法检查顶部项目而无需将其删除:

string topItem = stack.Peek(); 
// Retrieves the top item without removing it
// 检索顶部项目而不删除它

Console.WriteLine(topItem); 
// Outputs: Second

Summary of Collection Types

集合类型摘要

Arrays: Fixed-size collections for storing homogeneous elements.
数组:用于存储同质元素的固定大小的集合。

Lists: Dynamically sized collections with powerful methods for manipulation.
列表:动态大小的集合,具有强大的作方法。

Dictionaries: Key-value pairs for efficient lookups.
字典:用于高效查找的键值对。

Queues: FIFO collections for sequential processing.
Queues:用于顺序处理的 FIFO 集合。

Stacks: LIFO collections for managing recent items.
Stacks:用于管理最近项目的 LIFO 集合。

Best Practices for Working with Collections

使用集合的最佳实践

Choose the Right Collection Type: Select the most appropriate collection based on your use case. For example, use a dictionary for fast lookups or a list for ordered collections.
选择正确的集合类型:根据您的使用案例选择最合适的集合。例如,使用字典进行快速查找,或使用列表进行有序集合。

Avoid Performance Bottlenecks: Be aware of the performance characteristics of each collection type, especially in scenarios involving large datasets.
避免性能瓶颈:请注意每种集合类型的性能特征,尤其是在涉及大型数据集的情况下。

Use Generics: Favor generic collections (like List, Dictionary<TKey, TValue>) over non-generic ones for better type safety and performance.
使用泛型:优先使用泛型集合(如 List、Dictionary<TKey、TValue>),而不是非泛型集合,以获得更好的类型安全性和性能。

Use LINQ for Data Manipulation: The Language Integrated Query (LINQ) provides a powerful syntax for querying and manipulating collections, improving code readability.
使用 LINQ 进行数据作:语言集成查询 (LINQ) 为查询和作集合提供了强大的语法,从而提高了代码的可读性。

Properly Dispose of Resources: If your collection contains objects that implement IDisposable, ensure that you dispose of them properly to free resources.
正确处置资源:如果您的集合包含实现 IDisposable 的对象,请确保正确处置这些对象以释放资源。

Understanding how to work with collections in C# is essential for effective programming. Collections provide powerful tools for managing groups of related objects, enabling you to build efficient and maintainable applications. By mastering the different types of collections, their characteristics, and the best practices for their use, you can enhance your programming skills and develop more sophisticated software solutions. As you continue to explore C#, collections will play a crucial role in your ability to manipulate and manage data effectively.

了解如何在 C# 中使用集合对于有效编程至关重要。集合提供了用于管理相关对象组的强大工具,使您能够构建高效且可维护的应用程序。通过掌握不同类型的集合、它们的特性及其使用的最佳实践,您可以提高编程技能并开发更复杂的软件解决方案。随着您继续探索 C#,集合将在您有效作和管理数据的能力中发挥至关重要的作用。

Chapter 9: Working with LINQ in C

第 9 章:使用LINQ

LINQ

Language Integrated Query (LINQ) is a powerful feature in C# that allows developers to query and manipulate data from various sources using a consistent syntax. LINQ integrates seamlessly with C# and provides a unified way to work with collections, databases, XML, and more. This chapter explores the key concepts of LINQ, its syntax, and how to effectively use it to simplify data querying and manipulation.

语言集成查询 (LINQ) 是 C# 中的一项强大功能,它允许开发人员使用一致的语法查询和作来自各种来源的数据。LINQ 与 C# 无缝集成,并提供一种处理集合、数据库、XML 等的统一方式。本章探讨了 LINQ 的关键概念、语法以及如何有效地使用它来简化数据查询和作。

Understanding LINQ

了解 LINQ

LINQ provides a set of methods and query syntax that allows developers to express queries in a more readable and concise manner. It can be used with any data source that implements the IEnumerable or IQueryable interface, making it versatile for different types of data.

LINQ 提供了一组方法和查询语法,使开发人员能够以更易读、更简洁的方式表达查询。它可以与实现 IEnumerable 或 IQueryable 接口的任何数据源一起使用,使其适用于不同类型的数据。

Benefits of LINQ

LINQ 的优势

Readability: LINQ queries are often easier to read and understand compared to traditional loops and conditional statements.
可读性:与传统循环和条件语句相比,LINQ 查询通常更易于阅读和理解。

Consistency: LINQ provides a consistent querying experience across different data sources.
一致性:LINQ 提供跨不同数据源的一致查询体验。

Strongly Typed Queries: Because LINQ queries are integrated with C#, they benefit from compile-time checking, reducing runtime errors.
强类型查询:由于 LINQ 查询与 C# 集成,因此它们受益于编译时检查,从而减少运行时错误。

Declarative Syntax: LINQ allows you to express what you want to retrieve rather than how to retrieve it, improving code clarity.
声明性语法:LINQ 允许您表达要检索的内容,而不是如何检索它,从而提高代码的清晰度。

LINQ Syntax

LINQ 语法

LINQ supports two primary syntaxes: query syntax and method syntax. Both syntaxes can be used interchangeably based on developer preference.

LINQ 支持两种主要语法:查询语法和方法语法。这两种语法可以根据开发人员的偏好互换使用。

Query Syntax

查询语法

Query syntax resembles SQL and is often more readable for those familiar with database queries.

查询语法类似于 SQL,对于熟悉数据库查询的人来说,通常更具可读性。

Example:
示例:

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = from num in numbers
        where num % 2 == 0
        select num;

In this example, evenNumbers is a collection of even numbers selected from the numbers list.

在此示例中,evenNumbers 是从数字s 列表中选择的偶数的集合。

Method Syntax

方法语法

Method syntax uses extension methods to perform operations on collections.

方法语法使用扩展方法对集合执行作。
示例:
Example:

var evenNumbers = numbers.Where(num => num % 2 == 0);

This accomplishes the same result as the previous example, using the Where method to filter even numbers.

这将获得与上一个示例相同的结果,即使用 Where 方法筛选偶数。

Common LINQ Operators

常见的 LINQ 运算符

LINQ provides a rich set of operators for querying and manipulating data. Some of the most commonly used operators include:

LINQ 提供了一组丰富的运算符,用于查询和作数据。一些最常用的运算符包括:

Where: Filters a sequence based on a predicate.
Where:根据谓词筛选序列。

var filteredNumbers = numbers.Where(n => n > 3);

Select: Projects each element of a sequence into a new form.
Select:将序列的每个元素投影到新表单中。

var squaredNumbers = numbers.Select(n => n * n);

OrderBy: Sorts the elements of a sequence in ascending order.
OrderBy:按升序对序列的元素进行排序。

var sortedNumbers = numbers.OrderBy(n => n);

GroupBy: Groups elements of a sequence based on a specified key.
GroupBy:根据指定的键对序列的元素进行分组。

var groupedByEvenOdd = numbers.GroupBy(n => n % 2 == 0 ? "Even" : "Odd");

Join: Joins two sequences based on a common key.
Join:根据公共键连接两个序列。

var joinedData = from person in people
    join order in orders on person.Id equals order.PersonId
    select new { person.Name, order.Amount };

ToList: Converts a sequence into a list.
ToList:将序列转换为列表。

var numberList = filteredNumbers.ToList();

Working with LINQ to Objects

使用 LINQ to Objects

LINQ to Objects allows you to query in-memory collections, such as arrays and lists. This is a common use case for LINQ.

LINQ to Objects 允许您查询内存中的集合,例如数组和列表。这是 LINQ 的常见用例。

Example: Querying a List of Objects
示例:查询对象列表

Assuming you have a class representing a person:
假设你有一个表示 person 的类:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

You can use LINQ to filter and select properties from a list of Person objects.

您可以使用 LINQ 从 Person 对象列表中筛选和选择属性。

var people = new List<Person>
{
    new Person { Name = "Alice", Age = 30 },
    new Person { Name = "Bob", Age = 25 },
    new Person { Name = "Charlie", Age = 35 }
};

var adults = from person in people
    where person.Age >= 30
    select person.Name;

foreach (var name in adults)
{
    Console.WriteLine(name); 
    // Outputs: Alice, Charlie
}

Working with LINQ to SQL

使用 LINQ to SQL

LINQ to SQL allows you to query databases using LINQ syntax. It translates LINQ queries into SQL queries, enabling seamless interaction with relational databases.

LINQ to SQL 允许您使用 LINQ 语法查询数据库。它将 LINQ 查询转换为 SQL 查询,从而实现与关系数据库的无缝交互。

Setting Up LINQ to SQL

设置 LINQ to SQL

To use LINQ to SQL, you typically create a DataContext that represents your database. Here's how you might set it up:

要使用 LINQ to SQL,您通常会创建一个表示数据库的 DataContext。以下是设置方法:

Create a Database Model: Define your database schema in C# classes.
创建数据库模型:在 C# 类中定义数据库架构。

Generate DataContext: Use Visual Studio's Entity Framework tools or manually define a DataContext class.
生成 DataContext:使用 Visual Studio 的 Entity Framework 工具或手动定义 DataContext 类。

Example:
示例:

public class MyDataContext : DataContext
{
    public Table<Person> People;
    public MyDataContext(string connectionString) : base(connectionString) { }
}

Querying with LINQ to SQL

使用 LINQ to SQL 进行查询

You can perform LINQ queries against the DataContext.
您可以对 DataContext 执行 LINQ 查询。

using (var context = new MyDataContext("your_connection_string"))
{
var adultNames = from person in context.People
    where person.Age >= 30
    select person.Name;
foreach (var name in adultNames)
{
    Console.WriteLine(name);
}
}

Working with LINQ to XML

使用 LINQ to XML

LINQ to XML provides a convenient way to query and manipulate XML documents. This is useful for applications that interact with XML data sources.

LINQ to XML 提供了一种查询和作 XML 文档的便捷方法。这对于与 XML 数据源交互的应用程序非常有用。

Loading XML Documents

加载 XML 文档

You can load XML from a file or string using XDocument or XElement.
您可以使用 XDocument 或 XElement 从文件或字符串加载 XML。

Example:
示例:

XDocument xmlDoc = XDocument.Load("data.xml");

Querying XML with LINQ

使用 LINQ 查询 XML

You can use LINQ to query XML elements and attributes.

您可以使用 LINQ 查询 XML 元素和属性。

var names = from person in xmlDoc.Descendants("Person")
    where (int)person.Element("Age") >= 30
    select person.Element("Name").Value;

foreach (var name in names)
{
    Console.WriteLine(name);
}

Best Practices for Using LINQ

使用 LINQ 的最佳实践

Use Deferred Execution: LINQ queries are not executed until you iterate over them. This allows for optimized performance but be cautious about modifying the source collection during iteration.
Use Deferred Execution:在您迭代 LINQ 查询之前,不会执行 LINQ 查询。这允许优化性能,但在迭代期间修改源集合时要小心。

Prefer Method Syntax for Complex Queries: For more complex queries involving multiple operations, method syntax can be clearer and more concise.
复杂查询首选方法语法:对于涉及多个作的更复杂的查询,方法语法可以更清晰、更简洁。

Leverage LINQ with Asynchronous Programming: Use asynchronous LINQ methods when querying databases or external data sources to avoid blocking the main thread.
将 LINQ 与异步编程结合使用:在查询数据库或外部数据源时使用异步 LINQ 方法,以避免阻塞主线程。

Optimize for Performance: Be mindful of performance implications when working with large data sets. Use operators like Take and Skip to manage data efficiently.
优化性能:在处理大型数据集时,请注意性能影响。使用 Take 和 Skip 等运算符来有效地管理数据。

Avoid Side Effects in Queries: Keep LINQ queries free of side effects (modifying external state) to maintain clarity and avoid unintended behavior.
避免查询中的副作用:使 LINQ 查询没有副作用(修改外部状态),以保持清晰性并避免意外行为。

Summary of LINQ Concepts

LINQ 概念摘要

Unified Querying: LINQ allows you to query various data sources using a consistent syntax.
统一查询:LINQ 允许您使用一致的语法查询各种数据源。

Query and Method Syntax: Both query and method syntax can be used based on preference and readability.
查询和方法语法:可以根据首选项和可读性使用查询和方法语法。

Common Operators: LINQ provides operators like Where, Select, OrderBy, and GroupBy for data manipulation.
常用运算符:LINQ 提供了 Where、Select、OrderBy 和 GroupBy 等运算符,用于数据作。

LINQ to Objects and LINQ to SQL: LINQ can be used with in-memory collections and SQL databases, enhancing versatility.
LINQ to Objects 和 LINQ to SQL:LINQ 可以与内存中集合和 SQL 数据库一起使用,从而增强多功能性。

LINQ to XML: Provides easy querying and manipulation of XML data.
LINQ to XML:提供对 XML 数据的轻松查询和作。

LINQ is a powerful feature in C# that greatly simplifies data querying and manipulation. By mastering LINQ, you can write cleaner, more readable code that interacts effectively with various data sources. As you continue to develop your skills in C#, integrating LINQ into your applications will enhance your ability to work with data efficiently and effectively.
LINQ 是 C# 中的一项强大功能,可大大简化数据查询和作。通过掌握 LINQ,您可以编写更简洁、更具可读性的代码,以便与各种数据源进行有效交互。随着您不断提高 C# 技能,将 LINQ 集成到您的应用程序中将增强您高效处理数据的能力。

Chapter 10: Asynchronous Programming in C

第 10 章:C# 语言中的异步编程

Asynchronous Programming

异步编程

Asynchronous programming is a powerful technique that allows your applications to perform tasks without blocking the main thread. This is particularly important in scenarios where tasks can take a long time to complete, such as file I/O, network requests, or database operations. By leveraging asynchronous programming, you can create responsive applications that maintain performance and improve user experience. This chapter explores the fundamental concepts of asynchronous programming in C#, including the async and await keywords, tasks, and best practices for implementing asynchronous code.

异步编程是一种强大的技术,它允许您的应用程序在不阻塞主线程的情况下执行任务。这在任务可能需要很长时间才能完成的情况下(例如文件 I/O、网络请求或数据库作)尤其重要。通过利用异步编程,您可以创建响应式应用程序,以保持性能并改善用户体验。本章探讨了 C# 中异步编程的基本概念,包括 async 和 await 关键字、任务以及实现异步代码的最佳实践。

Understanding Asynchronous Programming

了解异步编程

In traditional synchronous programming, each operation must complete before the next one begins. This can lead to unresponsive applications, particularly in user interface scenarios. Asynchronous programming allows tasks to run concurrently, enabling the application to continue executing other code while waiting for long-running operations to complete.

在传统的同步编程中,每个作必须在下一个作开始之前完成。这可能会导致应用程序无响应,尤其是在用户界面场景中。异步编程允许任务并发运行,使应用程序能够在等待长时间运行的作完成的同时继续执行其他代码。

Key Concepts

关键概念

Threading: Asynchronous programming is often implemented using multiple threads. While the main thread handles user interactions, background threads can perform long-running tasks.
线程:异步编程通常使用多个线程实现。主线程处理用户交互,而后台线程可以执行长时间运行的任务。

Task-based Asynchronous Pattern (TAP): In C#, asynchronous operations are commonly represented using the Task class. This pattern provides a more manageable and flexible way to handle asynchronous work compared to previous approaches.
基于任务的异步模式 (TAP):在 C# 中,异步作通常使用 Task 类表示。与以前的方法相比,此模式提供了一种更易于管理、更灵活的方式来处理异步工作。

The Async and Await Keywords

async 和 await 关键字

The async and await keywords are central to asynchronous programming in C#. They simplify the process of writing asynchronous code and make it more readable.

async 和 await 关键字是 C# 中异步编程的核心。它们简化了编写异步代码的过程,使其更具可读性。

Using the Async Keyword

使用 async 关键字

To define an asynchronous method, use the async modifier in the method signature. This tells the compiler that the method will contain asynchronous operations.

要定义异步方法,请在方法签名中使用 async 修饰符。这会告诉编译器该方法将包含异步作。

Example:
示例:

public async Task<string> GetDataAsync()
{
    // Simulate an asynchronous operation
    // 模拟异步作
    await Task.Delay(1000);
    return "Data retrieved";
}

In this example, GetDataAsync is an asynchronous method that simulates a delay using Task.Delay. The method returns a Task to indicate that it will eventually produce a string result.

在此示例中,GetDataAsync 是使用 Task.Delay 模拟延迟的异步方法。该方法返回 Task 以指示它最终将生成字符串结果。

Using the Await Keyword

使用 await 关键字

The await keyword is used to call an asynchronous method. It pauses the execution of the method until the awaited task is complete, allowing other code to run in the meantime.

await 关键字用于调用异步方法。它会暂停方法的执行,直到等待的任务完成,从而允许其他代码同时运行。

Example:
示例:

public async Task ExecuteAsync()
{
    string result = await GetDataAsync();
    Console.WriteLine(result); 
    // Outputs: Data retrieved
    // 输出:检索到的数据
}

In this example, ExecuteAsync awaits the completion of GetDataAsync, allowing the application to remain responsive during the delay.

在此示例中, ExecuteAsync 等待 GetDataAsync 完成,从而允许应用程序在延迟期间保持响应。

Working with Tasks

使用任务

The Task class represents an asynchronous operation. It provides methods and properties to manage and retrieve the results of asynchronous operations.

Task 类表示异步作。它提供用于管理和检索异步作结果的方法和属性。

Creating and Starting Tasks

创建和启动任务

You can create and start tasks using the Task.Run method or by instantiating a Task directly.

您可以使用 Task.Run 方法或通过直接实例化 Task 来创建和启动任务。

Example:
示例:

Task<string> task = Task.Run(() =>
{
    // Simulate a long-running operation
    模拟长时间运行的作
    Thread.Sleep(2000);
    return "Task completed";
});

In this example, a new task is created to perform a long-running operation, simulating a delay.

在此示例中,将创建一个新任务来执行长时间运行的作,以模拟延迟。

Handling Task Results

处理任务结果

To retrieve the result of a task, you can use the Result property, but be cautious as this can lead to deadlocks if used incorrectly.

要检索任务的结果,您可以使用 Result 属性,但要小心,因为如果使用不当,这可能会导致死锁。

Example:
示例:

string result = task.Result; 
// Blocks until the task completes
// 在任务完成之前阻止

Console.WriteLine(result); 
// Outputs: Task completed
// 输出:任务已完成

For non-blocking retrieval, prefer using await.
对于非阻塞检索,首选使用 await。

Error Handling in Asynchronous Methods

异步方法中的错误处理

Error handling in asynchronous methods is slightly different from synchronous code. When an exception occurs in an asynchronous method, it is captured in the returned Task.

异步方法中的错误处理与同步代码略有不同。当异步方法中发生异常时,该异常将在返回的 Task 中捕获。

Catching Exceptions

捕获异常

You can catch exceptions from asynchronous methods using try-catch blocks around await.

您可以使用 await 周围的 try-catch 块从异步方法中捕获异常。

Example:
示例:

public async Task SafeExecuteAsync()
{
    try
    {
        string result = await GetDataAsync();
        Console.WriteLine(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
}

In this example, if GetDataAsync throws an exception, it will be caught and handled gracefully.
在此示例中,如果 GetDataAsync 引发异常,则会捕获并正常处理该异常。

Task Continuations

任务延续

You can create task continuations to specify actions that should occur after a task completes, whether it succeeds or fails. This can be done using the ContinueWith method.

您可以创建任务延续来指定任务完成后应执行的作,无论任务是成功还是失败。这可以使用 ContinueWith 方法完成。

Example:
示例:


Task.Run(() =>
{
    // Simulate work
    // 模拟工作
    Thread.Sleep(1000);
    return "Continuing work";
})
    .ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            Console.WriteLine("Error occurred.");
        }
        else
        {
            Console.WriteLine(task.Result); 
            // Outputs: Continuing work
            // 输出:持续工作
        }
    });

In this example, the continuation task checks if the original task faulted and handles the result accordingly.

在此示例中,延续任务检查原始任务是否出错并相应地处理结果。

Best Practices for Asynchronous Programming

异步编程的最佳实践

Use Async All the Way: When calling asynchronous methods, ensure that the entire call chain is asynchronous to avoid blocking the main thread.
全程异步:调用异步方法时,确保整个调用链都是异步的,以避免阻塞主线程。

Avoid Blocking Calls: Avoid using .Result or .Wait() on tasks, as this can lead to deadlocks. Instead, use await.
避免阻止调用:避免使用 .Result 或 .Wait(),因为这可能会导致死锁。相反,请使用 await。

Keep Asynchronous Methods Lightweight: Asynchronous methods should be designed to perform short tasks or I/O-bound work, rather than CPU-bound work. For CPU-bound tasks, consider using Task.Run to offload work to a background thread.
保持异步方法轻量级:异步方法应设计为执行短期任务或 I/O 密集型工作,而不是 CPU 密集型工作。对于 CPU 密集型任务,请考虑使用 Task.Run 将工作卸载到后台线程。

Use Cancellation Tokens: Implement cancellation in asynchronous methods using CancellationToken to allow users to cancel long-running operations gracefully.
使用取消令牌:使用 CancellationToken 在异步方法中实现取消,以允许用户正常取消长时间运行的作。

Gracefully Handle Exceptions: Ensure robust error handling in asynchronous methods to deal with exceptions effectively.
优雅地处理异常:确保在异步方法中进行强大的错误处理,以有效处理异常。

Optimize for Performance: Be mindful of the performance implications of asynchronous programming, especially in high-throughput applications.
优化性能:请注意异步编程对性能的影响,尤其是在高吞吐量应用程序中。

Summary of Asynchronous Programming Concepts

异步编程概念摘要

Asynchronous Programming: Allows for non-blocking execution, improving application responsiveness.
异步编程:允许非阻塞执行,从而提高应用程序响应能力。

Async and Await: Simplifies writing asynchronous code while maintaining readability.
Async 和 Await:简化异步代码的编写,同时保持可读性。

Tasks: Represents asynchronous operations, providing methods for managing and retrieving results.
任务:表示异步作,提供管理和检索结果的方法。

Error Handling: Use try-catch blocks around awaited tasks to handle exceptions effectively.
错误处理:在等待的任务周围使用 try-catch 块来有效地处理异常。

Best Practices: Follow guidelines to ensure efficient and effective use of asynchronous programming.
最佳实践: 遵循准则以确保高效和有效地使用异步编程。

Asynchronous programming in C# is a crucial skill for developing responsive and high-performance applications. By understanding and applying the concepts of async, await, and tasks, you can enhance your applications' responsiveness and efficiency. As you continue to build your expertise in C#, mastering asynchronous programming will significantly improve your ability to handle I/O-bound tasks and create smoother user experiences.
C# 中的异步编程是开发响应式和高性能应用程序的关键技能。通过理解和应用 async、await 和 tasks 的概念,您可以提高应用程序的响应能力和效率。随着您继续积累 C# 专业知识,掌握异步编程将显著提高您处理 I/O 密集型任务的能力,并创建更流畅的用户体验。

Chapter 11: Exception Handling in C

第 11 章:C# 语言中的异常处理

Exception Handling

异常处理

Exception handling is a critical aspect of programming that allows developers to manage errors gracefully and maintain the stability of applications. In C#, exceptions are events that disrupt the normal flow of program execution, typically due to errors like invalid input, unavailable resources, or runtime errors. This chapter explores the principles of exception handling in C#, including how to throw, catch, and manage exceptions, as well as best practices for writing robust and maintainable code.

异常处理是编程的一个关键方面,它允许开发人员正常管理错误并保持应用程序的稳定性。在 C# 中,异常是中断程序执行正常流程的事件,通常是由于输入无效、资源不可用或运行时错误等错误造成的。本章探讨 C# 中异常处理的原则,包括如何引发、捕获和管理异常,以及编写可靠且可维护的代码的最佳实践。

Understanding Exceptions

了解异常

An exception is an unexpected event that occurs during program execution. C# uses a class-based system for exception handling, where all exceptions are derived from the base class System.Exception. When an exception occurs, it can be "thrown" and "caught" by appropriate exception handling constructs, allowing developers to take corrective actions or log the error.

异常是在程序执行期间发生的意外事件。C# 使用基于类的系统进行异常处理,其中所有异常都派生自基类 System.Exception。发生异常时,适当的异常处理构造可以“引发”和“捕获”异常,从而允许开发人员采取纠正措施或记录错误。

Common Types of Exceptions

常见类型的异常

SystemException: This is the base class for all predefined system exceptions, including ArgumentNullException, IndexOutOfRangeException, and InvalidOperationException.
SystemException:这是所有预定义系统异常的基类,包括 ArgumentNullException、IndexOutOfRangeException 和 InvalidOperationException。

ApplicationException: This is the base class for application-defined exceptions. It’s generally used to create custom exceptions specific to an application.
ApplicationException:这是应用程序定义的异常的基类。它通常用于创建特定于应用程序的自定义异常。

IOExceptions: These exceptions relate to input/output operations, such as file access issues or network errors.
IOExceptions:这些异常与输入/输出作有关,例如文件访问问题或网络错误。

Custom Exceptions: Developers can define their own exceptions by inheriting from System.Exception or ApplicationException.
自定义异常:开发人员可以通过从 System.Exception 或 ApplicationException 继承来定义自己的异常。

Throwing Exceptions

引发异常

In C#, you can throw exceptions using the throw statement. This is commonly done when a method detects an issue that it cannot handle.

在 C# 中,可以使用 throw 语句引发异常。当方法检测到它无法处理的问题时,通常会这样做。

Example of Throwing an Exception

引发异常的示例

public void ValidateInput(string input)

{
    if (string.IsNullOrEmpty(input))
    {
        throw new ArgumentNullException(nameof(input), "Input cannot be null or empty.");
    }
}

In this example, if the input is null or empty, an ArgumentNullException is thrown, providing feedback about the invalid input.
在此示例中,如果 input 为 null 或为空,则引发 ArgumentNullException,提供有关无效输入的反馈。

Catching Exceptions

捕获异常

C# provides the try, catch, and finally blocks for handling exceptions. You wrap the code that may throw an exception in a try block, and you handle the exception in the corresponding catch block.

C# 提供 try、catch 和 finally 块来处理异常。将可能引发异常的代码包装在 try 块中,并在相应的 catch 块中处理异常。

Basic Try-Catch Example

基本 try-catch 示例

try
{
    // Code that may throw an exception
    int result = 10 / int.Parse("0");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

In this example, a division by zero occurs, which is caught in the catch block, allowing the program to continue running without crashing.

在此示例中,发生被零除的情况,该除法被捕获在 catch 块中,从而允许程序继续运行而不会崩溃。

Multiple Catch Blocks

多个 Catch 块

You can have multiple catch blocks to handle different types of exceptions. This allows for more granular error handling based on the exception type.

您可以有多个 catch 块来处理不同类型的异常。这允许根据异常类型进行更精细的错误处理。

Example of Multiple Catch Blocks

多个 catch 块的示例

try
{
    // Code that may throw an exception
    string[] numbers = { "one", "two", "three" };
    int result = int.Parse(numbers[3]); // This will throw IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Index error: {ex.Message}");
}
catch (FormatException ex)
{
    Console.WriteLine($"Format error: {ex.Message}");
}

In this example, the specific catch blocks handle IndexOutOfRangeException and FormatException, providing tailored messages for each error type.

在此示例中,特定的 catch 块处理 IndexOutOfRangeException 和 FormatException,为每种错误类型提供定制的消息。

The Finally Block

Finally 块

The finally block is executed after the try and catch blocks, regardless of whether an exception was thrown or caught. It is typically used for cleanup operations, such as closing file handles or releasing resources.

您可以有多个 catch 块来处理不同类型的异常。这允许根据异常类型进行更精细的错误处理。

Example with Finally Block
多个 catch 块的示例

try
{
    // Code that may throw an exception
    using (StreamReader reader = new StreamReader("file.txt"))
    {
    string content = reader.ReadToEnd();
    }
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
finally
{
    Console.WriteLine("Execution complete.");
}

In this example, the finally block executes whether the file is found or not, ensuring that the cleanup code runs.

在此示例中,无论是否找到文件,finally 块都会执行,从而确保清理代码运行。

Throwing Exceptions in Catch Blocks

在 catch 块中引发异常

Sometimes, you may want to catch an exception and rethrow it for further handling. You can do this by using the throw keyword without an exception object.

有时,您可能希望捕获异常并重新引发它以进行进一步处理。您可以通过使用 throw 关键字(不带异常对象)来执行此作。

Example of Rethrowing Exceptions
重新引发异常的示例

try
{
    // Code that may throw an exception
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[5]);
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Caught an exception: {ex.Message}");
    throw; // Rethrow the caught exception
}

In this example, the caught exception is rethrown, allowing higher-level code to handle it or log it appropriately.

在此示例中,将重新引发捕获的异常,从而允许更高级别的代码处理该异常或适当地记录该异常。

Creating Custom Exceptions

创建自定义例外

Custom exceptions allow you to define specific error conditions for your applications. You can create a custom exception class by inheriting from System.Exception or ApplicationException.

自定义异常允许您为应用程序定义特定的错误条件。您可以通过从 System.Exception 或 ApplicationException 继承来创建自定义异常类。

Example of Custom Exception

自定义异常示例

public class InvalidUserInputException : ApplicationException
{
    public InvalidUserInputException(string message) : base(message) { }
}

You can then throw and catch this custom exception just like any other exception.

然后,您可以像任何其他异常一样引发和捕获此自定义异常。

try
{
    throw new InvalidUserInputException("User input is invalid.");
}
catch (InvalidUserInputException ex)
{
    Console.WriteLine($"Custom error: {ex.Message}");
}

Best Practices for Exception Handling

异常处理的最佳实践

Catch Specific Exceptions: Always catch the most specific exception type first. This allows for targeted error handling and prevents catching unrelated exceptions.
捕获特定异常:始终首先捕获最具体的异常类型。这允许有针对性地进行错误处理,并防止捕获不相关的异常。

Avoid Empty Catch Blocks: Do not catch exceptions without handling them. Empty catch blocks can hide errors and make debugging difficult.
避免空 catch 块:不要在不处理异常的情况下捕获异常。空的 catch 块可能会隐藏错误并使调试变得困难。

Use Finally for Cleanup: Always use the finally block for cleanup operations to ensure resources are released, regardless of whether an exception occurred.
使用 Finally 进行清理:始终使用 finally 块进行清理作,以确保释放资源,无论是否发生异常。

Log Exceptions: Always log exceptions to facilitate debugging. Include relevant information, such as stack traces and contextual data.
记录异常:始终记录异常以方便调试。包括相关信息,例如堆栈跟踪和上下文数据。

Use Custom Exceptions Sparingly: While custom exceptions can be useful, use them judiciously to avoid cluttering your code. Stick to built-in exceptions for common error scenarios whenever possible.
谨慎使用自定义异常:虽然自定义异常可能很有用,但请谨慎使用它们以避免代码混乱。尽可能为常见错误场景使用内置异常。

Validate Inputs Early: Perform input validation to prevent exceptions from occurring in the first place. This can reduce the need for exception handling in many cases.
尽早验证输入:执行输入验证,从一开始就防止异常发生。在许多情况下,这可以减少对异常处理的需求。

Fail Fast: If a method encounters a state it cannot handle, throw an exception as soon as possible. This makes it clear where the error occurred.
快速失败:如果方法遇到无法处理的状态,请尽快引发异常。这清楚地表明了错误发生的位置。

Summary of Exception Handling Concepts

异常处理概念摘要

Exceptions: Unexpected events that disrupt program execution, managed through throwing and catching.
异常:中断程序执行的意外事件,通过引发和捕获进行管理。

Throwing Exceptions: Use the throw statement to indicate errors or invalid states.
引发异常:使用 throw 语句指示错误或无效状态。

Catching Exceptions: Use try, catch, and finally blocks for error handling and resource cleanup.
捕获异常:使用 try、catch 和 finally 块进行错误处理和资源清理。

Custom Exceptions: Create specific exceptions for your application needs by inheriting from System.Exception.
自定义异常:通过从 System.Exception 继承,为您的应用程序需求创建特定的异常。

Best Practices: Implement exception handling best practices to write robust and maintainable code.
最佳实践:实施异常处理最佳实践以编写健壮且可维护的代码。

Exception handling is a vital skill for any C# developer. By understanding how to throw, catch, and manage exceptions effectively, you can write robust applications that gracefully handle errors and maintain performance. With the principles and best practices outlined in this chapter, you can enhance your programming toolkit and ensure that your applications are resilient to unexpected events. As you continue to develop your expertise in C#, mastering exception handling will significantly contribute to the quality and reliability of your code.

异常处理是任何 C# 开发人员的一项重要技能。通过了解如何有效地引发、捕获和管理异常,您可以编写强大的应用程序来正常处理错误并保持性能。使用本章中概述的原则和最佳实践,您可以增强您的编程工具包,并确保您的应用程序能够灵活应对意外事件。随着您继续发展 C# 方面的专业知识,掌握异常处理将大大有助于提高代码的质量和可靠性。

Chapter 12: Working with Collections in C

第 12 章:在 C# 语言中使用集合

Collections

集合

In C#, collections are a fundamental feature that provides a way to store, manage, and manipulate groups of objects. Unlike arrays, which are fixed in size, collections offer dynamic sizing and more versatile functionality, allowing developers to manage data more efficiently. This chapter delves into various types of collections available in C#, including lists, dictionaries, sets, and queues, while discussing their characteristics, use cases, and best practices for working with them.

在 C# 中,集合是一项基本功能,它提供了一种存储、管理和作对象组的方法。与大小固定的数组不同,集合提供动态大小调整和更通用的功能,使开发人员能够更高效地管理数据。本章深入探讨了 C# 中可用的各种类型的集合,包括列表、字典、集和队列,同时讨论了它们的特性、用例和使用它们的最佳实践。

Understanding Collections

了解集合

Collections in C# are part of the System.Collections and System.Collections.Generic namespaces. They encapsulate the logic for storing and accessing groups of objects, providing built-in functionality for adding, removing, and iterating over items. Collections can be categorized into two main types: non-generic collections and generic collections.

C# 中的集合是 System.Collections 和 System.Collections.Generic 命名空间的一部分。它们封装了用于存储和访问对象组的逻辑,提供了用于添加、删除和迭代项目的内置功能。集合可以分为两种主要类型:非泛型集合和泛型集合。

Non-Generic Collections

非泛型集合
Non-generic collections can hold any data type, but they do not provide type safety, meaning that you may encounter runtime errors if you attempt to store incompatible types. Common non-generic collections include:

非泛型集合可以保存任何数据类型,但它们不提供类型安全,这意味着如果尝试存储不兼容的类型,可能会遇到运行时错误。常见的非泛型集合包括:

ArrayList: A resizable array that can hold objects of any type.
ArrayList:一个可调整大小的数组,可以保存任何类型的对象。

Hashtable: A collection that stores key-value pairs but does not guarantee type safety.
Hashtable:存储键值对但不保证类型安全的集合。

Example of ArrayList:
ArrayList 示例:

ArrayList arrayList = new ArrayList();
arrayList.Add("Hello");
arrayList.Add(42);
arrayList.Add(DateTime.Now);

In this example, an ArrayList is created and populated with various types of objects.

在此示例中,将创建一个 ArrayList 并填充各种类型的对象。

Generic Collections

泛型集合

Generic collections, introduced in .NET 2.0, provide type safety and better performance by allowing developers to specify the data type when creating the collection. This eliminates the need for boxing and unboxing when dealing with value types. Common generic collections include:

.NET 2.0 中引入的泛型集合允许开发人员在创建集合时指定数据类型,从而提供类型安全性和更好的性能。这样就无需在处理值类型时进行装箱和取消装箱。常见的泛型集合包括:

List: A dynamically resizable array that can store elements of a specified type.
List:一个可动态调整大小的数组,可以存储指定类型的元素。

Dictionary<TKey, TValue>: A collection that stores key-value pairs, allowing for fast lookups by key.
Dictionary<TKey, TValue>:存储键值对的集合,允许按键快速查找。

HashSet: A collection that stores unique elements, providing fast set operations.
HashSet:存储唯一元素的集合,提供快速的集合作。

Queue: A first-in, first-out (FIFO) collection that allows you to enqueue and dequeue items.
Queue:一个先进先出 (FIFO) 集合,允许您将项目加入队列和取消排队。

Example of List:
List 示例:

List<string> stringList = new List<string>();
stringList.Add("Apple");
stringList.Add("Banana");

In this example, a List is created to hold string values, providing type safety.
在此示例中,创建了一个 List 来保存字符串值,从而提供类型安全性。

Working with List

使用 List

List is one of the most commonly used collections in C#. It provides a range of methods for manipulating a dynamic array of objects.

List 是 C# 中最常用的集合之一。它提供了一系列用于作对象的动态数组的方法。

Adding Elements

添加元素

You can add elements to a list using the Add method or AddRange to add multiple elements at once.

您可以使用 Add 方法或 AddRange 将元素添加到列表中,以一次添加多个元素。

Example:
示例:

List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.AddRange(new int[] { 3, 4, 5 });

Accessing Elements

访问元素

Elements in a list can be accessed using an index, similar to arrays. The index is zero-based.

列表中的元素可以使用索引进行访问,类似于数组。索引从 0 开始。

Example:
示例:

int firstNumber = numbers[0]; 
// Accesses the first element

Removing Elements

删除元素

You can remove elements using the Remove method, which removes the first occurrence of a specified value, or the RemoveAt method, which removes an element at a specific index.

可以使用 Remove 方法(删除指定值的第一个匹配项)或 RemoveAt 方法(删除特定索引处的元素)删除元素。

Example:
示例:

numbers.Remove(3); // Removes the first occurrence of 3
numbers.RemoveAt(0); // Removes the first element

Sorting and Searching

排序和搜索

Lists provide methods like Sort and Contains for sorting and searching through elements.

列表提供了 Sort 和 Contains 等方法,用于对元素进行排序和搜索。

Example:
示例:

numbers.Sort(); 
// Sorts the list

bool hasTwo = numbers.Contains(2); 
// Checks if the list contains the number 2
// 检查列表是否包含数字 2

Working with Dictionary<TKey, TValue>

使用 Dictionary<TKey, TValue>

The Dictionary<TKey, TValue> collection is a powerful tool for storing key-value pairs, enabling fast lookups, insertions, and deletions.

Dictionary<TKey, TValue> 集合是用于存储键值对的强大工具,可实现快速查找、插入和删除。

Adding Key-Value Pairs

添加键值对

You can add key-value pairs using the Add method, where each key must be unique.

您可以使用 Add 方法添加键值对,其中每个键都必须是唯一的。

Example:
示例:

Dictionary<string, int> ageDictionary = new Dictionary<string, int>();

ageDictionary.Add("Alice", 30);
ageDictionary.Add("Bob", 25);

Accessing Values

访问值

Values can be accessed using their associated keys. If the key does not exist, an exception will be thrown.

可以使用其关联的键访问值。如果 key 不存在,则会引发异常。

Example:
示例:

int aliceAge = ageDictionary["Alice"]; 
// Retrieves Alice's age
// 检索 Alice 的年龄

Removing Entries

删除条目

You can remove an entry using the Remove method, specifying the key of the entry to be removed.

您可以使用 Remove 方法删除条目,并指定要删除的条目的键。

Example:
示例:

ageDictionary.Remove("Bob"); 
// Removes Bob's entry

Iterating Over Dictionary

迭代 Dictionary

You can iterate over key-value pairs using a foreach loop.

您可以使用 foreach 循环迭代键值对。

Example:
示例:

foreach (var entry in ageDictionary)
{
    Console.WriteLine($"{entry.Key}: {entry.Value}");
}

Working with HashSet

使用 HashSet

HashSet is a collection designed to store unique elements. It is particularly useful for scenarios where you need to ensure that no duplicates exist.

HashSet 是一个旨在存储独特元素的集合。对于需要确保不存在重复项的方案,它特别有用。

Adding Elements

添加元素

You can add elements to a HashSet using the Add method. If you try to add a duplicate, it will simply be ignored.

您可以使用 Add 方法将元素添加到 HashSet。如果您尝试添加重复项,它将被忽略。

Example:
示例:

HashSet<string> fruitSet = new HashSet<string>();

fruitSet.Add("Apple");
fruitSet.Add("Banana");

fruitSet.Add("Apple"); 
// This will not be added again
// 这不会再次添加

Set Operations

HashSet provides methods for performing set operations like union, intersection, and difference.

HashSet 提供了执行集合运算的方法,如并集、交集和差集。

Example:
示例:

HashSet<string> otherSet = new HashSet<string> { "Banana", "Cherry" };

fruitSet.UnionWith(otherSet); 
// Adds elements from otherSet to fruitSet
// 将 otherSet 中的元素添加到 fruitSet

Working with Queue

使用 Queue

Queue is a first-in, first-out (FIFO) collection that allows you to enqueue items to the end of the queue and dequeue items from the front.

Queue 是一个先进先出 (FIFO) 集合,允许您将项目排在队列末尾,并从前面取消项目排队。

Enqueuing and Dequeuing

入队和出队

You can add items to the queue using the Enqueue method and remove items using the Dequeue method.

您可以使用 Enqueue 方法将项目添加到队列中,并使用 Dequeue 方法删除项目。

Example:
示例:

Queue<string> queue = new Queue<string>();

queue.Enqueue("First");
queue.Enqueue("Second");

string firstItem = queue.Dequeue(); 
// Removes and returns "First"
// 删除并返回 “First”

Working with Stack

使用 Stack

Stack is a last-in, first-out (LIFO) collection that allows you to push items onto the stack and pop items off the stack.

Stack 是一个后进先出 (LIFO) 集合,允许您将项目推到堆栈上,并将项目从堆栈中弹出。

Pushing and Popping

推挤和爆裂

You can add items to the stack using the Push method and remove the most recently added item using the Pop method.

您可以使用 Push 方法将项目添加到堆栈中,并使用 Pop 方法删除最近添加的项目。

Example:
示例:

Stack<int> stack = new Stack<int>();

stack.Push(1);
stack.Push(2);

int lastItem = stack.Pop(); 
// Removes and returns 2
// 删除并返回 2

Best Practices for Working with Collections

使用集合的最佳实践

Choose the Right Collection: Select the appropriate collection type based on the use case. Use List for ordered collections, Dictionary<TKey, TValue> for key-value pairs, and HashSet for unique items.

选择正确的集合:根据使用案例选择适当的集合类型。使用 List 进行有序集合,使用 Dictionary<TKey、TValue> 进行键值对,使用 HashSet 进行唯一项。

Use Generics: Prefer generic collections over non-generic ones for type safety and performance.
使用泛型:在类型安全性和性能方面,首选泛型集合而不是非泛型集合。

Avoid Modifying Collections While Iterating: Modifying a collection while iterating over it can lead to exceptions. If you need to modify the collection, consider creating a separate list of items to be removed or added.
避免在迭代时修改集合:在迭代集合时修改集合可能会导致异常。如果需要修改集合,请考虑创建要删除或添加的项目的单独列表。

Use Collection Initializers: C# supports collection initializers for creating and populating collections in a concise manner.
使用集合初始值设定项:C# 支持以简洁的方式创建和填充集合的初始值设定项。

Example:
示例:

List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };

Use LINQ for Querying Collections: Language Integrated Query (LINQ) provides a powerful syntax for querying and manipulating collections in a readable way.
使用 LINQ 查询集合:语言集成查询 (LINQ) 提供了一种强大的语法,用于以可读的方式查询和作集合。

Summary of Collections Concepts

集合概念摘要

Collections: Groups of objects managed with built-in functionality for adding, removing, and accessing items.
集合:使用内置功能管理的对象组,用于添加、删除和访问项目。

Types of Collections: Non-generic (ArrayList, Hashtable) and generic (List, Dictionary<TKey, TValue>, HashSet, Queue, Stack).
集合类型:非通用(ArrayList、Hashtable)和通用(List、Dictionary<TKey、TValue>、HashSet、Queue、Stack)。

Working with Lists: Dynamic arrays that provide methods for adding, accessing, and removing elements.
使用列表:提供添加、访问和删除元素的方法的动态数组。

Dictionaries: Key-value pairs for fast lookups and storage.
字典:用于快速查找和存储的键值对。

HashSets: Unique collections that prevent duplicate entries.
HashSets:防止重复条目的唯一集合。

Queues and Stacks: FIFO and LIFO collections for managing data flow.
队列和堆栈:用于管理数据流的 FIFO 和 LIFO 集合。

Working with collections in C# is essential for effective data management and manipulation. By understanding the various types of collections available and their use cases, you can enhance the performance and readability of your code. This chapter has provided an overview of lists, dictionaries, sets, queues, and stacks, along with best practices for working with these powerful data structures. Mastering collections will significantly contribute to your ability to write efficient and maintainable C# applications.

在 C# 中使用集合对于有效的数据管理和作至关重要。通过了解各种类型的可用集合及其使用案例,您可以提高代码的性能和可读性。本章概述了列表、字典、集合、队列和堆栈,以及使用这些强大数据结构的最佳实践。掌握集合将极大地有助于您编写高效且可维护的 C# 应用程序。

Chapter 13: File I/O in C

第 13 章:C# 语言中的文件 I/O

File I/O

文件 I/O

File Input/Output (I/O) is an essential aspect of programming that allows applications to read from and write to files on disk. In C#, the System.IO namespace provides a rich set of classes for handling file operations, making it easier to manipulate text files, binary files, and directories. This chapter covers the fundamentals of file I/O in C#, including reading and writing files, handling exceptions, and best practices for efficient file handling.

文件输入/输出 (I/O) 是编程的一个重要方面,它允许应用程序读取和写入磁盘上的文件。在 C# 中,System.IO 命名空间提供了一组丰富的类来处理文件作,从而可以更轻松地作文本文件、二进制文件和目录。本章介绍 C# 中文件 I/O 的基础知识,包括读取和写入文件、处理异常以及高效处理文件的最佳实践。

Understanding File I/O

了解文件 I/O

File I/O operations involve two main actions: reading data from files and writing data to files. These operations can be performed on various types of files, including text files, CSV files, XML files, and binary files. The .NET framework provides various classes to facilitate these operations, including File, FileInfo, StreamReader, StreamWriter, and others.

文件 I/O作涉及两个主要作:从文件中读取数据和将数据写入文件。可以对各种类型的文件执行这些作,包括文本文件、CSV 文件、XML 文件和二进制文件。.NET Framework 提供了各种类来简化这些作,包括 File、FileInfo、StreamReader、StreamWriter 等。

Reading from Files

从文件中读取

C# provides multiple ways to read data from files. The most common methods involve using StreamReader, which is designed for reading characters from streams in a specific encoding, or using the File class, which provides static methods for reading entire files.

C# 提供了多种从文件中读取数据的方法。最常见的方法包括使用 StreamReader(用于从特定编码的流中读取字符)或使用 File 类(提供用于读取整个文件的静态方法)。

Using StreamReader

使用 StreamReader

StreamReader is a class that makes it easy to read text from a file line by line or in a specific format.

StreamReader 是一个类,可以轻松地逐行或以特定格式从文件中读取文本。

Example of Using StreamReader:
StreamReader 使用示例:

using System.IO;

public void ReadFromFile(string filePath)
{
    using (StreamReader reader = new StreamReader(filePath))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
        }
    }
}

In this example, the StreamReader reads each line of the specified file until the end of the file is reached. The using statement ensures that the reader is properly disposed of, freeing any associated resources.

在此示例中,StreamReader 读取指定文件的每一行,直到到达文件末尾。 using 语句确保正确释放读取器,从而释放任何关联的资源。

Using File.ReadAllText

使用 File.ReadAllText

For simple use cases where you want to read an entire file into a string, the File.ReadAllText method can be convenient.

对于想要将整个文件读入字符串的简单用例,File.ReadAllText 方法可能很方便。

Example of Using File.ReadAllText:
使用 File.ReadAllText 的示例:

string content = File.ReadAllText("example.txt");
Console.WriteLine(content);

This method reads all the text in the specified file and stores it in a string variable.

此方法读取指定文件中的所有文本并将其存储在 string 变量中。

Writing to Files

写入文件

Writing data to files in C# can be accomplished using StreamWriter, which provides methods for writing text to files, or using static methods from the File class.

在 C# 中,可以使用 StreamWriter(提供将文本写入文件的方法)或使用 File 类中的静态方法将数据写入文件。

Using StreamWriter

使用 StreamWriter

StreamWriter allows you to write characters to a stream in a specified encoding.

StreamWriter 允许您以指定编码将字符写入流。

Example of Using StreamWriter:
使用 StreamWriter 的示例:

public void WriteToFile(string filePath)
{
    using (StreamWriter writer = new StreamWriter(filePath))
    {
    writer.WriteLine("Hello, World!");
    writer.WriteLine("Writing to a file in C#.");
    }
}

In this example, the StreamWriter writes two lines to the specified file. Again, the using statement ensures that resources are properly released.

在此示例中,StreamWriter 将两行写入指定文件。同样,using 语句确保资源得到正确释放。

Using File.WriteAllText

使用 File.WriteAllText

For cases where you want to write a string to a file, the File.WriteAllText method provides a simple and efficient approach.

对于要将字符串写入文件的情况,File.WriteAllTex t 方法提供了一种简单有效的方法。

Example of Using File.WriteAllText:

使用 File.WriteAllText 的示例:

string textToWrite = "This is a simple text file.";
File.WriteAllText("output.txt", textToWrite);

This method creates a new file or overwrites an existing one with the specified text.

此方法创建新文件或使用指定文本覆盖现有文件。

Working with Binary Files

使用二进制文件

C# also allows you to read and write binary files using BinaryReader and BinaryWriter. These classes facilitate reading and writing primitive types in a binary format, which is useful for performance-sensitive applications.

C# 还允许您使用 BinaryReader 和 BinaryWriter 读取和写入二进制文件。这些类有助于以二进制格式读取和写入基元类型,这对于性能敏感的应用程序非常有用。

Using BinaryReader

使用 BinaryReader

Example of Using BinaryReader:

使用 BinaryReader 的示例:

public void ReadBinaryFile(string filePath)
{
    using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open)))
    {
        int number = reader.ReadInt32();
        string message = reader.ReadString();
        Console.WriteLine($"Number: {number}, Message: {message}");
    }
}

In this example, BinaryReader reads an integer and a string from a binary file.

在此示例中,BinaryReader 从二进制文件中读取整数和字符串。

Using BinaryWriter

使用 BinaryWriter

Example of Using BinaryWriter:
使用 BinaryWriter 的示例:

public void WriteBinaryFile(string filePath)
{
    using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Create)))
    {
        writer.Write(42);
        writer.Write("Hello Binary World!");
    }
}

This code writes an integer and a string to a binary file using BinaryWriter.

此代码使用 BinaryWriter 将整数和字符串写入二进制文件。

Handling File Exceptions

处理文件异常

File I/O operations are prone to exceptions due to various factors such as missing files, permission issues, or disk space problems. It is crucial to handle these exceptions gracefully to ensure that your application remains robust.

文件 I/O作容易由于各种因素(例如文件丢失、权限问题或磁盘空间问题)而出现异常。妥善处理这些异常以确保您的应用程序保持稳健性至关重要。

Using Try-Catch Blocks

使用 Try-Catch 块

You can wrap file I/O operations in try-catch blocks to handle exceptions.

您可以将文件 I/O作包装在 try-catch 块中以处理异常。

Example of Exception Handling:
异常处理示例:

try
{
    ReadFromFile("nonexistent.txt");
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine($"Access denied: {ex.Message}");
}

In this example, the application handles FileNotFoundException and UnauthorizedAccessException, providing meaningful feedback to the user.

在此示例中,应用程序处理 FileNotFoundException 和 UnauthorizedAccessException,向用户提供有意义的反馈。

Best Practices for File I/O

文件 I/O 的最佳实践

Use Using Statements: Always use using statements with StreamReader, StreamWriter, BinaryReader, and BinaryWriter to ensure proper resource management.
使用 Using 语句:始终将 using 语句与 StreamReader、StreamWriter、BinaryReader 和 BinaryWriter 一起使用 ,以确保适当的资源管理。

Check for File Existence: Before attempting to read from a file, check if the file exists using File.Exists.
检查文件是否存在:在尝试从文件中读取之前,请使用 File.Exists 检查文件是否存在。

Example:
示例:

if (File.Exists("example.txt"))
{
    // Read from the file
}

Handle Exceptions: Implement robust exception handling to deal with potential issues during file operations.
处理异常:实施强大的异常处理以处理文件作期间的潜在问题。

Consider File Encoding: When dealing with text files, specify the encoding when creating StreamReader or StreamWriter to avoid issues with character representation.
考虑文件编码:在处理文本文件时,请在创建 StreamReader 或 StreamWriter 时指定编码,以避免字符表示问题。

Example:
示例:

using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.UTF8))
{
    // Write to file
}

Perform I/O Operations Asynchronously: For better performance, especially in user interface applications, consider using asynchronous file I/O methods such as ReadAsync and WriteAsync.
异步执行 I/O作:为了获得更好的性能,尤其是在用户界面应用程序中,请考虑使用异步文件 I/O 方法,例如 ReadAsync 和 WriteAsync。

Be Mindful of Performance: For large files, read or write in chunks instead of reading the entire file into memory at once.
注意性能:对于大型文件,请以块的形式读取或写入,而不是一次将整个文件读入内存。

Summary of File I/O Concepts

文件 I/O 概念摘要

File I/O: Fundamental operations for reading from and writing to files.
文件 I/O:读取和写入文件的基本作。

Reading from Files: Using StreamReader and File.ReadAllText.
从文件中读取:使用 StreamReader 和 File.ReadAllText。

Writing to Files: Using StreamWriter and File.WriteAllText.
写入文件:使用 StreamWriter 和 File.WriteAllText。

Binary Files: Using BinaryReader and BinaryWriter for binary data manipulation.
Binary Files:使用 BinaryReader 和 BinaryWriter 进行二进制数据作。

Exception Handling: Use try-catch blocks to handle file-related exceptions gracefully.
异常处理:使用 try-catch 块正常处理与文件相关的异常。

Best Practices: Implement best practices for efficient and robust file handling.
最佳实践: 实施最佳实践以实现高效和强大的文件处理。

File I/O is a crucial aspect of many applications, enabling data persistence and interaction with external files. By mastering the techniques for reading and writing files in C#, as well as understanding how to handle exceptions effectively, you can create robust applications that manage data efficiently. This chapter has provided an overview of the key concepts and best practices in file I/O, equipping you with the knowledge needed to work confidently with files in C#. As you continue your journey in C#proficiency in file IO will enhance the capability and usability of your applications.

文件 I/O 是许多应用程序的关键方面,它支持数据持久性和与外部文件的交互。通过掌握在 C# 中读取和写入文件的技术,以及了解如何有效地处理异常,您可以创建能够高效管理数据的强大应用程序。本章概述了文件 I/O 中的关键概念和最佳实践,使您具备了在 C# 中自信地处理文件所需的知识。随着您继续学习 C#,熟练掌握文件 IO 将增强应用程序的功能和可用性。

Chapter 14: Exception Handling in C

第 14 章:C# 语言中的异常处理

Exception Handling

异常处理

Exception handling is a crucial aspect of robust application development in C#. It provides a mechanism to gracefully handle errors and unexpected situations that may arise during the execution of a program. By effectively managing exceptions, developers can ensure that their applications remain stable, maintainable, and user-friendly. This chapter covers the fundamentals of exception handling in C#, including the types of exceptions, the structure of try-catch blocks, creating custom exceptions, and best practices for error management.

异常处理是 C# 中可靠应用程序开发的一个重要方面。它提供了一种机制,可以正常处理程序执行过程中可能出现的错误和意外情况。通过有效管理异常,开发人员可以确保其应用程序保持稳定、可维护和用户友好。本章介绍 C# 中异常处理的基础知识,包括异常类型、try-catch 块的结构、创建自定义异常以及错误管理的最佳实践。

Understanding Exceptions

了解异常

An exception is an event that disrupts the normal flow of a program's execution. In C#, exceptions can occur due to various reasons, such as:

异常是中断程序执行的正常流程的事件。在 C# 中,异常可能由于各种原因而发生,例如:

Runtime errors: These include division by zero, null reference errors, and index out of range errors.
运行时错误:这些错误包括被零除、空引用错误和索引超出范围错误。

File handling errors: Errors that occur while accessing files, such as file not found or access denied.
文件处理错误:访问文件时发生的错误,例如找不到文件或访问被拒绝。

Database errors: Errors that arise when interacting with databases, such as connection failures or query syntax errors.
数据库错误:与数据库交互时出现的错误,例如连接失败或查询语法错误。

Invalid user input: When the input provided by the user does not meet the expected format or constraints.
无效的用户输入:当用户提供的输入不符合预期的格式或约束时。

C# provides a structured way to handle these exceptions using the try, catch, and finally blocks.

C# 提供了一种结构化的方法,可以使用 try、catch 和 finally 块处理这些异常。

The Try-Catch-Finally Structure

try-catch-finally 结构

The most common way to handle exceptions in C# is by using the try, catch, and finally blocks.

在 C# 中处理异常的最常见方法是使用 try、catch 和 finally 块。

Try Block

try块

The try block is used to enclose the code that may throw an exception. If an exception occurs within this block, the control is transferred to the corresponding catch block.

try块用于包含可能引发异常的代码。如果此模块内发生异常,则控制权将转移到相应的 catch 模块。

Example:
示例:

try
{
    int number = Convert.ToInt32("abc"); 
    // This will throw an exception
}

Catch Block

Catch 块

The catch block is used to handle the exception. You can specify the type of exception you want to catch, allowing you to handle different exceptions in different ways.

catch 块用于处理异常。您可以指定要捕获的异常类型,从而允许您以不同的方式处理不同的异常。

Example:
示例:

catch (FormatException ex)
{
    Console.WriteLine($"Format exception: {ex.Message}");
}

In this example, if a FormatException occurs within the try block, the message will be displayed.

在此示例中,如果 FormatException 出现在 try 块中,则将显示该消息。

Finally Block

Finally 块

The finally block is optional and is executed regardless of whether an exception occurred or not. This is typically used for cleanup operations, such as closing files or releasing resources.

finally 块是可选的,无论是否发生异常,都会执行。这通常用于清理作,例如关闭文件或释放资源。

Example:
示例:

finally
{
    Console.WriteLine("Execution completed.");
}

Catching Multiple Exceptions

捕获多个异常

You can have multiple catch blocks to handle different types of exceptions that may arise from the same try block. The order of catch blocks is important, as the most derived exceptions should be caught first.

您可以有多个 catch 块来处理同一 try 块可能引起的不同类型的异常。catch 块的顺序很重要,因为应该首先捕获最衍生的异常。

Example:
示例:

try

{
// Some code that may throw exceptions
// 一些可能引发异常的代码
}
catch (FormatException ex)
{
    Console.WriteLine($"Format exception: {ex.Message}");
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Division by zero: {ex.Message}");
}
catch (Exception ex) // General exception handler
{
    Console.WriteLine($"An error occurred: {ex.Message}");
}

Throwing Exceptions

引发异常

You can throw exceptions in your code using the throw keyword. This is particularly useful when you want to indicate that something has gone wrong in your custom logic.

您可以使用 throw 关键字在代码中引发异常。当您想要指示自定义 logic 中出现问题时,这特别有用。

Example:
示例:

public void ValidateAge(int age)
{
    if (age < 0)
    {
        throw new ArgumentException("Age cannot be negative.");
    }
}

In this example, if a negative age is passed to the ValidateAge method, an ArgumentException is thrown.

在此示例中,如果将负 age 传递给 ValidateAge 方法,则会引发 ArgumentException。

Custom Exceptions

自定义异常

Creating custom exceptions allows you to provide more meaningful error messages that are specific to your application’s context. Custom exceptions can be defined by deriving from the Exception class.

通过创建自定义异常,您可以提供更有意义的特定于应用程序上下文的错误消息。可以通过从 Exception 类派生来定义自定义异常。

Example of a Custom Exception:

自定义异常示例:

public class InvalidAgeException : Exception
{
    public InvalidAgeException(string message) : base(message) { }
}

You can then use your custom exception in your application:
然后,您可以在应用程序中使用自定义异常:

public void SetAge(int age)
{
if (age < 0)
{
    throw new InvalidAgeException("Age must be a positive integer.");

}}

Using Exception Properties

使用异常属性

When catching exceptions, you can access various properties of the exception object, such as Message, StackTrace, and InnerException. These properties provide additional information about the error that occurred.

在捕获异常时,您可以访问异常对象的各种属性,例如 Message、StackTrace 和 InnerException。这些属性提供有关所发生错误的其他信息。

Example:
示例:

try
{
    // Some code that may throw an exception
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
    Console.WriteLine($"Stack trace: {ex.StackTrace}");
}

Best Practices for Exception Handling

异常处理的最佳实践

Catch Specific Exceptions: Always catch specific exceptions rather than using a general catch (Exception ex). This helps in understanding and debugging the exact cause of the issue.
捕获特定异常:始终捕获特定异常,而不是使用常规捕获 (Exception ex)。这有助于了解和调试问题的确切原因。

Avoid Swallowing Exceptions: Do not catch an exception and do nothing with it. At the very least, log the exception or rethrow it to ensure that the error is not silently ignored.
避免吞噬异常:不要捕获异常,也不对它执行任何作。至少,记录异常或重新引发异常,以确保不会以静默方式忽略错误。

Use Finally for Cleanup: Utilize the finally block for cleanup code, ensuring that resources are released regardless of whether an exception occurred.
使用 Finally 进行清理:使用 finally 块进行清理代码,确保无论是否发生异常,资源都会被释放。

Use Custom Exceptions: Define custom exceptions for specific application scenarios, allowing for clearer error handling and improved debugging.
使用自定义异常:为特定应用程序场景定义自定义异常,从而实现更清晰的错误处理和改进调试。

Do Not Use Exceptions for Control Flow: Avoid using exceptions to control the normal flow of your application. Exceptions should be reserved for exceptional situations.
不要对控制流使用异常:避免使用异常来控制应用程序的正常流。应为特殊情况保留例外。

Log Exceptions: Implement logging for exceptions to capture detailed information about errors, which can be invaluable for diagnosing issues in production environments.
记录异常:实施异常日志记录以捕获有关错误的详细信息,这对于诊断生产环境中的问题非常宝贵。

Use Exception Filters: In C# 6.0 and later, you can use exception filters to catch exceptions based on certain conditions. This can make your code cleaner and more readable.
使用异常筛选器:在 C# 6.0 及更高版本中,可以使用异常筛选器根据特定条件捕获异常。这可以使您的代码更简洁、更具可读性。

Example:
示例:

catch (Exception ex) when (ex is FormatException && ex.Message.Contains("abc"))
{
    Console.WriteLine("Caught a format exception related to 'abc'.");
}

Summary of Exception Handling Concepts

异常处理概念摘要

Exceptions: Unforeseen errors that disrupt program execution.
异常:中断程序执行的不可预见的错误。

Try-Catch-Finally: The basic structure for handling exceptions in C#.
Try-Catch-Finally:在 C# 中处理异常的基本结构。

Catching Multiple Exceptions: Using multiple catch blocks to handle different exceptions.
捕获多个异常:使用多个 catch 块来处理不同的异常。

Throwing Exceptions: Indicating errors using the throw keyword.
引发异常:使用 throw 关键字指示错误。

Custom Exceptions: Defining exceptions specific to application logic.
自定义异常:定义特定于应用程序逻辑的异常。

Exception Properties: Accessing properties like Message and StackTrace for error information.
异常属性:访问 Message 和 StackTrace 等属性以获取错误信息。

Best Practices: Strategies for effective exception handling.
最佳实践:有效异常处理的策略。

Exception handling is a vital aspect of developing robust and maintainable C# applications. By understanding how to handle exceptions effectively and applying best practices, you can create applications that respond gracefully to unexpected situations, ensuring a better experience for users. This chapter has provided an overview of the key concepts and techniques for managing exceptions in C#, equipping you with the skills needed to handle errors gracefully and maintain application stability. As you continue your journey in C#, proficiency in exception handling will enhance the reliability and user-friendliness of your applications.

异常处理是开发可靠且可维护的 C# 应用程序的一个重要方面。通过了解如何有效处理异常并应用最佳实践,您可以创建能够正常响应意外情况的应用程序,从而确保为用户提供更好的体验。本章概述了在 C# 中管理异常的关键概念和技术,使您具备正常处理错误和维护应用程序稳定性所需的技能。随着您继续 C# 之旅,熟练掌握异常处理将增强应用程序的可靠性和用户友好性。

Chapter 15: Working with Asynchronous Programming in C

第 15 章:在 C# 语言中使用异步编程

Asynchronous Programming

异步编程

Asynchronous programming is a powerful paradigm that enables developers to write programs that can perform multiple tasks concurrently, enhancing responsiveness and performance. In C#, asynchronous programming is primarily achieved through the use of the async and await keywords, which simplify the process of writing asynchronous code. This chapter explores the principles of asynchronous programming, its benefits, and how to effectively implement it in C# applications.

异步编程是一种强大的范例,它使开发人员能够编写可以同时执行多个任务的程序,从而提高响应能力和性能。在 C# 中,异步编程主要是通过使用 async 和 await 关键字来实现的,这简化了编写异步代码的过程。本章探讨了异步编程的原理、其优点以及如何在 C# 应用程序中有效地实现异步编程。

Understanding Asynchronous Programming

了解异步编程

Asynchronous programming allows a program to execute tasks without blocking the main thread. This is particularly important in applications that require user interaction, such as desktop and web applications, where long-running tasks can cause the user interface to freeze. By using asynchronous methods, you can perform time-consuming operations—like file I/O, web requests, or database queries—without interrupting the user's experience.

异步编程允许程序在不阻塞主线程的情况下执行任务。这在需要用户交互的应用程序(如桌面和 Web 应用程序)中尤为重要,在这些应用程序中,长时间运行的任务可能会导致用户界面冻结。通过使用异步方法,您可以执行耗时的作,例如文件 I/O、Web 请求或数据库查询,而不会中断用户体验。

How Asynchronous Programming Works

异步编程的工作原理

When a method is marked as async, it can contain the await keyword, which indicates that the method can pause its execution until a certain task completes. This allows the program to continue executing other code while waiting for the task to finish.

当方法标记为 async 时,它可以包含 await 关键字,该关键字指示该方法可以暂停执行,直到某个任务完成。这允许程序在等待任务完成的同时继续执行其他代码。

The Async and Await Keywords

async 和 await 关键字

The async and await keywords are fundamental to writing asynchronous code in C#.
async 和 await 关键字是在 C# 中编写异步代码的基础。

The Async Keyword

async 关键字

The async modifier is used to indicate that a method will contain asynchronous operations. An async method typically returns a Task or Task, which represents the ongoing operation.

asyn c 修饰符用于指示方法将包含异步作。asyn c 方法通常返回 Task 或 Task,它表示正在进行的作。

Example of an Async Method:

异步方法示例:

public async Task<string> FetchDataAsync(string url)
{
    using (HttpClient client = new HttpClient())
    {
        string result = await client.GetStringAsync(url);
        return result;
    }
}

In this example, FetchDataAsync is an asynchronous method that retrieves data from a specified URL without blocking the calling thread.

在此示例中,FetchDataAsync 是一种异步方法,它从指定的 URL 检索数据,而不会阻止调用线程。

The Await Keyword

await 关键字

The await keyword is used to pause the execution of an async method until the awaited task completes. This allows the method to return control to the caller, making it possible for the application to continue executing other code.

await 关键字用于暂停 async 方法的执行,直到等待的任务完成。这允许该方法将控制权返回给调用方,使应用程序能够继续执行其他代码。

Example:
示例:

public async Task ProcessDataAsync()
{
    string data = await FetchDataAsync("https://example.com");
    Console.WriteLine(data);
}

In this example, ProcessDataAsync waits for FetchDataAsync to complete before proceeding to print the data.

在此示例中,ProcessDataAsync 等待 FetchDataAsync 完成,然后再继续打印数据。

Benefits of Asynchronous Programming

异步编程的好处

Improved Responsiveness: Asynchronous programming allows applications to remain responsive, even during long-running operations. This is particularly important in user interface applications where user experience is critical.
改进的响应能力:异步编程允许应用程序保持响应,即使在长时间运行的作期间也是如此。这在用户体验至关重要的用户界面应用程序中尤为重要。

Scalability: Asynchronous methods can handle more requests in parallel, making applications more scalable. This is especially beneficial in web applications, where multiple requests can be processed concurrently without blocking the server.
可扩展性:异步方法可以并行处理更多请求,使应用程序更具可扩展性。这在 Web 应用程序中特别有用,因为在 Web 应用程序中,可以同时处理多个请求而不会阻塞服务器。

Simplified Code: The async and await keywords simplify asynchronous programming by allowing developers to write code that looks synchronous, making it easier to read and maintain.
简化的代码:async 和 await 关键字允许开发人员编写看起来同步的代码,使其更易于阅读和维护,从而简化了异步编程。

Implementing Asynchronous Programming

实现异步编程

To implement asynchronous programming in C#, you typically follow a few steps:
要在 C# 中实现异步编程,通常遵循以下几个步骤:

Identify Long-Running Operations: Determine which parts of your code will benefit from being asynchronous. Common candidates include I/O operations, network calls, and CPU-bound tasks.
识别长时间运行的作:确定代码的哪些部分将从异步中受益。常见的候选项包括 I/O作、网络调用和 CPU 绑定任务。

Use Async Methods: Mark methods with the async modifier and return a Task or Task. Use the await keyword for any long-running operations.
使用异步方法:使用 async 修饰符标记方法并返回 Task 或 Task。 对任何长时间运行的作使用 await 关键字。

Handle Exceptions: Exceptions in asynchronous methods can be caught using standard try-catch blocks. However, the exception must be awaited to be caught.
处理异常:可以使用标准 try-catch 块捕获异步方法中的异常。但是,必须等待异常才能被捕获。

Test and Debug: Test asynchronous methods to ensure they work as expected. Use debugging tools to monitor the flow of execution and track down any issues.
测试和调试:测试异步方法以确保它们按预期工作。使用调试工具监控执行流程并跟踪任何问题。

Asynchronous Programming with Task and Task

使用 Task 和 Task进行异步编程

The Task class represents an asynchronous operation. It can be used to run code in the background and monitor the completion of that code.

Task 类表示异步作。它可用于在后台运行代码并监视该代码的完成情况。

Creating a Task

创建任务

You can create a task using the Task.Run method, which allows you to run a method asynchronously.

您可以使用 Task.Run 方法创建任务,该方法允许您异步运行方法。

Example:
示例:

public Task<string> GetDataAsync()
{
    return Task.Run(() =>
    {
        // Simulate a long-running operation
        Thread.Sleep(2000);
        return "Data fetched!";
    });
}

This example demonstrates how to run a long-running operation in a separate thread using Task.Run.

此示例演示如何使用 Task.Run 在单独的线程中运行长时间运行的作。

Using Async/Await with I/O Operations

将 Async/Await 与 I/O作一起使用

One of the most common use cases for asynchronous programming is handling I/O operations, such as reading and writing files or making web requests.

异步编程最常见的用例之一是处理 I/O作,例如读取和写入文件或发出 Web 请求。

Asynchronous File I/O

异步文件 I/O

The System.IO namespace provides asynchronous methods for reading from and writing to files.

System.IO 命名空间提供用于读取和写入文件的异步方法。

Example:
示例:

public async Task<string> ReadFileAsync(string filePath)
{
    using (StreamReader reader = new StreamReader(filePath))
    {
        return await reader.ReadToEndAsync();
    }
}

In this example, ReadFileAsync reads the contents of a file asynchronously.
在此示例中, ReadFileAsync 异步读取文件的内容。

Asynchronous Web Requests

异步 Web 请求

The HttpClient class allows you to make asynchronous web requests, making it easier to interact with web services without blocking the calling thread.

HttpClient 类允许您发出异步 Web 请求,从而更轻松地与 Web 服务交互,而不会阻止调用线程。

Example:
示例:

public async Task<string> FetchJsonDataAsync(string url)
{
    using (HttpClient client = new HttpClient())
    {
        HttpResponseMessage response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

In this example, FetchJsonDataAsync fetches JSON data from a specified URL asynchronously.

在此示例中,FetchJsonDataAsync 从指定的 URL 异步获取 JSON 数据。

Handling Exceptions in Asynchronous Methods

在异步方法中处理异常

Exceptions in asynchronous methods can be handled using standard try-catch blocks. However, it's essential to await the task to catch any exceptions that may have occurred during execution.

异步方法中的异常可以使用标准的 try-catch 块进行处理。但是,必须等待任务捕获执行期间可能发生的任何异常。

Example:
示例:

public async Task SafeFetchDataAsync(string url)
{
    try
    {
        string data = await FetchDataAsync(url);
        Console.WriteLine(data);
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Request error: {ex.Message}");
    }
}

In this example, SafeFetchDataAsync handles any exceptions that occur during the fetch operation.

在此示例中,SafeFetchDataAsync 处理 fetch作期间发生的任何异常。

Best Practices for Asynchronous Programming

异步编程的最佳实践

Avoid Async Void: Use async Task for asynchronous methods instead of async void. async void methods cannot be awaited and may lead to unhandled exceptions.
避免 Async Void:对异步方法使用 async Task,而不是 async void。 async void 方法无法等待,并且可能会导致未经处理的异常。

Use ConfigureAwait: When awaiting tasks in library code, use ConfigureAwait(false) to avoid capturing the synchronization context, which can improve performance and avoid deadlocks in certain scenarios.

使用 ConfigureAwait:在库代码中等待任务时,使用 ConfigureAwait(false) 来避免捕获同步上下文,这可以提高性能并在某些情况下避免死锁。

Example:
示例:

await someTask.ConfigureAwait(false);

Limit Concurrency: While asynchronous programming allows multiple tasks to run concurrently, be cautious about overwhelming resources. Use mechanisms like SemaphoreSlim to limit concurrent operations if necessary.
限制并发性:虽然异步编程允许多个任务并发运行,但要小心处理过多的资源。如有必要,请使用 SemaphoreSlim 等机制来限制并发作。

Use Cancellation Tokens: Implement cancellation support in your asynchronous methods to allow users to cancel long-running operations gracefully.
使用取消令牌:在异步方法中实现取消支持,以允许用户正常取消长时间运行的作。

Example:
示例:

public async Task<string> FetchDataWithCancellationAsync(string url, CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        cancellationToken.ThrowIfCancellationRequested();
        string result = await client.GetStringAsync(url, cancellationToken);
        return result;
    }
}

Test Asynchronous Code: Ensure that your asynchronous methods are well-tested, covering various scenarios, including error conditions and cancellation.
测试异步代码:确保您的异步方法经过充分测试,涵盖各种场景,包括错误条件和取消。

Summary of Asynchronous Programming Concepts

异步编程概念摘要

Asynchronous Programming: A paradigm that allows concurrent execution of tasks to improve application responsiveness and scalability.
异步编程:一种允许并发执行任务以提高应用程序响应能力和可扩展性的范例。

Async and Await Keywords: Keywords used to define and handle asynchronous methods.
Async 和 Await 关键字:用于定义和处理异步方法的关键字。

Task and Task: Classes that represent asynchronous operations.
Task 和 Task:表示异步作的类。

Asynchronous I/O Operations: Reading and writing files and making web requests asynchronously.
异步 I/O作:异步读取和写入文件以及发出 Web 请求。

Exception Handling: Managing exceptions in asynchronous methods using try-catch blocks.
异常处理:使用 try-catch 块管理异步方法中的异常。

Best Practices: Strategies for writing effective and efficient asynchronous code.
最佳实践:编写有效且高效的异步代码的策略。

Asynchronous programming is a vital skill for modern C# developers, allowing them to build responsive and high-performance applications. By leveraging the async and await keywords, developers can write clean and maintainable code that handles long-running operations gracefully. This chapter has provided an overview of the key concepts and techniques in asynchronous programming, equipping you with the tools necessary to implement it effectively in your C# applications. As you continue your journey in C#, mastering asynchronous programming will significantly enhance the usability and performance of your applications.

异步编程是现代 C# 开发人员的一项重要技能,允许他们构建响应迅速的高性能应用程序。通过利用 async 和 await 关键字,开发人员可以编写干净且可维护的代码,以正常处理长时间运行的作。本章概述了异步编程中的关键概念和技术,为您提供了在 C# 应用程序中有效实现异步编程所需的工具。随着您继续 C# 之旅,掌握异步编程将显著提高应用程序的可用性和性能。

Chapter 16: LINQ (Language Integrated Query)

第 16 章:LINQ(语言集成查询)

LINQ

Language Integrated Query (LINQ) is a powerful feature in C# that allows developers to perform queries directly in the language syntax, enabling seamless integration of data manipulation capabilities with programming logic. LINQ simplifies data access by providing a unified approach to querying various data sources, including collections, databases, XML, and more. This chapter covers the fundamentals of LINQ, its syntax, how to work with different data sources, and best practices for effective usage.

语言集成查询 (LINQ) 是 C# 中的一项强大功能,它允许开发人员直接在语言语法中执行查询,从而实现数据作功能与编程逻辑的无缝集成。LINQ 通过提供统一的方法来查询各种数据源(包括集合、数据库、XML 等),从而简化数据访问。本章介绍 LINQ 的基础知识、语法、如何使用不同的数据源以及有效使用的最佳实践。

Understanding LINQ

了解 LINQ

LINQ provides a set of methods and operators that enable querying and manipulating data using a syntax that is both readable and expressive. With LINQ, you can work with data in a consistent manner, regardless of the data source. This is achieved through LINQ providers that translate LINQ queries into the appropriate format for the underlying data source.

LINQ 提供了一组方法和运算符,这些方法和运算符允许使用可读且富有表现力的语法来查询和作数据。使用 LINQ,您可以以一致的方式处理数据,而不管数据源如何。这是通过 LINQ 提供程序实现的,这些提供程序将 LINQ 查询转换为适用于基础数据源的格式。

Key Benefits of LINQ

LINQ 的主要优势

Unified Data Access: LINQ allows querying various data sources (like arrays, lists, databases, and XML) using a consistent syntax, making data manipulation easier.
统一数据访问:LINQ 允许使用一致的语法查询各种数据源(如数组、列表、数据库和 XML),从而使数据作更容易。

Type Safety: LINQ queries are checked at compile time, reducing runtime errors and improving code reliability.
类型安全:在编译时检查 LINQ 查询,从而减少运行时错误并提高代码可靠性。

IntelliSense Support: The LINQ API is integrated into Visual Studio, providing IntelliSense support, which enhances developer productivity.
IntelliSense 支持:LINQ API 已集成到 Visual Studio 中,提供 IntelliSense 支持,从而提高开发人员的工作效率。

Readable and Maintainable Code: LINQ queries are often more readable than traditional approaches, making the code easier to maintain and understand.
可读可维护的代码:LINQ 查询通常比传统方法更具可读性,使代码更易于维护和理解。

LINQ Syntax

LINQ 语法

LINQ can be written in two primary syntaxes: Query Syntax and Method Syntax.

LINQ 可以用两种主要语法编写:查询语法和方法语法。

Query Syntax

查询语法

Query syntax resembles SQL and is often more readable for those familiar with SQL. It uses keywords like from, where, select, and order by.

查询语法类似于 SQL,对于熟悉 SQL 的人来说通常更具可读性。它使用 from、where、select 和 order by 等关键字。

Example of Query Syntax:
查询语法示例:

var query = from number in numbers
                where number > 5
                select number;

In this example, a query is constructed to select numbers greater than 5 from the numbers collection.

在此示例中,将构造一个查询以从 numbers 集合中选择大于 5 的数字。

Method Syntax

方法语法

Method syntax uses extension methods provided by LINQ, allowing you to chain methods together.

方法语法使用 LINQ 提供的扩展方法,允许您将方法链接在一起。

Example of Method Syntax:
方法语法示例:

var query = numbers.Where(n => n > 5);

This example achieves the same result as the previous one using method syntax, showcasing the flexibility of LINQ.

此示例使用方法语法实现了与上一个示例相同的结果,展示了 LINQ 的灵活性。

Working with LINQ to Objects

使用 LINQ to Objects

LINQ to Objects allows you to query in-memory collections, such as arrays, lists, and other IEnumerable collections.

LINQ to Objects 允许您查询内存中的集合,例如数组、列表和其他 IEnumerable 集合。

Basic Operations

基本操作

LINQ supports a wide range of operations, including filtering, projection, sorting, and grouping. Here are some common LINQ operations:
LINQ 支持多种操作,包括筛选、投影、排序和分组。以下是一些常见的 LINQ操作:

Filtering: Use Where to filter elements based on a condition.
筛选:使用 Where 根据条件筛选元素。
Example:

var evenNumbers = numbers.Where(n => n % 2 == 0);

Projection: Use Select to transform elements into a new form.
投影:使用 Select 将元素转换为新形式。
Example:

var squares = numbers.Select(n => n * n);

Sorting: Use OrderBy and OrderByDescending to sort collections.
排序:使用 OrderBy 和 OrderByDescending 对集合进行排序。
Example:

var sortedNumbers = numbers.OrderBy(n => n);

Grouping: Use GroupBy to group elements based on a key.
分组:使用 GroupBy 根据键对元素进行分组。
Example:

var groupedByLength = words.GroupBy(w => w.Length);

LINQ to SQL and Entity Framework

LINQ to SQL 和实体框架

LINQ can also be used with databases through LINQ to SQL and Entity Framework. These providers allow you to query databases using LINQ syntax, translating queries into SQL.

LINQ 还可以通过 LINQ to SQL 和 Entity Framework 与数据库一起使用。这些提供程序允许您使用 LINQ 语法查询数据库,将查询转换为 SQL。

LINQ to SQL

LINQ 到 SQL

LINQ to SQL provides a runtime infrastructure for managing relational data as objects. It simplifies database operations by allowing you to work with objects instead of writing raw SQL.

LINQ to SQL 提供了用于将关系数据作为对象进行管理的运行时基础结构。它允许您使用对象而不是编写原始 SQL,从而简化了数据库作。

Example:
示例:

using (var context = new MyDataContext())
{
var query = from customer in context.Customers
                where customer.City == "London"
                select customer;

}

In this example, the query retrieves customers from London using LINQ to SQL.

在此示例中,查询使用 LINQ to SQL 从London检索客户。

Entity Framework

实体框架

Entity Framework (EF) is an ORM (Object-Relational Mapper) that supports LINQ for querying databases. EF allows you to work with databases using C# objects, making data access more intuitive.

实体框架 (EF) 是一种 ORM(对象关系映射器),支持用于查询数据库的 LINQ。EF 允许您使用 C# 对象处理数据库,使数据访问更加直观。

Example:
示例:

using (var context = new MyDbContext())
{
var products = context.Products
                    .Where(p => p.Price > 100)
                    .OrderBy(p => p.Name)
                    .ToList();
}

In this example, products with a price greater than 100 are retrieved and ordered by name using Entity Framework.

在此示例中,使用实体框架检索价格大于 100 的产品并按名称排序。

Working with LINQ to XML

使用 LINQ to XML

LINQ to XML provides a simple way to work with XML data, enabling you to read, modify, and query XML documents using LINQ.

LINQ to XML 提供了一种处理 XML 数据的简单方法,使您能够使用 LINQ 读取、修改和查询 XML 文档。

Loading XML

加载 XML

You can load an XML document into an XDocument object.

可以将 XML 文档加载到 XDocument 对象中。

Example:
示例:

XDocument xmlDoc = XDocument.Load("data.xml");

Querying XML

查询 XML

LINQ can be used to query XML elements and attributes.
LINQ 可用于查询 XML 元素和属性。

Example:
示例:

var query = from element in xmlDoc.Descendants("Item")
                where (int)element.Element("Price") > 50
                select element;

In this example, the query retrieves Item elements with a price greater than 50 from the XML document.

在此示例中,查询从 XML 文档中检索 price 大于 50 的 Item 元素。

Best Practices for Using LINQ

使用 LINQ 的最佳实践

Use Deferred Execution: LINQ queries are lazily evaluated, meaning they do not execute until the data is actually needed. This can improve performance but be mindful of data context lifetimes.
使用延迟执行:LINQ 查询是延迟计算的,这意味着它们在实际需要数据之前不会执行。这可以提高性能,但要注意数据上下文的生命周期。

Limit the Number of Queries: Be cautious about performing multiple queries in a loop. Instead, try to retrieve all necessary data in a single query to minimize database calls.
限制查询数量:在循环中执行多个查询时要小心。相反,请尝试在单个查询中检索所有必要的数据,以最大程度地减少数据库调用。

Use Asynchronous LINQ: When working with I/O-bound operations (like database queries), consider using asynchronous LINQ methods to improve responsiveness.
使用异步 LINQ:在使用 I/O 绑定作(如数据库查询)时,请考虑使用异步 LINQ 方法来提高响应能力。

Avoid Complex Queries: Keep LINQ queries simple and readable. If a query becomes too complex, consider breaking it down into smaller parts.
避免复杂查询:保持 LINQ 查询简单易读。如果查询变得太复杂,请考虑将其分解为更小的部分。

Profile and Optimize: Use profiling tools to analyze the performance of LINQ queries, particularly when working with large datasets or databases.
分析和优化:使用分析工具分析 LINQ 查询的性能,尤其是在处理大型数据集或数据库时。

Use Indexes in Databases: When using LINQ to query databases, ensure appropriate indexes are in place to enhance query performance.
在数据库中使用索引:使用 LINQ 查询数据库时,请确保有适当的索引以提高查询性能。

Summary of LINQ Concepts

LINQ 概念摘要

LINQ Overview: A feature that enables querying data from various sources using C# syntax.
LINQ 概述:支持使用 C# 语法从各种源查询数据的功能。

Query and Method Syntax: Two primary ways to write LINQ queries.
查询和方法语法:编写 LINQ 查询的两种主要方法。

LINQ to Objects: Querying in-memory collections with LINQ.
LINQ to Objects:使用 LINQ 查询内存中集合。

LINQ to SQL and Entity Framework: Using LINQ with databases for simplified data access.
LINQ to SQL 和实体框架:将 LINQ 与数据库结合使用以简化数据访问。

LINQ to XML: Querying and manipulating XML data using LINQ.
LINQ to XML:使用 LINQ 查询和作 XML 数据。

Best Practices: Strategies for effective and efficient LINQ usage.
最佳实践:有效且高效的 LINQ 使用策略。

LINQ is a powerful and versatile tool for data manipulation in C#. By providing a unified approach to querying different data sources, LINQ simplifies code and enhances readability. This chapter has covered the fundamental concepts and techniques of LINQ, equipping you with the skills necessary to leverage LINQ effectively in your applications. Mastering LINQ will not only improve your productivity as a developer but also enable you to write cleaner and more maintainable code. As you continue your journey in C#, harnessing the power of LINQ will greatly enhance your ability to work with data seamlessly and efficiently.

LINQ 是一个功能强大且用途广泛的工具,用于 C# 中的数据作。通过提供查询不同数据源的统一方法,LINQ 简化了代码并增强了可读性。本章介绍了 LINQ 的基本概念和技术,使您具备在应用程序中有效利用 LINQ 所需的技能。掌握 LINQ 不仅可以提高您作为开发人员的工作效率,还可以让您编写更简洁、更易于维护的代码。随着您继续 C# 之旅,利用 LINQ 的强大功能将大大增强您无缝、高效地处理数据的能力。

Chapter 17: Error Handling and Debugging in C

第 17 章:C# 语言中的错误处理和调试

Error Handling and Debugging

错误处理和调试

Error handling and debugging are essential skills for any programmer. They ensure that applications can gracefully handle unexpected situations and assist developers in identifying and fixing issues within their code. In C#, robust error handling mechanisms and effective debugging tools are available to improve the reliability and quality of applications. This chapter covers the key concepts of error handling, best practices, and debugging techniques in C#, providing a comprehensive understanding of how to manage exceptions and troubleshoot code effectively.

错误处理和调试是任何程序员的基本技能。它们确保应用程序能够妥善处理意外情况,并帮助开发人员识别和修复其代码中的问题。在 C# 中,可以使用可靠的错误处理机制和有效的调试工具来提高应用程序的可靠性和质量。本章介绍 C# 中错误处理的关键概念、最佳实践和调试技术,全面了解如何有效地管理异常和排除代码故障。

Understanding Exceptions

了解异常

In C#, an exception is an error that occurs during the execution of a program. Exceptions can arise from various sources, including invalid user input, failed file operations, network issues, or programming mistakes. When an exception occurs, it disrupts the normal flow of the program, and without proper handling, it can lead to application crashes.

在 C# 中,异常是在程序执行过程中发生的错误。异常可能来自各种来源,包括无效的用户输入、失败的文件作、网络问题或编程错误。当异常发生时,它会破坏程序的正常流程,如果不妥善处理,可能会导致应用程序崩溃。

Types of Exceptions

异常类型

C# distinguishes between two main types of exceptions:

C# 区分两种主要类型的异常:

Built-in Exceptions: These are predefined exceptions provided by the .NET Framework, such as ArgumentNullException, FileNotFoundException, and DivideByZeroException. They are used to handle common error scenarios.
内置异常:这些是 .NET Framework 提供的预定义异常,例如 ArgumentNullException、FileNotFoundException 和 DivideByZeroException。它们用于处理常见的错误场景。

Custom Exceptions: Developers can create their own exception classes to handle application-specific errors. Custom exceptions are typically derived from the System.Exception class.
自定义异常:开发人员可以创建自己的异常类来处理特定于应用程序的错误。自定义异常通常派生自 System.Exception 类。

Example of a Custom Exception:
自定义异常示例:

public class InvalidAgeException : Exception
{
    public InvalidAgeException(string message) : base(message) { }
}

In this example, InvalidAgeException is a custom exception that can be used to indicate an invalid age input.
在此示例中,InvalidAgeException 是一个自定义异常,可用于指示无效的 age 输入。

Exception Handling in C

C# 语言中的异常处理

C# provides a structured way to handle exceptions using the try, catch, and finally blocks. This approach allows developers to define code that may throw exceptions and specify how to handle those exceptions.

C# 提供了一种结构化的方法来处理使用 try、catch 和 finally 块的异常。此方法允许开发人员定义可能引发异常的代码,并指定如何处理这些异常。

Using Try-Catch Blocks

使用 Try-Catch 块

A try block contains the code that might throw an exception. If an exception occurs, control is transferred to the corresponding catch block.

try 块包含可能引发异常的代码。如果发生异常,控制权将转移到相应的 catch 模块。

Example:
示例:

try
{
    int[] numbers = { 1, 2, 3 };
    int invalidAccess = numbers[5]; // This will throw an exception
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

In this example, attempting to access an invalid index in the numbers array raises an IndexOutOfRangeException, which is caught and handled in the catch block.

在此示例中,尝试访问数字s 数组中的无效索引会引发 IndexOutOfRangeException,该索引在 catch 块中捕获和处理。

Finally Block

Finally 块

The finally block is optional and is executed after the try and catch blocks, regardless of whether an exception occurred. It is commonly used for cleanup activities, such as closing file streams or releasing resources.

finally 块是可选的,无论是否发生异常,都会在 try 和 catch 块之后执行。它通常用于清理活动,例如关闭文件流或释放资源。

Example:
示例:

FileStream stream = null;

try
{
    stream = new FileStream("file.txt", FileMode.Open);
    // Read from the file
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.Message}");
}
finally
{
    if (stream != null)
    {
    stream.Close(); // Ensure the stream is closed
    }
}

In this example, the finally block ensures that the file stream is closed, regardless of whether an exception occurred.

在此示例中,finally 块确保文件流已关闭,无论是否发生异常。

Exception Propagation

异常传播

Exceptions in C# can propagate up the call stack if they are not handled in the current method. When an exception is thrown, the runtime searches for a matching catch block in the current method. If none is found, it moves to the calling method, continuing this process until it finds a catch block or reaches the top level of the application.

如果 C# 中的异常未在当前方法中处理,则可以在调用堆栈中向上传播。引发异常时,运行时将在当前方法中搜索匹配的 catch 块。如果未找到,则移动到 calling 方法,继续此过程,直到找到 catch 块或到达应用程序的顶层。

Handling Exceptions at Different Levels

处理不同级别的异常

You can handle exceptions at various levels of your application, depending on the granularity of error handling you require. For instance, you might choose to handle specific exceptions at the point where they occur while allowing others to propagate to higher levels for centralized handling.

您可以在应用程序的各个级别处理异常,具体取决于所需的错误处理粒度。例如,您可以选择在特定异常发生时对其进行处理,同时允许其他异常传播到更高级别进行集中处理。

Example:
示例:

public void ProcessData()
{
    try
    {
        ReadFile();
    }
    catch (IOException ex)
    {
        Console.WriteLine($"I/O Error: {ex.Message}");
    }
}

private void ReadFile()
{
    // Code that may throw an exception
}

In this example, ProcessData handles IOException specifically, while ReadFile can throw various exceptions that are not explicitly caught.

在此示例中,ProcessData 专门处理 IOException,而 ReadFile 可以引发未显式捕获的各种异常。

Best Practices for Exception Handling

异常处理的最佳实践

Use Specific Exceptions: Catch specific exceptions rather than using a generic catch block. This helps in understanding what kind of errors might occur and allows for more precise handling.
使用特定异常:捕获特定异常,而不是使用通用的 catch 块。这有助于了解可能发生的错误类型,并允许更精确的处理。

Avoid Swallowing Exceptions: Do not catch exceptions without handling them or logging the error. This can make debugging difficult as important error information may be lost.
避免吞噬异常:请勿在未处理异常或未记录错误的情况下捕获异常。这可能会使调试变得困难,因为重要的错误信息可能会丢失。

Log Exceptions: Implement logging to capture exception details, including stack traces. This information is crucial for diagnosing issues in production environments.
记录异常:实施日志记录以捕获异常详细信息,包括堆栈跟踪。此信息对于诊断生产环境中的问题至关重要。

Rethrow Exceptions: If you cannot handle an exception, consider rethrowing it to propagate it up the call stack. Use throw; instead of throw ex; to preserve the original stack trace.
Rethrow Exceptions:如果您无法处理异常,请考虑重新抛出它以将其向上传播到调用堆栈中。使用 throw; 而不是 throw ex; 来保留原始堆栈跟踪。

Example:
示例:

catch (Exception ex)
{
    LogError(ex);
    throw; // Rethrow the exception
}

Use Finally for Cleanup: Always use the finally block for cleanup operations to ensure resources are released, regardless of whether an exception occurred.
使用 Finally 进行清理:始终使用 finally 块进行清理作,以确保释放资源,无论是否发生异常。

Define Custom Exceptions: Create custom exceptions for application-specific errors to provide better context and information about failures.
定义自定义例外:为特定于应用程序的错误创建自定义例外,以提供更好的上下文和有关失败的信息。

Debugging Techniques in C

C# 语言中的调试技术

Debugging is the process of identifying and resolving bugs or issues within your code. C# provides several debugging tools and techniques to help developers troubleshoot their applications effectively.

调试是识别和解决代码中的 bug 或问题的过程。C# 提供了多种调试工具和技术,可帮助开发人员有效地解决其应用程序的问题。

Using Visual Studio Debugger

使用 Visual Studio 调试器

Visual Studio offers a powerful built-in debugger that allows you to step through your code, inspect variables, and evaluate expressions. Here are some key features:

Visual Studio 提供了一个功能强大的内置调试器,允许您单步执行代码、检查变量和计算表达式。以下是一些主要功能:

Breakpoints: Set breakpoints in your code to pause execution at specific lines. This allows you to examine the state of your application at that point.
断点:在代码中设置断点以在特定行处暂停执行。这允许您检查应用程序在该点的状态。

Step Over and Step Into: Use "Step Over" to execute the current line and move to the next line, or "Step Into" to enter a method call and debug it line by line.
Step Over and Step Into:使用 “Step Over” 执行当前行并移动到下一行,或使用 “Step Into” 输入方法调用并逐行调试。

Watch and Immediate Windows: Use the Watch window to monitor variable values during debugging. The Immediate window allows you to execute expressions and evaluate code on the fly.
Watch 和 Immediate Windows:使用 Watch 窗口在调试期间监控变量值。Immediate (即时) 窗口允许您动态执行表达式和评估代码。

Call Stack Window: View the call stack to see the sequence of method calls that led to the current execution point. This is helpful for understanding the flow of your application.
Call Stack Window:查看调用堆栈以查看导致当前执行点的方法调用序列。这有助于了解应用程序的流程。

Exception Settings: Configure the debugger to break on specific exceptions. This allows you to catch exceptions as they occur, even if they are caught elsewhere in the code.
异常设置:将调试器配置为在特定异常时中断。这样,您就可以在异常发生时捕获异常,即使它们在代码中的其他位置捕获也是如此。

Debugging Techniques

调试技术

Print Debugging: Insert Console.WriteLine statements in your code to output variable values and execution flow. This is a simple but effective debugging technique.
打印调试:在代码中插入 Console.WriteLine 语句以输出变量值和执行流。这是一种简单但有效的调试技术。

Isolate the Problem: Narrow down the location of the bug by commenting out sections of code or adding additional logging to track down the issue.
隔离问题:通过注释掉代码部分或添加其他日志记录来跟踪问题,从而缩小错误的位置。

Check Application Logs: Review application logs to identify error messages and stack traces. Logs can provide context about what went wrong in production environments.
检查应用程序日志:查看应用程序日志以识别错误消息和堆栈跟踪。日志可以提供有关生产环境中出错的上下文。

Use Unit Tests: Implement unit tests to validate individual components of your application. Tests can help identify bugs early in the development process and provide a safety net when making changes.
使用单元测试:实施单元测试来验证应用程序的各个组件。测试可以帮助在开发过程的早期识别错误,并在进行更改时提供安全网。

Review Code Changes: If a bug appears after recent changes, review those changes carefully. Consider using version control systems (like Git) to compare modifications.
查看代码更改:如果在最近的更改后出现 Bug,请仔细查看这些更改。考虑使用版本控制系统(如 Git)来比较修改。

Summary of Error Handling and Debugging Concepts

错误处理和调试概念摘要

Exceptions: Errors that disrupt the normal flow of a program.
异常:破坏程序正常流程的错误。

Exception Handling: Using try-catch-finally blocks to manage exceptions gracefully.
异常处理:使用 try-catch-finally 块正常管理异常。

Exception Propagation: Understanding how exceptions can propagate up the call stack.
异常传播:了解异常如何在调用堆栈中向上传播。

Best Practices: Guidelines for effective exception handling.
最佳实践:有效异常处理的准则。

Debugging Techniques: Strategies and tools for troubleshooting code in C#.
调试技术:用于对 C# 中的代码进行故障排除的策略和工具。

Error handling and debugging are critical aspects of software development that enhance the reliability and maintainability of applications. C# provides robust mechanisms for handling exceptions and powerful tools for debugging. This chapter has explored the key concepts, best practices, and techniques for effective error management and troubleshooting. Mastering these skills is essential for any developer, enabling you to write resilient code and quickly resolve issues, ultimately leading to higher-quality software. As you continue your journey in C#, apply these error handling and debugging principles to improve your programming proficiency and enhance the overall user experience of your applications.

错误处理和调试是软件开发的关键方面,可以提高应用程序的可靠性和可维护性。C# 提供了用于处理异常的可靠机制和强大的调试工具。本章探讨了有效错误管理和故障排除的关键概念、最佳实践和技术。掌握这些技能对任何开发人员来说都是必不可少的,它使您能够编写弹性代码并快速解决问题,最终获得更高质量的软件。在继续 C# 之旅时,请应用这些错误处理和调试原则来提高编程熟练程度并增强应用程序的整体用户体验。

程序员编程的8条小贴士

程序员编程的8条小贴士

1 编码之前想一想

用10分钟,20分钟甚至30分钟的时间来想想你需要什么,想想什么样的设计模式(如果有的话)适合你将要编码的东西。真的要好好想想,你会很庆幸“浪费”了那几分钟,当你不得不更改或添加东西到代码中,而这只花费了你30分钟的时间而不是5小时。

2 注释你的代码

说真的,没有什么比两个月后检查自己的代码,却不记得它用来干什么更糟糕的了。注释所有重要的内容,当然那些显而易见的就免了吧。

3 写干净的代码

错落有致。使用空格。根据功能模块化你的代码。阅读Robert C. Martin写的《Clean Code》,非常有帮助。此外,遵循代码约定/标准(如Java Code Conventions),尤其如果是共享的代码。

4 重构

没有人喜欢用那些超级长的方法。这通常(几乎总是)意味着你混杂了功能。用更易于管理的方法分离代码。还能使得代码更可重用。

5 不要复制粘贴代码

如果你有两个或两个以上相同的代码块,那么你可能做错了什么。阅读第4条。

6 使用有意义的名称

虽然命名int变量为“elligent”或char为“mander”是很好笑;但是,这样的名称并不能说明变量是用来做什么的。

7 测试代码

测试,测试,测试,还是测试。测试你的代码。不要等到已经做完程序之后再来测试,否则当你发现一个巨大的bug,却不知道它来自于哪里来的时候,你会追悔莫及。

7b

自动化测试通常都是有价值的。它还有助于节省大量重测试和回归测试的时间。

8 学会调试

在我的第一年,我习惯于管理事情而不知道如何去做,并且每次遇到问题的时候就println。大错特错。有时候,我甚至找不到bug,而且检查“丢失的”println浪费时间。

https://www.cnblogs.com/star8521/p/5619510.html

从0到1搭建.NET Core Web API项目

从0到1搭建.NET Core Web API项目

1. 设置 .NET 8 Web API 项目

概念:使用 .NET CLI 创建新的 Web API 项目。这设置了一个基本的项目结构,包括启动和 WeatherForecast 控制器作为示例。Program.cs

代码示例

dotnet new webapi -n MyWebApi

2. Program.cs — 最低限度的 API 配置

概念:.NET 8 延续了最小 API 的趋势,允许你直接在 Program.cs 文件中以简化和简洁的方式配置服务和终结点。

代码示例

var builder = WebApplication.CreateBuilder(args);  
var app = builder.Build();  
app.MapGet("/", () => "Hello, World!");

app.Run();

3. 定义控制器

概念:控制器处理传入的 HTTP 请求并响应客户端。它们是通过继承自 ControllerBase 并使用 进行注释来定义的。[ApiController]

代码示例

[ApiController]  
[Route("[controller]")]  
public class MyController : ControllerBase  
{  
    [HttpGet]  
    public IActionResult Get() => Ok("Hello from MyController");  
}

4. 控制器中的依赖注入

概念:.NET Core 的内置依赖项注入 (DI) 使管理依赖项变得容易。您可以通过控制器的构造函数将服务注入控制器。

代码示例


public class MyService  
{  
    public string GetMessage() => "Injected message";  
}  

public class MyController : ControllerBase
{
    private readonly MyService _myService;

    public MyController(MyService myService)
    {
        _myService = myService;
    }

    [HttpGet]
    public IActionResult Get() => Ok(_myService.GetMessage());
}

5. 配置服务

概念:服务(如数据库上下文、自定义服务等)在 Program.cs 文件中配置,使它们可用于整个应用程序的依赖项注入。

代码示例

builder.Services.AddScoped();

6. 基于环境的配置

概念:.NET 支持特定于环境的配置文件 (appsettings.json、appsettings.Development.json等),允许根据应用程序的环境进行不同的设置。

代码示例


// appsettings.Development.json  
{  
  "Logging": {  
    "LogLevel": {  
      "Default": "Debug"  
    }  
  }  
}

7. 中间件

概念:中间件组件形成一个处理请求和响应的管道。可以为日志记录或错误处理等跨领域问题创建自定义中间件。

代码示例

app.Use(async (context, next) =>  
{  
    // Custom logic before passing to the next middleware  
    await next();  
    // Custom logic after executing the next middleware  
});

8. 路由

概念:.NET Web API 中的路由是通过控制器和操作方法上的属性路由实现的。这允许将 URL 直接映射到控制器操作。

代码示例

[HttpGet("myaction/{id}")]  
public IActionResult GetAction(int id) => Ok($"Action with ID = {id}");

9. 模型绑定

概念:模型绑定自动将数据从 HTTP 请求映射到操作方法参数。它支持复杂类型,包括 JSON 正文和查询字符串参数。

代码示例

public class MyModel  
{  
    public int Id { get; set; }  
    public string Name { get; set; }  
}  

[HttpPost]
public IActionResult PostAction([FromBody] MyModel model) => Ok(model);

10. 数据验证

概念:数据注释可用于验证模型数据。该属性会自动强制执行验证,如果模型无效,则以 400 进行响应。[ApiController]

代码示例

public class MyModel  
{  
    [Required]  
    public int Id { get; set; }  

[StringLength(100)]  
public string Name { get; set; }  

[StringLength(100)]  
public string Name { get; set; }  
}

11. 异步操作

概念:异步操作通过在等待 I/O 操作完成时释放线程来提高可伸缩性。使用关键字并返回 或 。asyncTaskTask

代码示例

[HttpGet("{id}")]  
public async Task GetAsync(int id)  
{  
    var result = await _service.GetByIdAsync(id);  
    return Ok(result);  
}

12. 全局处理异常

概念:全局异常处理允许对未处理的异常进行集中式错误处理、日志记录和标准化的 API 响应。

代码示例

app.UseExceptionHandler(a => a.Run(async context =>  
{  
    var exceptionHandlerPathFeature = context.Features.Get();  
    var exception = exceptionHandlerPathFeature.Error;  
    // Log the exception, generate a custom response, etc.  
    context.Response.StatusCode = 500;  
    await context.Response.WriteAsJsonAsync(new { Error = "An unexpected error occurred" });  
}));

13. API 版本控制

概念:API 版本控制有助于管理随时间推移对 API 的更改。.NET 平台支持通过查询字符串、URL 路径或请求标头进行版本控制。

代码示例

builder.Services.AddApiVersioning(options =>  
{  
    options.AssumeDefaultVersionWhenUnspecified = true;  
    options.DefaultApiVersion = new ApiVersion(1, 0);  
    options.ReportApiVersions = true;  
});

14. 内容协商

概念:内容协商允许 API 根据请求中的标头提供不同格式的响应,从而支持 JSON、XML 等格式。Accept

代码示例

builder.Services.AddControllers()  
    .AddXmlDataContractSerializerFormatters();

15. 自定义 JSON 序列化设置

概念:通过配置 JSON 序列化程序设置,自定义 JSON 响应格式,例如 camelCase 命名或忽略 null 值。

代码示例

builder.Services.AddControllers()  
    .AddJsonOptions(options =>  
    {  
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;  
        options.JsonSerializerOptions.IgnoreNullValues = true;  
    });

16. 配置 CORS

概念:跨域资源共享 (CORS) 允许从托管在不同域上的 Web 应用程序调用 API。根据您的要求配置 CORS 策略。

代码示例

builder.Services.AddCors(options =>  
{  
    options.AddPolicy("AllowSpecificOrigin",  
        builder => builder.WithOrigins("http://example.com"));  
});  
app.UseCors("AllowSpecificOrigin");

17. 身份验证

概念:通过启用身份验证来保护 API,身份验证可验证发出请求的用户或服务的身份。

代码示例


builder.Services.AddAuthentication("Bearer")  
    .AddJwtBearer(options =>  
    {  
        options.Authority = "https://your-auth-server";  
        options.Audience = "your-api";  
    });

18. 授权

概念:身份验证后,授权确定经过身份验证的用户是否有权执行操作或访问资源。

代码示例

[Authorize]  
public class SecureController : ControllerBase  
{  
    // Action methods here  
}

19. Swagger/OpenAPI 集成

概念:Swagger (OpenAPI) 为您的 API 提供交互式文档,使开发人员能够轻松理解和使用它。

代码示例

builder.Services.AddEndpointsApiExplorer();  
builder.Services.AddSwaggerGen();  
app.UseSwagger();  
app.UseSwaggerUI();

20. 日志记录

概念:.NET Core 提供了一个内置的日志记录框架,该框架可以将消息记录到各种输出(控制台、调试窗口、外部服务等)。

代码示例

logger.LogInformation("This is an informational message");  
app.Use(async (context, next) =>
{
    logger.LogError("This is an error message before the next middleware");
    await next.Invoke();
    // Log after calling the next middleware
});

21. 使用 Entity Framework Core

概念:Entity Framework Core 是用于 .NET 应用程序中的数据访问的 ORM。它允许您使用强类型对象查询和操作数据。

代码示例

public class MyDbContext : DbContext  
{  
    public MyDbContext(DbContextOptions options) : base(options) {}  
    public DbSet MyModels { get; set; }  
}

22. Entity Framework Core 中的迁移

概念:迁移允许您通过跟踪数据模型中的更改将版本控制应用于数据库架构。

代码示例

dotnet ef migrations add InitialCreate  
dotnet ef database update

23. 存储库模式

概念:Repository 模式将数据层抽象化,使应用程序更加模块化且更易于维护。

代码示例

public interface IRepository  
{  
    Task<IEnumerable> GetAllAsync();  
    Task GetByIdAsync(int id);  
    // Other methods...  
}  
public class MyRepository : IRepository where T : class
{
    private readonly MyDbContext _context;
    public MyRepository(MyDbContext context)
    {
    _context = context;
    }
    // Implement methods...
}

24. 单元测试

概念:
单元测试通过单独测试单个代码单元来确保 Web API 正常运行。

代码示例

public class MyControllerTests  
{  
    [Fact]  
    public async Task Get_ReturnsExpectedValue()  
    {  
        // Arrange  
        var serviceMock = new Mock();  
        serviceMock.Setup(service => service.GetAsync()).ReturnsAsync("test");  
        var controller = new MyController(serviceMock.Object);  
        // Act  
        var result = await controller.Get();  
        // Assert  
        Assert.Equal("test", result.Value);  
    }  
}

25. 与前端集成

概念:.NET Web API 可以充当前端应用程序的后端,提供 RESTful 服务。

代码示例

fetch('https://localhost:5001/mycontroller')  
  .then(response => response.json())  
  .then(data => console.log(data));

26. 健康检查

概念:运行状况检查提供了一种监视应用程序及其依赖项状态的方法,对微服务体系结构非常有用。

代码示例

builder.Services.AddHealthChecks();  
app.MapHealthChecks("/health");

27. 使用 SignalR 进行实时通信

概念:SignalR 启用实时 Web 功能,允许服务器端代码将异步通知发送到客户端 Web 应用程序。

代码示例

public class MyHub : Hub  
{  
    public async Task SendMessage(string user, string message)  
    {  
        await Clients.All.SendAsync("ReceiveMessage", user, message);  
    }  
}

28. 配置响应缓存

概念:响应缓存通过存储以前请求的资源的副本来减少服务器必须处理的请求数。

代码示例

[HttpGet("{id}")]  
[ResponseCache(Duration = 60)]  
public IActionResult GetById(int id)  
{  
    // Retrieve and return your resource  
}

29. 静态文件

概念:提供静态文件(HTML、CSS、JavaScript 等)对于使用 .NET Web API 支持前端应用程序至关重要。

代码示例

app.UseStaticFiles(); // Enable static file serving

30. 高级配置和选项模式

概念:选项模式使用类来表示相关设置的组。使用 ,您可以在应用程序中的任何位置访问这些设置。IOptions

代码示例

public class MySettings  
{  
    public string Setting1 { get; set; }  
    // Other settings  
}  

builder.Services.Configure(builder.Configuration.GetSection("MySettings"));

public class MyService
{
    private readonly MySettings _settings;
    public MyService(IOptions settings)
    {
    _settings = settings.Value;
    }
    // Use _settings.Setting1
}

31. 自定义中间件

概念:中间件是组装到应用程序管道中以处理请求和响应的软件。可以创建自定义中间件来执行特定任务。

代码示例

public class MyCustomMiddleware  
{  
    private readonly RequestDelegate _next;  
    public MyCustomMiddleware(RequestDelegate next)  
    {  
        _next = next;  
    }  
    public async Task Invoke(HttpContext httpContext)  
    {  
        // Pre-processing logic here  
        await _next(httpContext); // Call the next middleware in the pipeline  
        // Post-processing logic here  
    }  
}  
// Extension method for easy middleware registration  
public static class MyCustomMiddlewareExtensions  
{  
    public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder)  
    {  
        return builder.UseMiddleware();  
    }  
}

32. 速率限制

概念:速率限制通过限制用户在特定时间范围内发出请求的频率来保护您的 API 免于过度使用。

代码示例

// Assume using a third-party library like AspNetCoreRateLimit  
builder.Services.AddInMemoryRateLimiting();  
builder.Services.Configure(options =>  
{  
    options.GeneralRules = new List  
    {  
        new RateLimitRule  
        {  
            Endpoint = "*",  
            Limit = 100,  
            Period = "1h"  
        }  
    };  
});

33. API 密钥身份验证

概念:API 密钥是验证和授权 API 调用的简单方法。它们在查询字符串或标头中从客户端传递到服务器。

代码示例

public class ApiKeyMiddleware  
{  
    private readonly RequestDelegate _next;  
    private const string APIKEYNAME = "x-api-key";  
    public ApiKeyMiddleware(RequestDelegate next)  
    {  
        _next = next;  
    }  
    public async Task Invoke(HttpContext context)  
    {  
        if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))  
        {  
            context.Response.StatusCode = 401;  
            await context.Response.WriteAsync("API Key was not provided.");  
            return;  
        }  
        // Validate the extracted API Key here...  
        await _next(context);  
    }  
}

34. 输出缓存

概念:输出缓存允许您存储对请求的响应。可以从缓存中处理后续请求,从而显著提高性能。

代码示例

[HttpGet]  
[ResponseCache(Duration = 120, Location = ResponseCacheLocation.Client, NoStore = false)]  
public IActionResult Get()  
{  
    // Your logic here  
}

35. 后台任务

概念:后台任务使操作能够在后台运行,独立于用户请求,例如发送电子邮件或处理长时间运行的作业。

代码示例

public class MyBackgroundService : BackgroundService  
{  
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)  
    {  
        while (!stoppingToken.IsCancellationRequested)  
        {  
            // Your background task logic here  
            await Task.Delay(TimeSpan.FromHours(1), stoppingToken);  
        }  
    }  
}

36. 网络套接字

概念:WebSocket 通过单个长期连接提供全双工通信通道,是实时应用程序的理想选择。

代码示例

app.UseWebSockets();  
app.Use(async (context, next) =>  
{  
    if (context.WebSockets.IsWebSocketRequest)  
    {  
        WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();  
        // Handle the WebSocket request here  
    }  
    else  
    {  
        await next();  
    }  
});

37. 请求本地化

概念:请求本地化提供了一种基于请求信息针对不同文化和语言本地化内容的方法。

代码示例

var supportedCultures = new[] { "en-US", "fr-FR" };  
var localizationOptions = new RequestLocalizationOptions()  
    .SetDefaultCulture(supportedCultures[0])  
    .AddSupportedCultures(supportedCultures)  
    .AddSupportedUICultures(supportedCultures);  
app.UseRequestLocalization(localizationOptions);

38. 与 GraphQL 集成

概念:GraphQL 是 API 的查询语言。将 .NET Web API 与 GraphQL 集成可以更高效地检索数据。

代码示例

// Assume using a library like HotChocolate  
builder.Services  
    .AddGraphQLServer()  
    .AddQueryType();  
app.MapGraphQL();

39. 监控和遥测

概念:监视和遥测涉及收集、分析和处理有关应用程序性能和使用情况的数据。

代码示例

// Assume using Application Insights  
builder.Services.AddApplicationInsightsTelemetry("YOUR_INSTRUMENTATION_KEY");

40. SignalR 集线器和实时通信

概念:SignalR 是一个库,可简化向应用添加实时 Web 功能的过程。实时 Web 功能是指服务器代码在内容发生时立即将内容推送到连接的客户端的能力,而不需要服务器等待客户端请求新数据。SignalR 非常适合开发聊天应用程序、实时仪表板和更具交互性的 Web 应用程序。

代码示例

public class ChatHub : Hub  
{  
    public async Task SendMessage(string user, string message)  
    {  
        // Call the broadcastMessage method to update clients.  
        await Clients.All.SendAsync("broadcastMessage", user, message);  
    }  
}  
// Startup or Program.cs

app.MapHub("/chathub");

集成Program.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
{  
    // Other configurations...  
    app.UseEndpoints(endpoints =>  
    {  
        endpoints.MapHub("/chathub");  
    });  
}

41. 高级实体框架核心 — 关系

概念:Entity Framework Core 允许映射实体之间的复杂关系,例如一对一、一对多和多对多。

代码示例

public class Author  
{  
    public int AuthorId { get; set; }  
    public string Name { get; set; }  
    public ICollection\ Books { get; set; }  
}  
public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public int AuthorId { get; set; }
    public Author Author { get; set; }
}

42. 自定义验证属性

概念:自定义验证属性允许您定义数据模型的验证逻辑,从而扩展内置验证属性。

代码示例

public class MyCustomValidationAttribute : ValidationAttribute  
{  
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)  
    {  
        // Your custom validation logic here  
        if (value is int intValue && intValue > 0)  
        {  
            return ValidationResult.Success;  
        }  
        return new ValidationResult("Value must be positive");  
    }  
} 
public class MyModel
{
    [MyCustomValidationAttribute]
    public int MyProperty { get; set; }
}

43. 高级配置方案

概念:.NET 的选项模式支持复杂的配置方案,包括嵌套对象、列表和验证。

代码示例

public class MyOptions  
{  
    public MyNestedOptions Nested { get; set; }  
    public List Items { get; set; }  
}  
public class MyNestedOptions  
{  
    public string Key { get; set; }  
}  
// In Program.cs or Startup.cs  
builder.Services.Configure(builder.Configuration.GetSection("MyOptions"));

44. 性能监控和分析

概念:监视和分析应用程序可以识别瓶颈和效率低下,这对于优化性能至关重要。

代码示例

app.UseMiniProfiler();

45. 带有 Swagger 和 XML 注释的 API 文档

概念:通过将 XML 注释集成到 Swagger UI 中来增强 API 文档,为使用 API 的开发人员提供更丰富的体验。

代码示例

builder.Services.AddSwaggerGen(c =>  
{  
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";  
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);  
    c.IncludeXmlComments(xmlPath);  
});

46. 全球化和本地化

概念:全球化和本地化使您的应用程序能够支持多种语言和文化,使其可供全球受众访问。

代码示例

builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");  
app.UseRequestLocalization(app.Services.GetRequiredService<IOptions>().Value);

47. 安全标头

概念:通过添加各种 HTTP 标头来提高 Web 应用程序的安全性可以防止常见的攻击和漏洞。

代码示例

app.UseHsts();  
app.UseXContentTypeOptions();  
app.UseReferrerPolicy(opts => opts.NoReferrer());  
app.UseXXssProtection(options => options.EnabledWithBlockMode());  
app.UseXfo(options => options.Deny());

48. 功能标志

概念:功能标志允许您在不部署新代码的情况下打开和关闭应用程序的功能,从而简化测试和推出。

代码示例

// Using a library like Microsoft.FeatureManagement  
builder.Services.AddFeatureManagement();

49. Blazor 集成

概念:Blazor 允许使用 C# 而不是 JavaScript 生成交互式 Web UI。将 Blazor 与 Web API 集成可提供无缝的全栈开发体验。

代码示例

// In a Blazor Server app  
@code {  
    private IEnumerable forecasts;  
    protected override async Task OnInitializedAsync()  
    {  
        forecasts = await Http.GetFromJsonAsync<WeatherForecast\[\]>("WeatherForecast");  
    }  
}

50. 用于响应压缩的高级中间件

概念:响应压缩可以减小 API 响应的大小,从而缩短客户端在慢速网络上的加载时间。

代码示例

builder.Services.AddResponseCompression(options =>  
{  
    options.Providers.Add();  
    options.EnableForHttps = true;  
});  

app.UseResponseCompression();

原文:https://www.cnblogs.com/star8521/p/18209870

NET 7 Design Patterns In-Depth 15. Base Design Patterns

Chapter 15
Base Design Patterns
Introduction
The design patterns of the base design patterns category can be divided into the following eleven main sections:

Gateway: It can encapsulate access to an external resource or system.
Mapper: It can communicate between two independent objects.
Layer supertype: A series of classes are considered as the parent of the rest of the classes, and in this way, common features and behaviors are placed in the parent class.
Separated interface: It can put interfaces and their implementations in different packages.
Registry: It can find and work with frequently used objects and services through an object.
Value object: It can put the values in the objects and then compare the objects based on their internal values.
Money: It can facilitate working with monetary values.
Special case: It can add a series of behaviors to the parent class in special situations.
Plugin: It can hold classes during configuration instead of compilation time.
Service stub: It can eliminate dependency on problematic services during testing.
Record set: It can provide a representation in memory of tabular data.
Structure
In this chapter, we will cover the following topics:

Gateway
Mapper
Layer supertype
Separated interface
Registry
Value object
Money
Special case
Plugin
Service stub
Record set
Objectives
In this chapter, you will get acquainted with base design patterns and learn how to implement other design patterns in a more suitable way with the help of these design patterns. You will also learn how to solve frequent problems in the software process with the help of these design patterns.

Base design patterns
Different design patterns can be used in the software development process to solve different problems. Each design pattern has its implementation method and can be used for specific issues.

Among them, some design patterns are the basis of other design patterns or the basis of solving many problems. These types of design patterns are called base design patterns. Today, programmers may deal with many of these design patterns daily.

Gateway
Name:

Gateway

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, access to an external resource or system can be encapsulated.

Motivation, Structure, Implementation, and Sample code:

Suppose that we need to communicate with a Web API in our program, there are various methods to connect to this Web API, but the best method will be to implement the method of connecting and communicating with this Web API in one place so that the rest of the program can connect to it and use it without needing to know the details of the connection with the Web API.

To implement the above scenario, the following codes can be considered:

public class WebAPIGateway

{

private static readonly HttpClient HttpClient;

static WebAPIGateway() => HttpClient = new HttpClient();

public static async Task GetDataAsync(string url)

{

try

{

var response = await HttpClient.GetAsync(url);

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync();

}

catch

{

throw new Exception("Unable to get data");

}

}

}

As seen in the preceding code, in order to connect to WebAPI, the way to receive information from it is encapsulated inside the WebAPIGateway class. The rest of the program can easily use the data of this Web API through WebAPIGateway.

The above code can be used as follows:

var result = await WebAPIGateway.GetDataAsync

("https://jsonplaceholder.typicode.com/posts");

As it is clear in the above code, when using the GetDataAsync method, it is enough to send the service address to this method, and the access details to the external resource are encapsulated.

Notes:

The Gateway should be as simple as possible and designed and implemented in line with business requirements.
This design pattern is very similar to facade and adapter design patterns, but in terms of purpose, they have differences. For example, the facade design pattern presents a different view of what it covers (for example, it presents the system's complexity as a simple view to the client), but the gateway may present the same view. Also, the adapter design pattern tries to connect two classes that are not structurally compatible with each other and work with them. On the other hand, it is possible to use an adapter design pattern in the gateway implementation.
Consequences: Advantages

Code readability improves in the rest of the program.
It becomes easier to test the program.
Using this design pattern, external resources or systems can be changed without changing the code of other program parts.
Consequences: Disadvantages

Due to its similarity with other design patterns, it may not be used in its proper place and damage the design.
Applicability:

In order to connect to external resources or systems, this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the gateway design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Facade
Adapter
Mapper
Name:

Mapper

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern makes it possible to establish a connection between two independent objects.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a need to communicate between the model and the database. This relationship has already been shown in the data mapper design pattern. As seen in the data mapper design pattern, the model and the database are unaware of each other's existence. Also, both sections are unaware of the mapper's existence. Using the data mapper design pattern is one of the most specific uses of the mapper design pattern.

Notes:

This design pattern is very similar to the gateway design pattern. Mapper is often used when the desired objects do not depend on each other (For example, communicating with a database).
This design pattern can be very similar to the mediator design pattern. The difference is that in this design pattern, the objects that the mapper intends to communicate with are unaware of the existence of the mapper.
In order to be able to use a mapper, there is usually another object that uses a mapper and communicates with other objects. Another way to use a mapper is to combine this design pattern with the observer design pattern. In this case, the mapper can listen to the events in the objects and do something when those events occur.
Consequences: Advantages

Different objects can be connected without creating dependencies on each other.
Consequences: Disadvantages

In terms of its similarity with other design patterns, it may not be used in the right place and cause damage to the design.
Applicability:

It can be useful in order to communicate between objects that are not related to each other.
Related patterns:

Some of the following design patterns are not related to the mapper design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Data mapper
Gateway
Mediator
Observer
Layer supertype
Name:

Layer supertype

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, a series of classes are considered as the parent of the rest of the classes, and in this way, common features and behaviors are placed in the parent class.

Motivation, Structure, Implementation, and Sample code:

Suppose that we are defining domain models. All models must have a property called ID. To implement this requirement, the definition of this feature can be assigned to any of the classes, or the ID property can be placed in a class so that other domain models can inherit from this class, as shown below:

public abstract class BaseModel

{

public int Id { get; set; }

}

public class Author : BaseModel

{

public string FirstName { get; set; }

}

As it is clear in the preceding code, the ID property has been transferred to the BaseModel class, and any class that needs an ID can inherit from this class.

Notes:

This design pattern can be combined with Generics in C# for better usability.
You can have several layer supertypes in each layer of the software.
Using this design pattern to define the identity field in models is very useful.
Consequences: Advantages

Using this design pattern will reduce duplicate codes, making it easier to maintain the code.
Consequences: Disadvantages

If this design pattern is not used correctly, it will confuse the code structure. For example, imagine if the BaseModel class has an ID property with an int data type, and the Book class inherits from BaseModel class. But we do not want the ID data type to be int (but we want it to have access to other behaviors in the BaseModel). Then it will be required to add another property to this class, or it will be required to ignore the provided property of the parent class by using the hiding feature (public new string Id { get; set; }), which will make the code confusing and will reduce the readability and maintainability of the code.
Applicability:

This design pattern can be used when there are common features or behaviors between classes.
Related patterns:

Some of the following design patterns are not related to layer supertype design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Identity field
Separated interface
Name:

Separated interface

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, interfaces, and their implementation are placed in different packages.

Motivation, Structure, Implementation, and Sample code:

Suppose that we are designing a program. In order to have a better design, reducing the dependency between different parts of the program can be a very important factor. One of the ways to reduce dependency is to use interfaces and then provide implementation based on these interfaces.

This separation itself has different states, including:

Interfaces and related implementations are in one package. Refer to the following figure:
Figure%2015.1.png
Figure 15.1: Interface and its implementation in the same package

namespace Package

{

public class Client

{

public IUnitOfWork UnitOfWork { get; set; }

}

public interface IUnitOfWork

{

bool Commit();

}

public class UnitOfWork : IUnitOfWork

{

public bool Commit() => true;

}

}

As it is clear in the above code, classes one and two are all in one package. In the above code, the namespace is assumed as a package.

Interfaces and related implementations are in different packages:
Interfaces and clients are in one package: Using this method can be easier if only one or all clients are in one package. In fact, in this method, the client is responsible for defining the interfaces, and the client will be able to work with any package that implements the defined interfaces.

Figure%2015.2.png
Figure 15.2: The interface and the client are in the same package,
but the implementation is in a different package

In the following code, it can be seen that Client and IUnitOfWork are in Package01 and the implementation of IUnitOfWork is placed in the UnitOfWork class defined in Package02.

namespace Package01

{

public class Client

{

public IUnitOfWork UnitOfWork { get; set; }

}

public interface IUnitOfWork

{

bool Commit();

}

}

namespace Package02

{

public class UnitOfWork : Package01.IUnitOfWork

{

public bool Commit() => true;

}

}

Interfaces and clients are also in different packages: If there are different clients, it is better to separate the package related to interfaces and clients. Refer to the following figure:

Figure%2015.3.png
Figure 15.3: Interface, client, and implementation all are in different packages

Notes:

Using interface keywords or programming language capability to define the interfaces is unnecessary. Sometimes using abstract classes is a much better option; you can put default implementations in them. From version 8 of the C# language, it is possible to provide default implementations for interfaces too.
Initialling the object from implementation is noteworthy in this design pattern. To make this easier, you can use the factory design pattern or assign the object initialization to another package. The appropriate object will be created in that package according to the related interface and implementation.
Consequences: Advantages

Dependencies between different parts of the program are reduced.
Consequences: Disadvantages

Using this design pattern in all parts of the program will only increase the complexity and volume of the code.

Applicability:

This design pattern can be used when there is a need to loosen the dependency between two parts of the system or to provide different implementations for an interface.
When there is a need to communicate with another module, this design pattern can be used so that communication can be established without depending on the implementation of that module.
Related patterns:

Some of the following design patterns are not related to the separated interface design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Factory
Registry
Name:

Registry

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, you can find and work with frequently used objects and services through an object.

Motivation, Structure, Implementation, and Sample code:

Suppose that we want to get all the books of an author. We will probably access their books through the author object to implement this requirement. In fact, through the author object, we can access the books. Now the question is, if we do not have the author object or there is no connection between the author and the book, how can we get an author's books? This is exactly where the registry design pattern comes in handy.

To implement this design pattern, you can do as follows:

public class Registry

{

private static Registry _registry = new();

}

In this implementation, we intend to implement this design pattern using the singleton design pattern. As it is clear in the preceding code, we have defined _registry as static so that we have only one instance of this class. We have also considered its access level private because the user does not need to be involved with the implementation details. Now we need to provide an object to the user so that they can work with the book. For this purpose, the Registry class can be changed as follows:

public class Registry

{

private static Registry _registry = new();

protected BookFinder bookFinder = new();

public static BookFinder BookFinder() => _registry.bookFinder;

}

If you pay attention to the preceding code, we have defined BookFinder as protected. The reason for this is that later we can change the instantiation of this object. Testing is one of the most specific modes in the service stub design pattern. Also, the BookFinder method is defined static, making it easy to work with the Registry class.

Considering the preceding implementation, suppose we also want to design a solution to test these codes. For this, you can proceed as follows:

public class RegistryStub : Registry

{

public RegistryStub() => base.bookFinder = new BookFinderStub();

}

As you can see, the RegistryStub class has inherited from the Registry class. Inside the constructor of this class, we instantiate the bookFinder object in the parent class using the BookFinderStub class. Now, to access the RegistryStub class, you can put the following method in the Registry class:

public static void InitializeStub() => _registry = new RegistryStub();

Or it may even be necessary somewhere in the program to re-instance the _registry. For this purpose, the following method can be defined in the Registry class:

public static void Initialize() => _registry = new Registry();

Notes:

This design pattern can also be implemented as thread safe.
Since we have defined the methods as static in the preceding implementation, there is no reason to define the variables or fields as static. This decision can be different according to the nature of the data and object. One of the applications for defining static variables or fields is the presence of static data, such as a list of cities or provinces and the like.
Paying attention to the scope of data in this design pattern is very important. According to the data type or object, their scope can be variable (at the process, thread, and session levels). Different implementations can be provided according to the scope of data or object. Diversity in the scope can also cause the emergence of a registry class for each scope or one Registry class for all scopes.
Using singleton implementation for mutable data in a multi-thread environment can be inappropriate. Using a singleton for process-level data that cannot be changed (such as a list of cities or provinces) is better.
For data in the scope of one thread (such as a connection to a database), using thread-specific storage resources such as the ThreadLocal class in C# can be very useful. Another solution is to use structures like a dictionary, where the key can be the thread ID.
As in the previous case, you can use a dictionary for data with a session scope. In this case, the key will be the session ID. Also, a thread's session data can still be placed in ThreadLocal.
Passing shared data as a method parameter or adding a reference to shared data as a property to the class are alternative methods of using the registry design pattern. Each of these methods has its problems. For example, if data is passed to a method as a parameter, there may be situations where this method needs to be called through other methods in the Call Tree. In this case, adding a parameter to the method will not be a good approach, and the registry will be better. Also, in the case of adding a reference to shared data as a class property, the preceding problem regarding the class constructor will still exist.
Consequences: Advantages

This design pattern allows you to easily access commonly used or shared data or objects.
Consequences: Disadvantages

Implementing this design pattern can be complicated due to the scope and nature of the data.
The use of this design pattern is often in confirming and tolerating mistakes in the design. You should always try to access the data in the objects through intra-object communication, and using the registry design pattern should be the last option.
It becomes difficult to test the code using this design pattern. Because if the data or objects presented in this design pattern change, the test results can change, and therefore the complexity of writing the test increases.
Applicability:

When providing a series of common data or objects to the rest of the program is necessary, using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the registry design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Singleton
Service stub
Value object
Name:

Value object

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, you can put the values in the objects, then compare them based on their internal values.

Motivation, Structure, Implementation, and Sample code:

Suppose we need to store different addresses for publishers in our program and then compare these with each other. In the desired scenario, an address consists of a city, main street, sub-street, and alley. A simple way is to design our model as follows:

public class Publisher

{

public string Title { get; set; }

public List

Addresses { get; set; }

}

public class Address

{

public string City { get; set; }

public string MainStreet { get; set; }

public string SubStreet { get; set; }

public string Alley { get; set; }

}

According to the preceding structure, suppose we have two different addresses, and we want to compare them:

Address address1 = new()

{

City = "Shahid beheshti",

MainStreet = "Kavoosifar",

SubStreet = "Nakisa",

Alley = "Rahnama"

};

Address address2 = new()

{

City = "Shahid beheshti",

MainStreet = "Kavoosifar",

SubStreet = "Nakisa",

Alley = "Rahnama"

};

As it is known, both address1 and address2 objects have the same content. With this assumption, we want to check that only one is saved if two addresses are identical. With this assumption, we write the following code:

if (address1 == address2)

publisher.Addresses.Add(address1);

else

publisher.Addresses.AddRange(new List

{ address1, address2 });

By executing the previous code, address1 is not equal to address2. This is because for reference types, instead of comparing values, the addresses of objects are compared.

Using the value object design pattern, we have previously transferred the values into the object (we have placed city and street properties into the Address class). Now, we need a mechanism to compare the values of these objects. For this, you can change the Address class as follows and add the following methods to it:

public static bool operator == (Address address1, Address address2)

=> address1.City == address2.City

&& address1.MainStreet == address2.MainStreet

&& address1.SubStreet == address2.SubStreet

&& address1.Alley == address2.Alley;

public static bool operator !=(Address address1, Address address2)

=> address1.City != address2.City

|| address1.MainStreet != address2.MainStreet

|| address1.SubStreet != address2.SubStreet

|| address1.Alley != address2.Alley;

As it is clear in the preceding codes, we have rewritten the == and != operators in order to present our method for comparing two objects. Now, if we run the following code again, we will find that the comparison results of address1==address2 are true:

if (address1 == address2)

publisher.Addresses.Add(address1);

else

publisher.Addresses.AddRange(new List

{ address1, address2 });

Another point is that if we create two objects with equal values, we will face two HashCodes. For example, consider the following commands:

Address address1 = new("Rahnama","Nakisa","Kavoosifar","Shahid beheshti");

Address address2 = new("Rahnama","Nakisa","Kavoosifar","Shahid beheshti");

In the preceding two lines, two different objects of the Address class are created, but their values are equal. Therefore, we will face two different HashCodes. We may want objects with equal values to generate equal HashCodes. In this case, we must rewrite the GetHashCode method in the Address class as follows:

public override int GetHashCode() =>

City.GetHashCode() ^

MainStreet.GetHashCode() ^

SubStreet.GetHashCode() ^

Alley.GetHashCode();

As seen in the preceding code, to generate the HashCode, the HashCode values of different properties have been combined using XOR(^) and produced the final HashCode. The advantage of the XOR combination is that the result of A XOR B always equals B XOR A.

Another solution to solve this problem is to use struct instead of class. In this case, there was no need to rewrite the operators == and != and GetHashCode.

Notes:

One of the most important differences between reference and value types is how they deal with comparison operations. When using value objects, the comparison operation must be based on values.
Value objects should be immutable. Otherwise, for example, two publishers may refer to the same address. Therefore, for any publisher who changes the address, this change should also be applied to the other publisher. For this purpose, the properties of the Address model can be changed as follows:
public string City { get; private set; }

public string MainStreet { get; private set; }

public string SubStreet { get; private set; }

public string Alley { get; private set; }

public Address(string city,string mainStreet,string subStreet,string alley)

{

City = city;

MainStreet = mainStreet;

SubStreet = subStreet;

Alley = alley;

}

In this case, the objects created from the Address class cannot be changed. There are other ways to make it immutable.

Consequences: Advantages

Using this design pattern will increase the readability of the code.
Logics such as validation can be encapsulated.
It provides the possibility of type safety.
Consequences: Disadvantages

This design pattern will increase the number of classes and objects in the program.
Applicability:

This design pattern is useful when comparing multiple properties using the == operation.
This design pattern will be useful when faced with objects that only complete the body and concept of other objects. For example, the Address object helps to complete the Publisher object.
When there are a series of parameters or data that are always used together and produce the same meaning. For example, the position of a point on the page (X position and Y position).
Related patterns:

Value object design pattern is very important and useful, which can be the basis of many design patterns. Also, in domain-driven design, this design pattern is highly useful.

Money
Name:

Money

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern can facilitate working with monetary values.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing a program in which the currency type Rial and Dollar will be used. To implement this requirement, one method is to store the amount of money and its unit in one of the class properties. But the problem will occur when we want to work with these monetary amounts. For example, add or subtract a value to it. A bigger problem will occur when multiplying or dividing numerically on these monetary values.

A better solution is to use a class to implement all the complexity and related details. Wherever we need to use monetary values, we can instantiate this class and use that instance. In other words, the Money class will be a value object. To implement the preceding requirement, suppose we have the following enum:

public enum Currency

{

Rial,

Dollar

}

In the preceding enum, we have defined different types of monetary units. According to the preceding enum, the Money class can be defined as follows:

public class Money

{

public int Amount { get; private set; }

public int Fraction { get; private set; }

public Currency Currency { get; private set; }

public Money(int amount, int fraction, Currency currency)

{

Currency = currency;

(int NewAmount, int NewFraction) = NormalizeValues(amount, fraction);

Amount = NewAmount;

Fraction = NewFraction;

}

private (int NewAmount, int NewFraction) NormalizeValues(

int amount, int fraction)

=> NormalizeValues((double)amount, (double)fraction);

private (int NewAmount, int NewFraction) NormalizeValues(

double amount, double fraction)

{

if (Currency == Currency.Rial)

fraction = 0;

else if (Currency == Currency.Dollar)

{

double totalCents = amount * 100 + fraction;

amount = totalCents / 100;

fraction = totalCents % 100;

}

return ((int)amount, (int)fraction);

}

}

In the preceding class, the Amount property represents the integer part of the value, and the Fraction represents its decimal part. As we know, the values do not have a decimal part in Rial currency, so it has been checked in the NormalizedValues method so that if the currency unit was Rial, the value of the decimal part is considered zero. The Currency property in this class also represents the monetary unit.

In the following, we will rewrite the Equals method to compare two Money objects.

public override bool Equals(object? other)

=> other is Money otherMoney && Equals(otherMoney);

public bool Equals(Money other)

=> Currency.Equals(other.Currency)

&& Amount == other.Amount && Fraction == other.Fraction;

The condition for two Money objects to be the same is that both objects have the same monetary unit, and the Amount and Fraction values are equal. In the same way, the ( ==, !=, < and > ) operators can also be rewritten.

public static bool operator ==(Money a, Money b) => a.Equals(b);

public static bool operator !=(Money a, Money b) => !a.Equals(b);

public static bool operator >(Money a, Money b)

{

if (a.Currency == b.Currency)

{

if (a.Amount > b.Amount)

return true;

else if (a.Amount == b.Amount && a.Fraction > b.Fraction)

return true;

else

return false;

}

return false;

}

public static bool operator <(Money a, Money b) { if (a.Currency == b.Currency) { if (a.Amount < b.Amount) return true; else if (a.Amount == b.Amount && a.Fraction < b.Fraction) return true; else return false; } return false; } public static bool operator >=(Money a, Money b)

{

if (a.Currency == b.Currency)

{

if (a.Amount > b.Amount)

return true;

else if (a.Amount == b.Amount)

{

if (a.Fraction > b.Fraction || a.Fraction == b.Fraction)

return true;

}

else

return false;

}

return false;

}

public static bool operator <=(Money a, Money b) { if (a.Currency == b.Currency) { if (a.Amount < b.Amount) return true; else if (a.Amount == b.Amount) { if (a.Fraction < b.Fraction || a.Fraction == b.Fraction) return true; } else return false; } return false; } As mentioned before, Money is a type of value object, and therefore the GetHashCode method must be rewritten as follows: public override int GetHashCode() => Amount.GetHashCode() ^ Fraction.GetHashCode() ^ Currency.GetHashCode();

Now we need to use a method to increase or decrease the amount of money in a Money object. For this, the following codes can be considered:

public void Add(Money other)

{

if (Currency == other.Currency)

{

int a = Amount + other.Amount;

int f = Fraction + other.Fraction;

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

else

throw new Exception("Unequal currencies");

}

public void Subtract(Money other)

{

if (Currency == other.Currency)

{

int a = Amount - other.Amount;

int f = Fraction - other.Fraction;

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

else

throw new Exception("Unequal currencies");

}

As it is clear in the implementation of the preceding methods, the condition of adding or subtracting is that the monetary units are the same. Also, similar methods can be considered for multiplication and division. The important thing about the multiplication and division methods is to pay attention to the decimal nature of the number, which we want to multiply by the Amount and Fraction values. The following is the simple implementation of the Multiply method:

public void Multiply(double number)

{

number = Math.Round(number, 2);

double a = Amount * number;

double f = Math.Round(Fraction * number);

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

For example, the input number is rounded to a number with two decimal places in the preceding implementation. Or in the calculation of f, the number of cents is rounded because, for example, in the Dollar currency, we do not have anything called one and a half cents. Also note that in the preceding implementations, in terms of ease of learning, simple examples with the least details are given. More details may be needed in operational tasks, or combining this design pattern with other design patterns is necessary.

Notes:

When using this design pattern, you should consider the decimal values and how to treat these values.
You can convert monetary units to each other using this design pattern and exchange rates.
Consequences: Advantages

Increases readability and facilitates code maintenance.
It causes the implementation of meaningful mechanisms for working with monetary items.
Consequences: Disadvantages

Using this design pattern can have a negative effect on efficiency, and most of the time, this effect will be very small.
Applicability:

You can benefit from this design pattern when using several monetary units in the program.
Related patterns:

Some of the following design patterns are not related to money design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Value object
Special case
Name:

Special case

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, a series of behaviors can be added to the parent class in special situations.

Motivation, Structure, Implementation, and Sample code:

Suppose a code is written inside a method that can be used to get the author's ID, retrieve their information from the database, and return it. The important thing here is what should be returned if no author for the given ID is found. There are usually three ways to answer this question:

Use of exceptions
Return NULL
Returning a specific object
Using method 1 has a negative effect on efficiency. Using an exception in these cases is not a good decision because as soon as the exception occurs, .NET stops everything to process the exception.

Method 2 seems to be suitable and is often used. But by using this method, it will be necessary to write different codes to check the returned response, and, in this way, we will find out whether the response is NULL. Usually, this method will need to inform the user that the author's ID is wrong through method 1.

Using method 3, this scenario is more suitable. Using this method, an object of a specific type is returned. This special type has a series of features, among the most important of which it can be mentioned that this special type inherits from the main type in question.

According to the preceding description, suppose we have the Author model as follows:

public class Author

{

public virtual int AuthorId { get; set; }

public virtual string FirstName { get; set; }

public virtual string LastName { get; set; }

public override string ToString() => $"{FirstName} {LastName}";

}

Note that in the preceding model, we have defined the features as virtual so that we can rewrite them later. Also, suppose the following method returns information related to the author after receiving the author's ID:

public class AuthorRepository

{

private readonly List authorList = new()

{

new Author{AuthorId=1,FirstName="Vahid",LastName="Farahmandian"},

new Author{AuthorId=2,FirstName="Ali",LastName="Mohammadi"}

};

public Author Find(int authorId)

{

Author result = authorList.FirstOrDefault(x => x.AuthorId == authorId);

return result;

}

}

As specified in the Find method, a NULL value is returned if the author ID does not exist in the current implementation. We need to define a special type with the mentioned conditions to correct this problem. Therefore, we define the AuthorNotFound class as follows:

public class AuthorNotFound : Author

{

public override int AuthorId { get => -1; set { } }

public override string FirstName { get => ""; set { } }

public override string LastName { get => ""; set { } }

public override string ToString() => "Author Not Found!";

}

As seen in the preceding code, the AuthorNotFound class has inherited from the Author class, so this class is considered a special type of the Author class. Now, with this class, you can rewrite the Find method in the AuthorRepository class as follows:

public Author Find(int authorId)

{

Author result = authorList.FirstOrDefault(x => x.AuthorId == authorId);

if (result == null)

return new AuthorNotFound();

return result;

}

The preceding code says that if the author ID is unavailable, an object of type AuthorNotFound will be returned. When using this code, you can use it as follows:

Author searchResult = new AuthorRepository().Find(3);

if (searchResult is AuthorNotFound)

{

Console.WriteLine("Author not found!");

}

After receiving the response in the preceding code, it is checked whether the return type is AuthorNotFound or not.

Notes:

You can often use the flyweight design pattern to implement this design pattern. In this case, the amount of memory used will also be saved.
This design pattern is very similar to the null object design pattern. But a null object can be considered a special case of this design pattern. There are no behaviors in the null object design pattern, or these behaviors do nothing. But in the special case design pattern, behaviors can do certain things. For example, in the preceding scenario, the ToString() method in the AuthorNotFound class returns a meaningful message to the user.
Among the most important applications of this design pattern, we can mention the implementation of infinite positive/negative values or NaN in working with numbers.
Consequences: Advantages

By using this design pattern, complications caused by NULL values can be avoided.
Consequences: Disadvantages

Sometimes returning a NULL value will be a much simpler solution, and using this design pattern will increase the complexity of the code as well.
Using this design pattern when critical events occur can cause the program to face problems. In these cases, using exceptions will be the best option.
Applicability:

In order to return the results of mechanisms such as search, this design pattern can be used.
Related patterns:

Some of the following design patterns are not related to special case design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Flyweight
Null object
Plugin
Name:

Plugin

Classification:

Base design patterns

Also known as:

---

Intent:

This design pattern allows classes to be held during configuration instead of at compile time.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a method through which a log can be recorded. The behavior of this method is different for test and operational environments. In the test environment, we want to save the logs in a text file, but in the operational environment, we want to save the logs in the database. To implement this requirement, one method is to identify the environment using condition blocks in the mentioned method and perform the desired work based on the environment. The problem with this method is that if we need to check the environment for various tasks, we will end up with messy and complicated code. In addition, if the settings are changed, the code must be changed, recompiled, and finally deployed in the production environment.

Another method is to use the plugin design pattern. This design pattern helps select and execute the appropriate implementation based on the environment using centralized runtime settings. The Figure 15.4 sequence diagram shows the generality of this design pattern:

Figure%2015.4.png
Figure 15.4: Plugin design pattern sequence design pattern

As you can see in Figure 15.4, the request to receive the plugin is given to the Plugin Factory. Based on the requested type, Plugin Factory searches for plugins in the settings and creates an object of that plugin type if it finds the corresponding plugin.

The first step in implementing the plugin design pattern is to use the separated interface design pattern.

public interface ILog

{

void Execute();

}

The preceding code shows the interface that any class that wants to do logging must implement it:

public class TxtLogger : ILog

{

public void Execute()

=> File.AppendAllText(

$"E:\\log\\{Guid.NewGuid()}.txt",

$"Executed at: {DateTime.Now}");

}

public class SqlServerLogger : ILog

{

public void Execute()

=> new SqlCommand($"" +

$"INSERT INTO log (Content)" +

$"VALUES(N'Executed {DateTime.Now}')",

DB.Connection).ExecuteNonQuery();

}

TxtLogger and SqlServerLogger classes are intended for recording logs in a file and database. Both classes have implemented the ILog interface.

Now that we have the classes, we need a class to select and return the appropriate class by referring to the settings. In this scenario, we have put the settings in the files test.props.json and prod.props.json as follows:

{

"logging":

[

{

"interface": "ILog",

"implementation": " MyModule.Log.Plugin.TxtLogger",

"assembly": "MyModule"

}

]

}

As it is clear in the preceding JSON content, it is said that when working with the logging module, the implementation of the ILog interface is in the TxtLogger class in the MyModule assembly. If the environment changes, it will be enough to load the prod.props.json file instead of the test.props.json file.

Now, we need the PluginFactory class so that through this class, the appropriate class can be identified and instantiated based on the environment. For this purpose, consider the following codes:

public class PluginFactory

{

private static readonly List configs;

static PluginFactory()

{

var jsonConfigs =

JObject.Parse(File.ReadAllText(@$"{Environment.Name}.props.json"));

configs = JsonConvert.DeserializeObject>

(jsonConfigs.SelectToken("logging").ToString());

}

public static ILog GetPlugin(Type @interface)

{

ConfigModel config = configs.FirstOrDefault(

x => x.Interface == @interface.Name);

if(config == null)

throw new Exception("Invalid interface");

return (ILog)Activator.CreateInstance(

config.Assembly, config.Implementation).Unwrap();

}

}

As seen in the preceding code, in the static constructor of the class, the settings are read from the JSON file. Next, the GetPlugin method selects the desired settings from the list of settings, and then the corresponding object is created and returned.

Thanks to the possibility of having a default implementation in interfaces in C# language, the GetPlugin method can be used without making any changes in the subset classes. With this explanation, the ILog interface changes as follows:

public interface Ilog

{

public static Ilog Instance = PluginFactory.GetPlugin(typeof(Ilog));

void Execute();

}

In the preceding code, it has been tried to combine the plugin design pattern with the singleton design pattern and increase the readability of the code. To use these codes, you can do the following:

ILog.Instance.Execute();

As you can see, when referring to Instance in ILog, the appropriate plugin is selected, and its Execute method is executed. Now, with this design, at the time of execution, you can change the settings and see the results without the need to make changes in the code and build and deploy again.

Notes:

Settings can be saved in different formats and presented through different data sources.
The setting of the environment can be done in different ways, including JSON files available in the project configuration, YAML files available for pipelines in CI/CD processes, and sending parameters through the CLI.
The important thing about this design pattern is that the interface and the implementation communication should be done dynamically from execution.
Usually, different plugins are in different library files, and the implementation does not need to be in the same interface assembly.
This design pattern is closely related to the Separated Interface design pattern. In fact, you can implement interfaces in other modules or files using a Separated Interface design pattern.
When presenting the assembly containing the intended implementation, attention should be paid to dependencies and dependent assemblies. Using the dotnet publish command for the class library project, all dependencies will be copied to the output. Another way to copy all the dependencies and the rest of the files is to use EnableDynamicLoading and set it to true in the csproj file.
When using this design pattern in .NET, it should be noted that until the writing of this book, .NET does not allow the introduction of new frameworks to the host program. Therefore, everything that is needed must be pre-loaded by the host program.
Consequences: Advantages

By using this design pattern, it is possible to eliminate the dispersion of settings in the length of the code, and in this case, it will be easier to maintain the code.
This design pattern improves the development capability, and new plugins can be added to the program.
Consequences: Disadvantages

This design pattern makes all the development items presented in the interface format. Therefore, from this point of view, the ability to develop will be limited.
Maintainability using this design pattern will be difficult because plugins will need to be compatible with different versions of the provided interface.
The complexity of testing will increase. Because the plugin may work alone, but it may have problems interacting with the rest of the plugins and the program.
Applicability:

When faced with behaviors requiring different implementations during execution, we can benefit from this design pattern.
Related patterns:

Some of the following design patterns are not related to the plugin design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Separated interface
Singleton
Service stub
Name:

Service stub

Classification:

Base design patterns

Also known as:

Mock object

Intent:

By using this design pattern, it is possible to eliminate dependence on problematic services during testing.

Motivation, Structure, Implementation, and Sample code:

Let us assume that to integrate the identity information of people in the country, it is necessary to communicate with the web service of the National Organization for Civil Registration and receive the person's identity information while providing the national code. The problem is that the web service used for querying the person's information is not ready at the time of software development by us. In this case, the production team has to wait for the registration web service to be ready so that they can start their production work.

This method will delay production on our side. By using the service stub design pattern, we try to simulate the production processes on the side of the National Organization for Civil Registration in the simplest possible approach. In that case, there will be no need to stop the production and testing process.

To implement the preceding scenario, the following codes can be considered. Let us assume that it is agreed that the return response from the registry office has the following structure:

public class UserInformation

{

public string NationalCode { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime DoB { get; set; }

}

And assume that the real-person query service is supposed to take the username and return an appropriate response with the preceding structure. With this assumption, the InquiryService interface, which has the role of the gateway, can be defined as follows:

The InquiryService feature can be set using the plugin design pattern in the above interface. Based on the value of this feature, it is determined that the input request should be directed to the web service of the National Organization for Civil Registration, or the service stub defined in our program should be executed. The classes are responsible for directing the request to the National Organization for Civil Registration web service or service stub and must implement the preceding interface.

To connect to the web service of the National Organization for Civil Registration, the following codes are written:

public class InquiryService : IInquiryService

{

public UserInformation Inquiry(string nationalCode)

=> throw new NotImplementedException();

}

Obviously, in the Inquiry method, the necessary codes to connect to the National Organization for Civil Registration web service must be written. But currently, we do not have information on how to connect to that service.

The codes related to ServiceStub are as follows:

public class InquiryServiceStub : IInquiryService

{

public UserInformation Inquiry(string nationalCode)

{

return new UserInformation()

{

NationalCode = nationalCode,

FirstName = "Vahid",

LastName = "Farahmandian",

DoB = new DateTime(1989,09,07)

};

}

}

Now, to use this structure, it is enough to act as follows:

IInquiryService.InquiryService = new InquiryServiceStub();

var result = IInquiryService.InquiryService.Inquiry("1234567890");

As shown in the preceding code, the InquiryService property is set with a ServiceStub object so that the input request will be directed to the ServiceStub. In the future, whenever the web service of the National Organization for Civil Registration is ready, an object of the InquiryService class can be returned through the plugin so that the incoming requests are directed to the web service of the National Organization for Civil Registration.

Notes:

As much as possible, service stubs should be simple without any complexity.
The important point in using this design pattern is that the design is based on abstractions and interfaces, and the classes should not be directly dependent on each other.
Using the Microsoft Fakes framework, you can automatically create stubs from the interfaces in an assembly. This feature has some limitations in relation to static methods or sealed classes.
Combining this design pattern with gateway and plugin design patterns can provide a better design.
Consequences: Advantages

It is easier to test remote services.
Development does not depend on external services and modules, and the development speed increases.
Consequences: Disadvantages

If the class dependencies are not in the form of dependencies, using this design pattern will double the problem. Therefore, before using this design pattern, it is suggested to make sure that the dependencies between the involved classes are correct so that the Service Stub can be easily replaced with the actual implementation.
Applicability:

When an external service or module causes damage to the development process due to its internal problems, this design pattern can be used until the status of that service or module is stabilized so that the development process does not stop.
Related patterns:

Some of the following design patterns are not related to the service stub design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Plugin
Gateway
Record set
Name:

Record set

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, it is possible to provide a memory display of tabular data.

Motivation, Structure, Implementation, and Sample code:

We must get the author's table's data from the database, perform business logic validation, and deliver the UI. One way to do this is to embed the business logic into the database. The main problem with this method is that the business logic will be spread between the application codes and the database. A better approach is to fetch the data related to the authors from the database and put it in a table inside the DataSet. Then you can disconnect from the database and work with this DataSet instead. The DataSet in ADO.NET is the same as the record set; often, we will not need to implement a record set and can use existing features.

Notes:

One of the most important requirements of the record set is that it should be exactly like the database structure.
If the record set can be serialized, then the record set can also play the role of a data transfer object.
Due to the possibility of disconnecting from the database, you can use UnitOfWork and Optimistic Offline Lock when you need to do something to change the data.
There are two general ways to implement a record set. Using the implicit method and the explicit method. In the implicit method, to access the data of a specific table or column, we give its name in the form of a string to a method and get the result. In the explicit method, a separate class can be defined for each table, and the data can be received from the record set through the internal communication of the classes.
ADO.NET uses explicit methods, and with the help of XSD content, it identifies relationships between classes and produces classes based on these relationships.
By combining the design pattern of the record set and table module, domain logic can be placed in the table module. Therefore, the data can be fetched from the database and placed in the record set. Then through the table module, domain logic was implemented on this data and provided to the UI. When changing the data, the changes are given from the UI to the table module, where the business rules and necessary validations are performed. The record set is delivered, so the changes are applied to the database.
Consequences: Advantages

Using this design pattern, you can retrieve data from the database and then disconnect from the database and start working with the data.
Consequences: Disadvantages

In the explicit method, each table's corresponding class must be generated, increasing the code's volume and maintenance difficulty.
In the Implicit method, it will be difficult to understand what tables and columns the Record Set contains.
If a large amount of data is fetched from the database, memory consumption will increase, negatively affecting performance.
Applicability:

This design pattern can be useful when it is necessary to take the data from the database and work with the data in offline mode.
Related patterns:

Some of the following design patterns are not related to the record set design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Data transfer object
Unitofwork
Optimistic offline lock
Table module
Conclusion
In this chapter, you got acquainted with the base design patterns and learned how to complete the implementations of other design patterns with the help of these design patterns. Also, in this chapter, you learned how to use these design patterns to implement things like converting currencies to each other and so on.

Join our book's Discord space

Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

NET 7 Design Patterns In-Depth 14. Session State Design Patterns

Chapter 14
Session State Design Patterns
Introduction
In order to organize the session state, the design patterns of this category can be divided into the following three main sections:

Client session state: Information about the user session is stored on the client side.
Server session state: Information about the user session is stored on the server side.
Database session state: Information about the session is stored in the database.
Structure
In this chapter, we will discuss the following topics:

Session state design patterns
Client session state
Server session state
Database session state
Objectives
In this chapter, you will learn the differences between stateless and stateful software. You will learn the different session maintenance and management methods, how to save the session on the client or server-side or even the database, and each method's differences, advantages, and disadvantages.

Session state design patterns
When we talk about transactions, we often talk about system and business transactions, and this continues into the discussion of stateless or stateful sessions. But first, it should be determined what is meant by stateful or stateless.

When we look at an object, this object consists of a series of data (status) and behaviors. In enterprise software, stateless means a state where the server does not keep the data between two requests. We will face stateful mode if the server needs to store data between two requests.

There are several considerations for dealing with the stateful method. Sometimes we need to store the data during a series of specific sessions. In this case, this data is the session state. Since the session state is often inside the business transaction, then the attributes proposed for the transaction will also be true for the session state.

The important thing about session states is how they are stored. If we want to store data on the client side, then using the client session state design pattern can be useful. The server session state design pattern will be a good option for storing the data in the server memory. The database session state design pattern will be more suitable if we want to store the data in the database.

If we face many users, we will need methods such as clustering to improve throughput. In this case, paying attention to session migration will often be necessary. For session migration, one request may be processed on server A and the next request, which is a continuation of the previous request, may be processed on server B. The opposite of this method will be session affinity. In this method, a server will be responsible for processing all requests for a specific session.

Client session state
Name:

Client session state

Classification:

Session state design patterns

Also known as:

---

Intent:

Using this design pattern, the information related to the user's session is stored on the client side.

Motivation, Structure, Implementation, and Sample code:

Let us suppose that a requirement has been raised, and it is necessary to implement the possibility of video sharing on the website. To implement this requirement, there are various methods, but what is important in this requirement is the possibility of video sharing. The easiest way to share videos on the web is to share via URL. For example, pay attention to the following URL:

http://jinget.ir/videos?id=1

The preceding address consists of several different parts, which are:

Exchange protocol: In the preceding address, it is the same as http
Domain name: jinget.ir in the preceding address.
Path: There are videos at the preceding address
Query parameters: id=1 in the preceding address
With the help of the preceding address, by sending a GET request, you can inform the server that we intend to receive the video with id equal to 1. In this type of connection, the server does not need to keep any information about the request; all the necessary information is stored on the client side. This method is one of the methods the client session state design pattern tries to provide. Suppose the user changes the preceding address and sends the parameter id=2 instead of id=1. Since the server has not saved any information about the user's session, it will process the request and return the video with an Id equal to 2 in response. Therefore, one of the problems of using this method will be its security.

One of the ways to connect the user’s session to the client and the server is to return the session's id to the client in the form of a URL. When the client sends its request to the server by providing the session ID, it helps the server to find the complete information of the previous session related to the given session ID. Often, in order to reduce the probability of estimating and guessing the session ID, this ID is generated as a random string. For example, consider the following URL:

http://jinget.ir/sessionId=ahy32hk

In the preceding URL, the value of ahy32hk is a random value for the sessionId. By providing it to the server, the client can cause the corresponding session to be found on the server and the information processed.

To protect security, suppose we want to send information related to the user's identity as a request. Usually, this information includes a token with long string content. The next problem in using this method is the limitation of the length of the address. Therefore, we need to either go for other methods or combine the preceding method with other methods. Another method is using the hidden field to save the session information on the user's side. Hidden fields in HTML can be defined as follows:

Hidden fields are uncommon nowadays, but this method allows for storing and sending more information to the server. Following the preceding requirement, we can get the user's identity information from the server and save it as a string in the hidden field. The important feature of a hidden field is that it is placed on the page as an HTML element but not displayed to the user. This does not mean that the user does not have access to its value, and the user can view the content stored in the hidden field by referring to the source of the HTML page.

Users can change this content and face the security threat when they see it. For this purpose, encryption methods can be used. The drawback of using encryption will be that it will disrupt the application's overall performance because each request and response will need to be encrypted and decrypted before and after processing.

The advantage of this method is that you can send any type of request to the server, and it is not limited to GET requests like the URL method. For example, the DTO received from the server can be serialized in XML or JSON format and stored in the hidden field. The important problem in using this method is that today, with the expansion of web service and Web API, the client does not necessarily include HTML codes that can store session information in them.

There is also a third method to solve the problem related to hidden fields, and that is to use cookies. The use of cookies has always been controversial and has its supporters and opponents. One of the most important problems with cookies is that cookies may be disabled in the user's browser. In this case, the program's performance will face problems, so when entering most websites that use cookies, the user is asked to allow the website to use cookies.

For example, in the following code, sessionState is set inside the web.config file. As this code snippet shows, the session state uses an id cookie to identify client sessions. Therefore, in every request and response, this cookie is transferred between the user and the server.

The important thing about the cookie is that the information included in the cookie is sent to the server with every request and returned to the client in the server's response. Like the URL method, cookies also have a size limit, which can be an important problem. Also, cookies, like hidden fields, have the problem that the user can see or change their content. Cookies provide more important features than hidden fields. For example, cookies can be set to only be read on the server side, and client codes do not have access to cookie values.

Notes:

Regardless of what design pattern is used to maintain the session or its information, this design pattern is required to communicate between the request on the client side with the related processing and the response on the server side.
An object is generated on the server side and stored in memory. A key is needed to identify this object, and we call this key SessionId. The response sent to the client contains this SessionId. Hence, the client, in the return request to the server by presenting the SessionId causes the object related to them to be identified among the many objects.
Regardless of which method is used to store and send session information to the server, the received information must be validated from the beginning whenever a request is sent.
Consequences: Advantages

To have a stateless server, using this design pattern is very useful. The stateless nature of the server makes it possible to implement clustering methods on the server side with the lowest cost.
Consequences: Disadvantages

This design pattern is always influenced by the amount of data we want to store. Usually, for a large volume of data, the location and method of data storage on the client side will be an important problem.
Since data is stored on the client side, security threats are important in this design pattern.
Applicability:

This design pattern can be used to implement the server in stateless mode.
Related patterns:

Some of the following design patterns are not related to the client session state design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Server session state
Database session state
Server session state
Name:

Server session state

Classification:

Session state design patterns

Also known as:

---

Intent:

Using this design pattern, the information related to the user session is stored on the server side.

Motivation, Structure, Implementation, and Sample code:

In the example provided for the client session state design pattern, suppose we want to store information about the session on the server side. In this case, an object is generated on the server side for each session.

By default, this object is stored in the memory, and SessionId is used to refer to that object and find it among the many objects related to different sessions. SessionId is key to finding information about the session. This key must be provided to the client in some way, and in the next request that the client sends to the server, by providing the SessionId to the server, the information related to their session will be found, and the processing and response generation operations will be done. Figure 14.1 shows the overall process of the server session state design pattern:

Figure14.1.png
Figure 14.1 Server Session State

The problem with the preceding scenario is that with the increase in the number of users or the increase in the number of sessions, the number of objects in the memory will increase, and this will cause a significant amount of server memory to be allocated to store these objects. In addition to increasing the amount of memory consumption on the server side, the possibility of clustering and implementing load-balancing methods will also be difficult.

The session status can be serialized and stored in a data source using the memento design pattern to solve this problem. In this case, choosing the serial format will be important. As mentioned, data can be serialized and stored in string or binary formats. This will make the server stateful while providing clustering, load balancing, and so on the server side. This method will affect the overall performance of the program.

The location of the data source will be important in the preceding method. You can consider the data source on the same application server or use a separate server to store this information. It is evident that if these data are stored on the same server application, the flexibility will be reduced in front of the design and implementation of clustering solutions, and so on.

To implement this design pattern, suppose we need to use an In-Memory session provider, so we need to install Microsoft.AspNetCore.Session package to enable the use of session middleware. Run the following command to install this package:

dotnet add package Microsoft. AspNetCore.Session

Note that you can use different methods to install NuGet packages. For example, you can use package manager CLI as follows:

NuGet\Install-Package Microsoft.AspNetCore.Session

For more information about this NuGet package, please refer to the following link: https://www.nuget.org/packages/Microsoft.AspNetCore.Session

After installing this package, we need to configure the use of the session as follows in Program.cs file:

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>

{

options.Cookie.Name = "SessionInfo";

options.IdleTimeout = TimeSpan.FromMinutes(60);

});

In the preceding code, it is mentioned that an in-memory provider is going to be used. Also, it is stated that the session ID will be stored in the SessionInfo cookie in the client’s browser so that with each server request, this cookie will be sent to the server too. By providing this cookie to the server, the server will be able to find the client-related session and loads its data.

The IdleTimeout indicates how long the session can be idle before its contents are abandoned. Each session access resets the timeout, and note this only applies to the content of the session, not the cookie.

Finally, call the app.UseSession() adds the SessionMiddleware to enable the session state for the application automatically. After configuring the preceding settings, Program.cs file should look as follow:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>

{

options.Cookie.Name = "SessionInfo";

options.IdleTimeout = TimeSpan.FromMinutes(60);

});

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

{

app.UseExceptionHandler("/Home/Error");

}

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.UseSession();

app.Run();

When the client loads the application, a cookie will be created in the client’s browser, which will be used to find the session data related to the specific client. Figure 14.2 shows this cookie (SessionInfo cookie):

Figure14.2.png
Figure 14.2: Cookie created in the client browser

Now we can set the value to the session object as follows:

public IActionResult Index()

{

HttpContext.Session.SetString("Name", "Vahid Farahmandian");

HttpContext.Session.SetString("TimeStamp", DateTime.Now.ToString());

return View();

}

And get the value from the session object as follow:

public IActionResult Privacy()

{

ViewBag.data = HttpContext.Session.GetString("Name") +

HttpContext.Session.GetString("TimeStamp");

return View();

}

If we navigate the browser to the Index view, values will be set in the session object, and when we navigate to the Privacy view, we can see the values stored in the session object.

If we host the application in IIS, when we restart the application pool, all the data related to the session will be abandoned because the session information is stored in the server’s memory. The memory will be freed up by restarting the application pool.

Notes:

To store session information, you can use Serialized LOB design pattern.
Consequences: Advantages

The implementation and use of this design pattern is very simple.
Consequences: Disadvantages

It is difficult to implement solutions to improve efficiency, including clustering.
Applicability:

This design pattern can be used to implement the server in stateful mode.
Related patterns:

Some of the following design patterns are not related to the server session state design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Client session state
Database session state
Memento
Serialized lob
Database session state
Name:

Database session state

Classification:

Session state design patterns

Also known as:

---

Intent:

This design pattern stores information about the session in the database.

Motivation, Structure, Implementation, and Sample code:

The database session state design pattern is similar to the server session state design pattern. The difference is that when the SessionId is sent from the client to the server, the server refers to the database and reads the information related to the SessionId from the database by providing this SessionId to the database. Any changes that occur to this information must be rewritten in the database.

Figure 14.3 shows the overall process of the Database Session State design pattern:

Figure14.3.png
Figure 14.3: Database Session State

When the session is closed, the information related to the session should also be deleted. Usually, SessionId is the primary key in the table where session information is stored, and SessionId can be used to delete a closed session. Sometimes the closing of the session may not be accompanied by a notification. In this case, by considering a timeout for the sessions, the sessions table can be referred to in certain time intervals, and the sessions whose timeout has reached can be deleted.

Suppose that we want to implement the mechanism of saving the session in the database using Microsoft SQL Server and ASP.NET Core.

Note that implementing this design pattern in ASP.NET Core differs from ASP.NET. For example, to implement this design pattern in ASP.NET, it was necessary to create several tables and procedures in the database using the aspnet_regsql command or tool, but in ASP.NET Core, these tables and stored procedures are no longer needed, and the design pattern can be implemented with just one table.

There are different ways to create this table, including using the NuGet package called Microsoft.Extensions.Caching.SqlConfig.Tools, or you can create the table manually and directly in the SQL Server.

According to the preceding explanations, to implement this design pattern in .NET, the following steps can be taken to create a table by using the NuGet package, run the following command to install the package:

dotnet add package Microsoft.Extensions.Caching.SqlConfig.Tools

Note that you can use different methods to install NuGet packages. For example, you can use Package Manager CLI as the following:

NuGet\Install-Package Microsoft.Extensions.Caching.SqlConfig.Tools

For more information about this NuGet package, please refer to the following link: https://www.nuget.org/packages/Microsoft.Extensions.Caching.SqlConfig.Tools

When the package is installed successfully, run the following command to create the corresponding table in the database:
dotnet sql-cache create

In the preceding command, is the connection string used to connect to the destination database, indicates that the desired table should be created under which schema in the database, and finally, is the name of the desired table. For example:

dotnet sql-cache create

"Data Source=jinget.ir; Initial Catalog=MyDb; Integrated Security=True;"

"dbo"

"SessionStore"

As specified in the preceding command, sessions will store in the SessionStore table, which is a member of dbo schema inside the MyDb database located in the SQL Server instance reachable at jinget.ir.

Instead of using the preceding NuGet package, which is also deprecated, the table can be created manually in the database, which is enough to run the following query:

USE MyDB

GO

CREATE TABLE [dbo].[SessionStore](

[Id] [nvarchar](449) NOT NULL,

[Value] [varbinary](max) NOT NULL,

[ExpiresAtTime] [datetimeoffset](7) NOT NULL,

[SlidingExpirationInSeconds] [bigint] NULL,

[AbsoluteExpiration] [datetimeoffset](7) NULL,

CONSTRAINT [PK_Index_Id] PRIMARY KEY CLUSTERED

(

[Id] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,

IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

ON [PRIMARY]

) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

CREATE NONCLUSTERED INDEX [NCI_Index_ExpiresAtTime]

ON [dbo].[SessionStore]

(

[ExpiresAtTime] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,

SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF,

ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

After creating the SessionStore table, the following settings should be applied in the Program.cs codes:
builder.Services.AddDistributedSqlServerCache(options =>

{

options.ConnectionString = @"

Data Source=.;

Initial Catalog=MyDb;

Integrated Security=True;";

options.SchemaName = "dbo";

options.TableName = "SessionStore";

});

services.AddSession(options => {

options.CookieName = "SessionInfo";

options.IdleTimeout = TimeSpan.FromMinutes(60);

});

Calling the app.UseSession() adds the SessionMiddleware to enable the session state for the application automatically. After configuring the preceding settings, Program.cs file should look as follow:

// Add services to the container.

builder.Services.AddControllersWithViews();

builder.Services.AddDistributedSqlServerCache(options =>

{

options.ConnectionString = @"

Data Source=.;

Initial Catalog=MyDb;

Integrated Security=True;";

options.SchemaName = "dbo";

options.TableName = "SessionStore";

});

builder.Services.AddSession(options =>

{

options.Cookie.Name = "SessionInfo";

options.IdleTimeout = TimeSpan.FromMinutes(60);

});

var app = builder.Build();

// Configure the HTTP request pipeline.

if (!app.Environment.IsDevelopment())

{

app.UseExceptionHandler("/Home/Error");

}

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(

name: "default",

pattern: "{controller=Home}/{action=Index}/{id?}");

app.UseSession();

app.Run();

SQL session state internally uses memory cache to enable SQL Server session, Microsoft.Extensions.Caching.SqlServer NuGet package should be installed as a dependency along with Microsoft.AspNet.Session to enable the use of UseSession middleware. To install this package following command can be used:

dotnet add package Microsoft.Extensions.Caching.SqlServer

dotnet add package Microsoft.AspNet.Session

You can also install these two packages using NuGet CLI as follow:

NuGet\Install-Package Microsoft.Extensions.Caching.SqlServer

NuGet\Install-Package Microsoft.AspNet.Session

For more information about these two NuGet packages, refer to the following links:

https://www.nuget.org/packages/Microsoft.Extensions.Caching.SqlServer/8.0.0-preview.3.23177.8

https://www.nuget.org/packages/Microsoft.AspNet.Session/1.0.0-rc1-final

As specified in the Program.cs code, using AddDistributedSqlServerCache method, the session store table is configured. The configurations can be placed in the appsetting.json file, but they are hard coded in the given code for simplicity.
Now we can set the value to the session object as follow:
public IActionResult Index()

{

HttpContext.Session.SetString("Name", "Vahid Farahmandian");

HttpContext.Session.SetString("TimeStamp", DateTime.Now.ToString());

return View();

}

Also, the same as the server session state design pattern, when the client loads the application, a cookie will be created in their browser, as shown in Figure 14.2.

As soon as the data is set in the Session object, a record will also be inserted into the SessionStore table, as shown in Figure 14.4:

Figure14.4.png
Figure 14.4: SessionStore table

To get that value from the session object, we can do as follow:
public IActionResult Privacy()

{

ViewBag.data = HttpContext.Session.GetString("Name") +

HttpContext.Session.GetString("TimeStamp");

return View();

}

If we navigate the browser to the Index view, values will be set in the session object, and when we navigate to the Privacy view, we can see the values stored in the session object.

If we host the application in IIS, when we restart the application pool, all the data related to the session will be retained because the session information is persisted in the SQL Server database, so restarting the application pool will not lose session information.

Notes:

The session information stored in the database is final. If we are in the middle of an order processing and need to save its current status (not final), this information should not be combined with final information. To distinguish between finalized and unfinished information, a separate column or table can be used to determine which information is the final record and which information is temporary.
Consequences: Advantages

By saving the session information in the database, the server becomes stateless. Although this transformation facilitates the implementation of clustering infrastructure, it has a negative effect on performance because, with each request that enters the server, the relevant session information must be fetched from the database.
Consequences: Disadvantages

It can reduce the overall performance of the program.
Deleting information related to old and unused sessions is complicated.
Applicability:

This design pattern can be used when storing session information in the database.
Using this design pattern will be very beneficial in limiting the number of online users.
Related patterns:

Some of the following design patterns are not related to the Database Session State design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Client session state
Server session state
Conclusion
In this chapter, you got acquainted with various storage and session management methods and learned how to use client, server, and database session state methods to manage the session status. Note that, as stated, the choice of each of these design patterns is directly related to the requirement.

In the next chapter, you will learn about base design patterns.

Join our book's Discord space

Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

NET 7 Design Patterns In-Depth 13. Offline Concurrency Design Patterns

Chapter 13
Offline Concurrency Design Patterns
Introduction
To organize offline concurrency, the design patterns of this category can be divided into the following four main sections:

Optimistic offline lock: By identifying the collision and rolling back the transaction, it prevents the occurrence of a collision between the business transactions simultaneously.
Pessimistic offline lock: By making data available to only one transaction, it is possible to prevent collisions between simultaneous transactions.
Coarse-grained lock: With the help of a lock, a lock can be defined on a set of related objects.
Implicit lock: Frameworks are responsible for managing locks.
Note that in writing this chapter, some images in Patterns of Enterprise Applications Architecture by Martin Fowler1's book are used.

Structure
In this chapter, we will cover the following topics:

Offline concurrency design patterns
Optimistic offline lock
Pessimistic offline lock
Coarse-grained lock
Implicit lock
Objectives
In this chapter, you will deal with different transaction concepts and learn how to manage problems related to concurrency and transaction management problems with the help of different design patterns. In this chapter, you will learn how to prevent unwanted problems caused by concurrency by locking different resources and managing requests and give your software the ability to process requests simultaneously and deliver better performance to users.

Offline concurrency design patterns
One of the most complicated parts of software production is dealing with concurrency-related topics. Whenever several threads or processes have access to the same data, there is a possibility of problems related to concurrency, so one should think about concurrency in production software. Different solutions are at different levels for working and managing concurrency in enterprise applications. For example, you can use transactions, internal features of relational databases, and so on. This reason does not prove that concurrency management can be blamed on these methods and tools.

One of the most frequent concurrent problems in programs is the lost update problem. During this problem, the update operation of one transaction is overwritten by another transaction, and the changes of the first transaction are lost. The next concurrency problem is inconsistent reading. During this problem, the data is changed between two reading operations; therefore, the data read at the beginning does not match the data read later.

Both problems endanger the accuracy of the data and the ability to trust the data, which will be the root of strange and wrong behavior in the program. If we focus too much on data accuracy, we end up with a solution where different transactions wait for previous transactions to finish their work. Besides increasing data accuracy, this expectation threatens the data's liveness, and it is necessary always to balance the data's accuracy and liveness.

To manage concurrency, there are different methods. One way is to allow multiple people to read the data, but when saving the changes, accept the changes from someone whose version of the data is the same as the version in the data source. This method is what optimistic offline lock tries to provide. Another method is to lock the data read by one person and not allow another person to read it until the first person finishes his transaction. This method is also what pessimistic offline lock tries to provide. The choice between these two methods is between collision detection and collision prevention.

Deadlock is the important thing that happens when using the pessimistic method. There are different methods to identify and manage deadlocks. One of these methods is sacrificing a party and canceling his operation, so his locks will also be released. The second method is to set the lifetime for the locks so that if the lock is not released within a certain period, the transaction is automatically canceled, and the lock is released so that the deadlock does not occur.

When talking about concurrency, likely, the transaction leg is also involved. The transaction has an important feature: either all the changes are applied in the database or none are applied. Software transactions have four important characteristics known as Atomicity-Consistency-Isolation-Durability (ACID):

Atomicity: Either the whole work is done, or none of the work parts are done. Obviously, in the process of doing the work, if one of the steps is not done, all the changes must be rolled back. Otherwise, the changes can be committed at the end of all the work. For example, in an application that transfers funds from one account to another, the atomicity property ensures that the corresponding credit is made to the other if a debit is made successfully from one account.
Consistency: All resources before and after a transaction must be consistent and healthy. For example, in an application that transfers funds from one account to another, the consistency property ensures that the total value of funds in both accounts is the same at the start and end of each transaction.
Isolation: The results obtained during a transaction should not be accessible to other transactions until the completion of that transaction. For example, in an application that transfers funds from one account to another, the isolation property ensures that another transaction sees the transferred funds in one account or the other but not in both.
Durability: In case of an error or problem, the results of successful transaction changes should not be lost. For example, in an application that transfers funds from one account to another, the durability property ensures that the changes made to each account will not be reversed.
Design transactions, there are three different methods:

Long transaction: Transactions that span several requests.
Request transaction: Transactions related to a request and the transaction are also determined upon completion of the request.
Late transaction: All reading operations are performed outside the transaction, and only data changes are included.
When using a transaction, knowing what will be locked during the transaction is very important. Locking a table during a transaction is usually dangerous. Because it will cause the rest of the transactions that need the table to wait until the lock is removed so they can do their work; also, different isolation levels can be determined by using transactions. Each of these isolation levels has different strengths and behavior. Isolation levels include serializable, repeatable read, read committed and read uncommitted. SQL server offers other isolation levels beyond this book's scope.

When managing concurrency in transactions, it is tried to use optimistic offline lock because it is easier to implement and delivers better output in terms of liveness. In addition to these advantages, the big drawback of this method is that the user will notice an error at the end of his work and when saving, which can lead to dissatisfaction. In this case, the pessimistic method can be useful, but compared to the optimistic method, it is more complicated to implement and has a worse output in terms of liveness.

Another method is not to apply the lock for each object but instead for a group of objects. That is how coarse-grained lock tries to deliver. The better method would be to use the implicit lock, where the supertype layer or the framework will manage the concurrency and apply and release the lock.

Optimistic offline lock
Name:

Optimistic offline lock

Classification:

Offline concurrency design patterns

Also known as:

---

Intent:

Using this design pattern, it is possible to prevent collisions between simultaneous business transactions by identifying the collision and rolling back the transaction.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement has been raised and requested in its format so that different users can update the authors' data. To implement this mechanism, you can easily implement the update operation. But the problem will be that two users may want to update the data of the same author at the same time. In this case, most of the update operations of one of the users will be lost, and the so-called lost update will occur. To clarify the scenario, consider the following sequence diagram:

Figure13.1.png
Figure 13.1: Update author sequence diagram

As shown in Figure 13.1 diagram, User1 first gets the data related to Author1 and starts changing this information. Meanwhile, User2 also gets Author1's data, changes it, and stores it in the database. Next, User1 saves their changes in the database. In this case, if User1's changes are written to the database, then User2's will be lost. To prevent this, you can prevent User1's changes from being saved and return an error to them.

The important thing is, how can you find out that Author1's data has changed before saving User1's changes? There are different ways to answer this question, one of which is to use the Version field in the database table.

According to the preceding explanation, the proposed requirement can be implemented as follows:

CREATE TABLE author(

AuthorId INT PRIMARY KEY,

FirstName VARCHAR(50),

LastName VARCHAR(50),

Version INT NOT NULL

)

According to the table's structure, the Version column will store the data version. In this way, every time the data of the record changes, the value in Version increases by one unit. In this case, whenever the UPDATE or DELETE operation is sent to the table, along with the conditions sent, the condition related to the Version is also sent. As follows:

SELECT * FROM author WHERE AuthorId = 1

By executing the preceding query, the data related to the author with Id 1 will be delivered to the user. Suppose the data is as follows:

Author: 1

FirstName: Vahid

LastName: Farahmandian

Version: 1

Now that this data has been delivered to User1, this user is busy making changes. Meanwhile, User2 also requests the same data, so the same data is also delivered to them. Next, User2 applies his changes and saves them in the database. For this, you must send the following query to the database:

UPDATE author SET FirstName='Ali', LastName='Rahimi', Version = Version +1

WHERE AuthorId=1 AND Version = 1

As specified in the WHERE section, the condition related to the Version is sent to the database along with other conditions. After applying these changes, the data in the table will change as follows:

Author: 1

FirstName: Ali

LastName: Rahimi

Version: 2

Then User1 sends his changes to the database. The point here is that the Version value delivered to User1 was equal to 1, so the following query will be delivered to the database:

UPDATE author SET FirstName='Vahid', LastName='Hassani', Version = Version +1

WHERE AuthorId=1 AND Version = 1

No record matches the given conditions in the database, and the database will reply that no record has been changed during the sent query. When no record has been changed, another person has already changed the desired record, and you can inform the user of this change by presenting an error. With the preceding explanation, the following codes can be considered for the Author:

public async Task Find(int authorId)

{

IDataReader reader = await new SqlCommand($"" +

$"SELECT * " +

$"FROM author " +

$"WHERE AuthorId={authorId}")

.ExecuteReaderAsync();

reader.Read();

return new Author()

{

AuthorId = (int)reader["AuthorId"],

FirstName = (string)reader["FirstName"],

LastName = (string)reader["LastName"],

Version = (int)reader["Version"]

};

}

public async Task ModifyAuthor(Author author)

{

var result = await new SqlCommand($"" +

$"UPDATE author " +

$"SET FirstName='{author.FirstName}', " +

$"LastName='{author.LastName}' " +

$"Version = Version +1 " +

$"WHERE AuthorId={author.AuthorId} AND " +

$"Version={author.Version}")

.ExecuteNonQueryAsync();

if (result == 0)

throw new DBConcurrencyException();

else

return true;

}

As seen in the preceding codes, when sending the UPDATE query, the Version condition is also sent along with the other conditions. According to the database's response, the collision event is identified, and the user is informed by sending an exception. The same process can be considered for the DELETE operation and send the Version condition to the database along with the other conditions in DELETE.

Notes:

Other methods can be used besides the version to implement this design pattern. For example, columns can be used to identify the person who changed or the time of change. This method has serious and important flaws. Focusing on time can cause errors and problems because sometimes the client and server clock settings differ. Another method is to specify all columns in the WHERE clause when modifying the record. The problem with this method is that the queries may be long, or the database may be unable to use the right index to speed up the work.
Normally, this design pattern cannot be used to identify inconsistent reads. To prevent this problem, you can also read the data with Version. It is necessary to have a proper isolation level in the database (Repeatable Read or stronger). Using this method will cost a lot. A better solution to this problem is using the coarse-grained lock design pattern.
One of the famous applications of this design pattern is in the design of Source Control Management (SCM) systems.
To optimally implement this design pattern, automatic integration strategies can be used during a collision.
Using the layer supertype design pattern to reduce the amount of code in the design of this pattern can be useful.
Using the entity framework, this design pattern will be very simple.
Using a single copy of a record during a transaction can be useful in using the identity map design pattern. During a transaction, we may encounter the phenomenon of inconsistent readings.
You can manage transactions better by combining the UnitOfWork design pattern with this one.
Consequences: Advantages

The implementation of this design pattern is very simple.
There is no need for record locking overhead when using this design pattern.
Consequences: Disadvantages

When the system load is high, and there are many simultaneous transactions, using this design pattern will cause many transactions to be rolled back, and the user experience will suffer.
Applicability:

When the probability of a collision between two different business transactions is low, using this design pattern can be useful; otherwise, using the pessimistic offline lock design pattern will be a better option.
Related patterns:

Some of the following design patterns are not related to the optimistic offline lock design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Coarse-grained lock
Layer supertype
Identity map
Unit of work
Pessimistic offline lock
Pessimistic offline lock
Name:

Pessimistic offline lock

Classification:

Offline concurrency design patterns

Also known as:

---

Intent:

By using this design pattern and making data available to only one transaction, it is possible to prevent the collision between simultaneous transactions.

Motivation, Structure, Implementation, and Sample code:

As seen in the optimistic offline lock design pattern, this design pattern can be used to manage concurrency and prevent collisions. But the method used in this design pattern can identify the collision only at the end of the work and when the changes have been sent to the database to be saved. This will cause the application to face serious problems in scenarios with many concurrent transactions and increase dissatisfaction. Because after doing all the work, the user will find out that his operation could not be saved.

You can use the pessimistic offline lock design pattern to prevent this from happening. Using this design pattern, if the data is provided to a transaction, its operations will be performed, and the changes will be saved. Because by using this design pattern, when retrieving data from the database, a lock is placed on the data so that another transaction cannot retrieve that data.

The implementation of the pessimistic offline lock design pattern includes three steps:

Determining what type of lock is needed: To choose the right type of lock, factors such as providing the maximum possible concurrency, appropriately responding to business needs, and minimizing the complexity of the code should be considered. The different types of locks are:
Exclusive write lock: The data will be locked in modification, and only one transaction will be allowed to modify the data.
Exclusive read lock: The data will be locked for reading. Naturally, this method will impose stricter restrictions on data access.
Read/write lock: This type will combine the previous two locks. For this type of lock, the following rules apply:
Both read and write locks are exclusive; other transactions cannot obtain a write lock if a transaction has a read lock on a record. The opposite is also true, and if a transaction has a write lock on a record, other transactions will not be able to obtain a read lock.
It is possible to provide read locks for multiple transactions, increasing the program's concurrency.
Building the lock management module: The task of this module is to manage the access of transaction requests to get or release the lock. This module must manage access to know what is locked at any given moment and who has this lock. This information can be stored in a database table or an object in memory. If an object in memory is used, this object must be a singleton. The database table method would be reasonable if the web server is clustered. If you use a table, managing the concurrency of the table itself will be very important. For this purpose, you can use the serializable isolation level to access this table. Also, storing procedures and the appropriate isolation level can be useful in this case. The important point for the lock management module is that business transactions must be associated with the module and not access objects in memory or tables, regardless of where and how the lock is stored.
Definition of business procedures and their use in locks: in the definition of these procedures, questions must be answered, including:
What should be locked and when?
The question "when" must be answered first to answer this question. The lock must be applied before delivering the data to the program. Applying the lock or fetching the data is an event directly related to the transaction's isolation level, but applying the lock and then fetching the data can improve reliability.
After "when" is determined, "what" must be answered, what should be locked is usually the table's primary key value or whatever value is used to find the record.
When can the lock be released? The lock must be released whenever the transaction is completed (Commit or Rollback).
What should happen when it is not possible to provide a lock? In this case, the simplest thing that happens is to cancel the transaction. Because basically, the purpose of this design pattern is to inform the user at the beginning of the work if there is no possibility to access the data.
Suppose the optimistic offline lock design pattern complements the pessimistic offline lock in addition to the preceding three steps. In that case, it is necessary to determine which records should be locked.

For this design pattern, the following sequence diagram can be considered:

Figure13.2.png
Figure 13.2: Get author info using Pessimistic Offline Lock

As shown in Figure 13.2 diagram, User1 reads the data of Author1. At this stage, a record is inserted in the lock table as follows:

Owner: User1

Lockable: 1

Next, User1 is busy changing the data of Author1. At the same time, User2 requests access to the data of Author1, but because User1 locks this record, it is impossible to read it for User2 and encounters an error. Then User1 sends his changes to the database for storage and deletes the record in the lock table, which removes the lock.

According to the preceding description, the lock management module can be considered as follows:

public static class LockManager

{

private static bool HasLock(int lockable, string owner)

{

//check if an owner already owns a lock.

return true;

}

}

The HasLock method checks once before a record for the owner is registered in the table that the owner has not locked the desired record before. If he had already locked the record, inserting a new record into the lock table is unnecessary. The characteristic of the record in this example is the value of its primary key, and it is assumed that all the primary keys are of INT type:

public static void AcquireLock(int lockable, string owner)

{

if (!HasLock(lockable, owner))

{

try

{

//Insert into lock table/object

}

catch (SqlException ex)

{

throw new DBConcurrencyException($"unable to lock {lockable}");

}

}

}

Using this method, a lock is defined on a record and given to the owner. In this method, it is assumed that we put the lock on a column in the tables with INT data type, and their value is unique throughout the database. In actual implementations, this part will probably need to be changed. Also, the meaning of owner in these methods can be HTTP SessionID or anything else according to your needs. The important thing about lockable is that its value is unique within the table or object of the lock. Therefore, if two different owners want to lock a lockable, the database will save one of them, and for the second case, it will return a unique value violation error:

public static void ReleaseLock(int lockable, string owner)

{

try

{

//delete from lock table/object

}

catch (SqlException ex)

{

throw new Exception($"unable to release lock {lockable}");

}

}

public static void ReleaseAllLocks(string owner)

{

try

{

//delete all locks for given owner from lock table/object

}

catch (SqlException ex)

{

throw new Exception($"unable to release locks owned by {owner}");

}

}

The user of the two methods ReleaseLock and ReleaseAllLocks is also to release the lock. If we want to place the locks in the database table, this method will be a CRUD operation on the desired table. Now with the presence of the lock management module, it can be used as follows:

public class AuthorDomain

{

public bool Modify(Author author)

{

LockManager.ReleaseAllLocks("Session 1");

LockManager.AcquireLock(author.AuthorId, "Session 1");

// Implementation of transaction requirements

LockManager.ReleaseLock(author.AuthorId, "Session 1");

return true;

}

}

In the preceding method, before doing anything, we first delete all the locks that were for Session1, and then we define a lock for the author that we want to edit, and at the end of the work, we release the defined lock. Real implementation requirements will be more complex than this simple example, and this example is given only to clarify how this design pattern works.

Notes:

Choosing the right strategy for locking is a decision that should be made with the help of domain experts. Because this problem is not just a technical problem, it can affect the entire business.
Choosing an inappropriate locking strategy can make the system become a single-user system.
Identifying and managing inactive meetings is an important point that should be addressed. For example, the client shuts down its system after receiving the lock and in the middle of the operation. Now we are facing an open transaction, and a series of locks have been placed on a series of resources. In this case, you can use different mechanisms, such as the timeout mechanism on the web server. Or, an active time can be set for the records included in the lock maintenance table so that the lock is invalid and released after that time.
Locking everything in the system will cause many problems in the system. Therefore, it is better to use this design pattern only when needed and place it next to the pessimistic offline lock design pattern.
The preceding example assumes that the selected lock type is set in the form of defined transactions. Otherwise, the lock type can also be stored as a column in the lock table.
Consequences: Advantages

By using this design pattern, it is possible to prevent the occurrence of inconsistent readings.
Consequences: Disadvantages

Management of locks is a complex operation.
As the number of users or requests increases, the efficiency decreases.
There is a possibility of a deadlock using this design pattern. Therefore, one of the tasks of the lock management module is to return an error instead of waiting if it is not possible to grant a lock to prevent deadlocks as much as possible.
If this design pattern is used and there are long transactions in the system, the system's efficiency will suffer.
Applicability:

When the probability of collision between two different business transactions is high, using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the pessimistic offline lock design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Pessimistic offline lock
Singleton
Coarse-grained lock
Name:

Coarse-grained lock

Classification:

Offline concurrency design patterns

Also known as:

---

Intent:

Using this design pattern, it is possible to define a lock on a set of related objects with the help of a lock.

Motivation, Structure, Implementation, and Sample code:

Usually, different objects need to be changed in different programs in the form of a transaction. With the approach in the design patterns of pessimistic offline lock and optimistic offline lock, it is necessary to define a lock on each object or record (resources) to manage locks. This process itself can be the source of various problems. For example, all resources must be processed in order, and locks must be created on them. This process becomes more complicated when faced with a complex graph of objects. On the other hand, with the approach that existed in the pessimistic offline lock design pattern, for each node in the graph, it will be necessary to define a record in the table related to the lock, which will cause us to face a large table.

Another approach is to define a lock on a set of related resources and manage concurrency in this way. This process is what the coarse-grained lock design pattern aims to provide. The most concrete example of implementing this method is aggregated. In fact, by using aggregates, a change point can be defined for a set of related objects (Root), and all related objects can be changed only through that point. This feature that aggregates provides the implementation of the coarse-grained design pattern. Because when we are facing aggregate, its members can be accessed from only one point, and the same point can be used to manage the lock (Root Lock). In this case, a lock can be considered for each aggregate. In other words, by applying the lock on aggregate, the lock will be applied to all the members of its subset.

Using root lock, it will always be necessary to use a mechanism to move from the subgroup members to the root and apply the lock on the root. There are different methods for this mechanism. The simplest method is to navigate each object and move it to the root. This method may cause performance problems in more complex graphs, which can be managed using the lazy load design pattern. Figure 13.3 shows a view of the root lock method:

Figure13.3.png
Figure 13.3: Root Lock method

Another way to implement the coarse-grained lock design pattern is to use the shared version mechanism. In this method, every object in a group shares a specific version. Therefore, the lock will be applied to the entire group whenever this shared version increases. This method is very similar to the method presented in the optimistic offline lock design pattern. If we want to present the same method on the design pattern of pessimistic offline lock, then it will be necessary for each member of a group to share a type of token. Since the pessimistic method is often used to complement the optimistic method, using the shared version as a token can be useful. Figure 13.4 shows the shared version method:

Figure13.4.png
Figure 13.4: Shared Version method

According to the preceding description, the shared version method can be implemented in the form of a Shared optimistic offline lock as follows:

Suppose we have the version table as follows:

CREATE TABLE version

(

Id INT PRIMARY KEY,

Value INT NOT NULL,

ModifiedBy VARCHAR(50) NOT NULL,

Modified DATETIME

)

As seen in the preceding table, the Value column will store the subscription version's value. Also, to work with the version table, the Version class can be considered as follows:

public class Version

{

public int Id { get; set; }

public int Value { get; set; }

public string ModifiedBy { get; set; }

public DateTime Modified { get; set; }

public Version(int id, int value, string modifiedBy, DateTime modified)

{

Id = id;

Value = value;

ModifiedBy = modifiedBy;

Modified = modified;

}

public static async Task FindAsync(int id)

{

//Try to get version from cache using Identity Map;

//IdentityMap.GetVersion(id);

Version version = null;

if (version == null)

{

version = await LoadAsync(id);

}

return version;

}

private static async Task LoadAsync(int id)

{

Version version = null;

var result = await new SqlCommand($"" +

$"SELECT * " +

$"FROM version " +

$"WHERE Id ={id}", DB.Connection)

.ExecuteReaderAsync();

if (result.Read())

{

version = new(

(int)result["Id"],

(int)result["Value"],

(string)result["ModifiedBy"],

(DateTime)result["Modified"]);

//put version in cache IdentityMap.Put(version);

}

else

{

throw new DBConcurrencyException($"version {id} not found!");

}

return version;

}

}

As it is clear in the FindAsync method, it tried to load the requested version from the cache. If the version is unavailable in the cache, then the information related to the version is retrieved from the database using the LoadAsync method. After fetching the version information from the database, this data is placed in the cache and returned. Suppose no record is found in the database for the provided id. In that case, another transaction has changed the version, which will be a sign of the possibility of a collision, and for this reason, a DBConcurrencyException exception will occur.

Naturally, if a new object wants to be created, its corresponding record in the version table should also be created. You can use the INSERT command to create a record in the version table. The point, in this case, is that after the version record is created in the table, that record must be placed in the cache:

public async void Insert()

{

await new SqlCommand($"" +

$"INSERT INTO " +

$"version " +

$"VALUES({Id},{Value},'{ModifiedBy}','{Modified}')",

DB.Connection).ExecuteNonQueryAsync();

//put version in cache IdentityMap.Put(version);

}

Regarding the Version class, a mechanism will be needed to increase its value. This can also be done using the UPDATE command. The important thing about this method is that if no record has been updated during the update operation, this can be a sign of a collision and should be reported to the user. Before changing the version value, it should be ensured that the previous version is not in use. That is, the desired records have not been locked before:

public async void Increment()

{

if(!Locked()){

var effectedRowCount = await new SqlCommand($"" +

$"UPDATE version " +

$"SET " +

$"Value = {Value}," +

$"ModifiedBy='{ModifiedBy}',"+

$"Modified='{Modified}' " +

$"WHERE Id = {Id}",

DB.Connection)

.ExecuteNonQueryAsync();

if (effectedRowCount == 0)

{

throw new DBConcurrencyException($"version {Id} not found!");

}

Value++;

}

}

Finally, when the Aggregate is deleted, it will be necessary to delete the version corresponding to that record, for which the DELETE operation can be used. Again, when deleting, if the database declares that no records have been deleted, the possibility of a collision should be reported to the user. The following code shows the Delete operation:

public async void Delete()

{

var effectedRowCount = await new SqlCommand($"" +

$"DELETE FROM version " +

$"WHERE Id = {Id}", DB.Connection)

.ExecuteNonQueryAsync();

if (effectedRowCount == 0)

{

throw new DBConcurrencyException($"version {Id} not found!");

}

}

Now that the necessary mechanism for version management has been prepared, you can use it as follows:

public abstract class BaseEntity

{

public Version Version { get; set; }

protected BaseEntity(Version version) => this.Version = version;

}

BaseEntity class is considered a layer supertype. It is used to set the value of the Version property:

public interface IAggregate { }

public class Author : BaseEntity, IAggregate

{

public string Name { get; set; }

public List

Addresses { get; set; } = new List
();

public Author(Version version, string name) : base(version)

{

Name = name;

}

public Author AddAuthor(string name)=>new Author(Version.Create(), name);

public Address AddAddress(string street)

{

Address address = new Address(Version, street);

Addresses.Add(address);

return address;

}

}

Author class as Aggregate inherits from BaseEntity class. In the AddAuthor method, as soon as an Author object is created, the corresponding version object is also created. The code related to creating the version object in the Version class is as follows:

public static Version Create()

{

Version version = new(NextId(),

0, //Initial version number

GetUser().Name,

DateTime.Now); //modification datetime

return version;

}

Next, when we want to add an address for the Author, we use the existing Version property. Also, whenever a request to update or delete an object is received, the Increment method in the Version class must be called:

public abstract class AbstractMapper

{

public void Insert(BaseEntity entity) => entity.Version.Increment();

public void Update(BaseEntity entity) => entity.Version.Increment();

public void Delete(BaseEntity entity) => entity.Version.Increment();

}

public class AuthorMapper : AbstractMapper

{

public new void Delete(BaseEntity entity)

{

Author author = (Author)entity;

//delete addresses

//delete author

base.Delete(entity);

author.Version.Delete();

}

}

As seen in the preceding code, to delete the author; first, the addresses corresponding to him are deleted, then the author is deleted. In the future, the version will be increased by one. If the version is not updated, there is a possibility of a collision, and an error will occur. The deletion operation can be completed by deleting the record related to the version.

You can also implement the shared version method as a shared pessimistic offline lock. The implementation of this method will be the same as the optimistic method. The only difference will be that in this method, we have to use a mechanism to find out that the data that has been uploaded is the latest version. A simple way to ensure this would be to execute the Increment method within the transaction and before the commit. If the Increment execution is executed successfully, we have the latest version of the data; otherwise, due to a DBConcurrencyException error, we will notice that the data is not updated, and the transaction will be rolled back.

The mechanism will be slightly different to implement the root optimistic offline lock method. Because in this method, there is no shared version. To implement this method, you can use the UnitOfWork design pattern. Before saving the changes in the database, navigate the object and increase the value of the parent version with the Increment method each time. As follows:

public class DomainObject : BaseEntity

{

public DomainObject(Version version) : base(version){}

public int Id { get; set; }

public DomainObject Parent { get; set; }

}

Suppose there is a model as above.

public class UnitOfWork

{

...

public void Commit()

{

foreach (var item in modifiedObjects)

{

if (item.Parent != null)

item.Parent.Version.Increment();

}

foreach (var item in modifiedObjects)

{

//save changes to database

}

}

}

Therefore, when saving the changes in the database, the Increment method is first called and then saved in the database.

Notes:

Both shared version and Root Lock methods have their advantages and disadvantages. For example, if the shared version is used, then to retrieve the data, it will always be necessary to join with the version table, which can have a negative effect on the performance. On the other hand, if root lock is used along with the optimistic method, the important challenge will be to ensure that the data is up to date.
The identity map design pattern will be crucial in implementing the shared optimistic offline lock method because all group members must always refer to a common version.
To implement this design pattern, you can use the layer supertype design pattern for simplicity of design and implementation.
Consequences: Advantages

Applying and releasing the lock in this design pattern will be simple and low-cost.
Consequences: Disadvantages

If this design pattern is not designed and used in line with business requirements, it will lock objects that should not be locked.
Applicability

This design pattern can be used when it is necessary to put a lock on an object along with the related objects.
Related patterns:

Some of the following design patterns are not related to coarse-grained lock design patterns, but to implement this design pattern, checking the following design patterns will be useful:

Pessimistic offline lock
Optimistic offline lock
Lazy load
Layer supertype
Unit of work
Identity map
Implicit lock
Name:

Implicit lock

Classification:

Offline concurrency design patterns

Also known as:

---

Intent:

Using this design pattern, the framework or Layer Supertype is responsible for managing locks.

Motivation, Structure, Implementation, and Sample code:

One of the major problems with offline concurrency management methods is that they are difficult to test. Therefore, a capability should be created so that programmers can use the capabilities created once instead of being involved in the daily implementation of concurrency management methods in the code. The reason for this is that if the concurrency management process is not implemented correctly, it can cause serious damage to the quality of data, the correctness of work, and the efficiency of the program.

The implicit lock design pattern helps use the layer supertype design pattern or any other pattern to implement concurrency management processes in the form of parent classes or framework facilities and be available to programmers for use.

For example, consider the following code:

public interface IMapper

{

DomainObject Find(int id);

void Insert(DomainObject obj);

void Update(DomainObject obj);

void Delete(DomainObject obj);

}

Public class LockingMapper : IMapper

{

private readonly IMapper _mapper;

public LockingMapper(IMapper mapper) => _mapper = mapper;

public DomainObject Find(int id)

{

//Acquire lock

return _mapper.Find(id);

}

public void Delete(DomainObject obj) => _mapper.Delete(obj);

public void Insert(DomainObject obj) => _mapper.Insert(obj);

public void Update(DomainObject obj) => _mapper.Update(obj);

}

As seen in the LockingMapper class, when a record is fetched, it is locked and then fetched. The important thing about this design pattern is that the business transactions do not know anything about the mechanism of locking and releasing locks to apply data changes. All these operations happen behind the scenes.

Figure13.5.png
Figure 13.5: Lock management process using Implicit Lock design pattern

In Figure 13.5, the transaction related to the editing of customer information delivers the request to retrieve customer information to LockingMapper. Behind the scenes, this mapper communicates with the lock management module and receives the lock. After receiving the lock, it retrieves the data and delivers it to the relevant transaction.

Notes:

Using this design pattern, programmers should still consider the consequences of using concurrency management methods and locks.
You can use the data mapper design pattern to implement this design pattern.
To design mappers as best as possible in the preceding design pattern, you can use the decorator design pattern.
Consequences: Advantages

It increases the code's quality and prevents errors related to the lack of proper management of concurrency management processes.
Consequences: Disadvantages

By using this design pattern, there is a possibility that programmers will cause the program to encounter various errors by not paying attention to the consequences of concurrency management methods and locks.
Applicability:

This design pattern should often be used to implement concurrency management mechanisms.
Related patterns:

Some of the following design patterns are not related to the implicit lock design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Layer supertype
Data mapper
Decorator
Conclusion
In this chapter, you got acquainted with different design patterns, including pessimistic and optimistic offline locks, coarse-grained locks, and implicit lock design patterns. You also learned how to manage and solve problems caused by concurrency with the help of these design patterns. In this chapter, you learned that you could sometimes lock readers to solve concurrency problems and sometimes manage concurrency problems by simply locking writers.

In the next chapter, you will learn about session state design patterns.

1 https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420

Join our book's Discord space

Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

NET 7 Design Patterns In-Depth 12 . Distribution Design Patterns

Chapter 12
Distribution Design Patterns
Introduction
To organize the distribution, the design patterns of this category can be divided into the following two main sections:

Remote Facade: By providing a coarse-grained view of fine-grained objects, effectiveness, and efficiency are increased throughout the network.
Data Transfer Object: It can move data between processes and reduce the number of calls to different methods.
Structure
In this chapter, we will cover the following topics:

Distribution design patterns
Remote Facade
Data Transfer Object
Objectives
In this chapter, you will learn the types of design patterns related to distributed software, such as Remote Facade and Data Transfer Object, and how to communicate in distributed software and move data between different parts.

Distribution design patterns
One of the hot discussions today is the discussion of distributed software, and today the need for distributed software is growing. The important thing is to know the boundaries and limits of distribution. For example, consider the following separations:

Separation of the client from the server
Separation of the server part of the program from the database
Separating the web server from the application server
In addition to the preceding separations, others can also be considered. In all these separations, it is necessary to pay attention to what effect this separation will have on efficiency. When designing the software, the boundaries and limits of distribution should be reduced as much as possible. It cannot be eliminated and must be properly designed and managed in some places. For example, connecting the front-end to the back-end codes may be necessary. To do this, you can use the remote facade design pattern. When there is a need to establish communication between the front and back end, a method will need to move the data. For this purpose, a data transfer object can be used.

Remote facade
Name:

Remote facade

Classification:

Distribution design patterns

Also known as:

---

Intent:

By using this design pattern, by providing a coarse-grained view of fine-grained objects, the effectiveness and efficiency increase throughout the network.

Motivation, Structure, Implementation, and Sample code:

Suppose a requirement is raised, and, in its format, it is requested to provide a web service to collect and display the information of authors and their books. It is only necessary to display the author's name next to the list of his books. There are different ways to collect and display this information. One method is to provide the following web services to the client for use:

Web service for registering and displaying the author's identity information.
Web service for recording and displaying information about the author's books.
The problem with using this structure is that at least 2 HTTP requests are needed to record this information. 2 HTTP requests mean moving data across the network twice, checking authentication and access control information twice, and so on. This volume of work, when the number of simultaneous requests increases, or the number of round trips during the network increases, can greatly overshadow efficiency and effectiveness.

Another way is to provide a coarse-grained view to the client, and the client delivers all the required data to the server in the form of an HTTP request, and the rest of the processing takes place on the server. With these explanations, the following codes can be considered to implement the preceding requirement:

We have the models related to the author and the book as follows:

public class Author

{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public bool IsActive { get; set; }

public ICollection Books { get; set; }

}

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public string Language { get; set; }

public ICollection Authors { get; set; }

}

As it is clear in the preceding models, each author can have several books, and each book can have several authors. According to the stated requirement, using these models will not work well in displaying the information of authors and their books because many of the features in these models will not be needed for display. Based on this, we need models that are the same as the client's needs. Therefore, we are going to define a series of DTOs. These DTOs are defined using the data transfer object design pattern:

public class AuthorDTO

{

public string FirstName { get; set; }

public string LastName { get; set; }

public ICollection Books { get; set; }

}

public class BookDTO

{

public string Title { get; set; }

public string Language { get; set; }

}

In the preceding code, for the sake of simplicity, AuthorId and BookId features are not included in DTO. In real implementations, these two features will probably be needed. Now that the DTOs the client requires are ready, a mechanism for the model to DTO and vice versa is needed. For this, you can use a mapper or any other method. Therefore, we have the following codes for conversion:

public class AuthorAssembler

{

public AuthorDTO ToDTO(Author author)

{

AuthorDTO dto = new()

{

FirstName = author.FirstName,

LastName = author.LastName

};

ConvertBooks(dto, author);

return dto;

}

public void ConvertBooks(AuthorDTO dto, Author model)

{

foreach (var book in model.Books)

{

dto.Books.Add(new BookDTO

{

Title = book.Title,

Language = book.Language

});

}

}

public Author ToModel(AuthorDTO author)

{

Author dto = new()

{

FirstName = author.FirstName,

LastName = author.LastName

};

ConvertBooks(dto, author);

return dto;

}

public void ConvertBooks(Author model, AuthorDTO dto)

{

foreach (var book in dto.Books)

{

model.Books.Add(new Book

{

Title = book.Title,

Language = book.Language

});

}

}

}

As you can see, in the preceding class named AuthorAssembler, there are two methods for converting the model to DTO and DTO to model. To get the list of authors and insert them, the following two methods are defined in the AuthorAssembler class:

public void CreateAuthor(AuthorDTO dto)

{

Author author =ToModel(dto);

author.AuthorId = new Random().Next(1, 100);

CreateBooks(dto.Books, author);

}

private void CreateBooks(ICollection dtos, Author author)

{

if (dtos != null)

{

if (dtos.Any(x => string.IsNullOrWhiteSpace(x.Title)))

throw new Exception("Book title cannot be null or empty");

foreach (var item in dtos)

{

var book = new Book()

{

Title = item.Title,

Language = item.Language

};

book.BookId = new Random().Next(1, 100);

author.Books.Add(book);

}

}

}

public List GetAuthors()

=> DatabaseGateway.GetAuthors().Select(x => ToDTO(x)).ToList();

Note that the implementations done in this class are considered simple for the sake of simplicity. Now, to receive user requests, the coarse-grained view can be defined as follows:

public interface IAuthorService

{

void AddAuthor(AuthorDTO dto);

ICollection GetAuthors();

}

public class AuthorService : IAuthorService

{

public void AddAuthor(AuthorDTO dto)

=> new AuthorAssembler().CreateAuthor(dto);

public ICollection GetAuthors()

=> new AuthorAssembler().GetAuthors();

}

As the preceding codes show, this view does nothing except translate coarse-grained methods to fine-grained ones. With the existence of AuthorService, there is no need to go back and forth in the network and its related negative effects, and the user's needs can be met with minimal back and forth.

Notes:

This design pattern will not be needed when all transactions are inside a process. But this design pattern can be very useful when the transactions are divided between several processes (within a machine or across the entire web).
The generated coarse-grained view must not contain any domain-related logic. The entire application should work correctly, regardless of the classes associated with these views.
If there are models with the same structure on both sides of the communication, DTO is unnecessary, but in reality, this is almost impossible. Usually, this design pattern is designed and used alongside the data transfer object design pattern.
There may be different methods for communicating with different objects in the design of a coarse-grained view. Having one or more coarse-grained views is a decision that can be made at the time of implementation.
Designed views can be stateless or stateful. If these views need to be stateful, you can use the design patterns in the session state category.
Designed views should usually be good places to control access or manage transactions.
An important feature of this design pattern is several processes or so-called remote use. If we take the feature of being remote from this design pattern, then this design pattern will be very similar to the service layer design pattern.
Consequences: Advantages

Reducing the number of requests during the network will increase productivity and efficiency.
By implementing this design pattern asynchronously, the efficiency can be increased even more.
Consequences: Disadvantages

It will increase the complexity if used in smaller programs or programs only inside a Process.
Applicability:

Improving effectiveness and efficiency in remote communication or outside the current process can be very useful. For example, connecting the front end to the back end in scenarios where the front end is separated from the back end.
Related patterns:

Some of the following design patterns are not related to the Remote Facade design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Data transfer object
Session state
Service layer
Data transfer object
Name:

Data transfer object

Classification:

Distribution design patterns

Also known as:

---

Intent:

Using this design pattern, data can be moved between processes, and the number of calls to different methods can be reduced.

Motivation, Structure, Implementation, and Sample code:

Example 1:

suppose we need to deliver the authors' information and their books to the client to display to the user. There are several methods for this. , the client receives the list of authors once and the list of their books once. As mentioned in the remote facade design pattern, this work increases the number of round trips in the network and affects effectiveness and efficiency.

Another method is to deliver the data to the client as domain models, keeping the round trips to the server to a minimum. The problem with this method is that the structure we deliver to the client may differ from the structure of the domain models.

The data transfer object design pattern helps to deliver the data in the format required by the client while minimizing back and forth to the server.

With the preceding explanations, DTO can be used to implement the preceding scenario. The important point here is how DTO is related to the domain model. The dependence of these two on each other will not be a pleasant event because they can have completely different structures. You can get help from a mapper to solve this problem.

Now for the preceding scenario, the following codes can be considered for DTO and domain model:

public class Author{

public int AuthorId { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public bool IsActive { get; set; }

public ICollection Books { get; set; }

}

public class AuthorDTO{

public string FirstName { get; set; }

public string LastName { get; set; }

public string Status { get; set; }

public ICollection Books { get; set; }

}

For example, in the preceding code, the Author class has features such as AuthorId or IsActive. These features are not considered in the DTO definition because the client does not require these features. Also, in the definition of DTO, there is an attribute called Status, which does not exist in the Author model. The same structure is true for Book:

public class BookDTO

{

public string Title { get; set; }

public string Language { get; set; }

}

public class Book

{

public int BookId { get; set; }

public string Title { get; set; }

public string Language { get; set; }

}

Now, for the model and DTO to be connected, as mentioned, you can use a mapper. As follows:

public static class AuthorAssembler

{

public static Author ToModel(this AuthorDTO dto)

=> new Author{

FirstName = dto.FirstName,

LastName = dto.LastName,

IsActive = dto.Status == "Active",

Books = dto.Books.Select(x => x.ToModel()).ToList()

};

public static AuthorDTO ToDTO(this Author model)

=> new AuthorDTO{

FirstName = model.FirstName,

LastName = model.LastName,

Status = model.IsActive ? "Active" : "Deactive",

Books = model.Books.Select(x => x.ToDTO()).ToList()

};

private static Book ToModel(this BookDTO dto)

=> new Book{

Title = dto.Title,

Language = dto.Language

};

private static BookDTO ToDTO(this Book model)

=> new BookDTO{

Title = model.Title,

Language = model.Language

};

}

The preceding implementation is done using extension methods in C#.NET. As can be seen, the model is converted to DTO through the ToDTO method, and the DTO is converted to the model through the ToModel method. With the preceding structure, if we want to return the information of authors and their books to the client, it is enough to convert the result to DTO through the ToDTO method and return it to the client after preparing the model. Also, the methods related to Book in implementing this scenario are considered private because we only wanted to deliver the data as authors. (Each author object has an attribute for books).

Example 2:

Suppose that we want to receive and save the information of authors and their books from the client this time. To do this, separate DTOs can be defined, or existing DTOs can be used. If there is a big difference between read and write DTOs, then defining two different DTO classes would be the right thing to do. That said, the way to record the author's data will be very similar to the mode of reading the author's data.

Notes:

A new record feature was introduced in version 9.0 of the C#.NET programming language. By introducing this feature, DTO can also be called Data Transfer Record (DTR).
A DTO only contains data and does not have any behavior. In this case, the word ViewModel can also be used for DTO. ViewModel does not necessarily only contain data; in MVVM architecture, it can also contain a series of behaviors, in which case the terms ViewModel and DTO cannot be used interchangeably.
This design pattern can also implement the remote facade design pattern.
DTO usually contains more data than the client needs. In this case, the DTO can be used for various requirements, and in this way, it can prevent back and forth to the server. Whether to use the same DTO for requests and responses or to put different DTOs for them is a decision related to the request and response structure. If the structures are similar, a DTO can also be used.
Changeable or not is another decision that should be considered in DTO design. No general rule approves one of these structures and negates the other.
Record Set can be considered as the DTO defined for the database.
DTOs should always be able to be serialized so that they can be transferred along the network. For DTOs to be serializable, we must keep them as simple as possible. To serialize DTO using .NET, it is unnecessary to do much work, and .NET has provided the possibility of serializing to various formats such as JSON or XML.
DTOs can be generated automatically by using code generators. To automatically generate DTO, you can also benefit from reflection. The difference between these two methods is the simplicity and cost of implementation.
Mapper can be used to connect the domain model with DTO.
The lazy load design pattern can also use DTO in asynchronous mode.
Consequences: Advantages

This design pattern creates a loose connection between the domain and presentation layers.
Because of this, the data needed by the client is provided to him with the least back and forth to the server, and the efficiency is improved.
Consequences: Disadvantages

The number of DTOs is also proportionally increased in large programs with a lot of existence. This causes an increase in complexity, the volume of the code, and, finally, an increase in the coding time.
Applicability:

Using this design pattern can be useful to transfer data between the client and the server to minimize the back and forth to the server.
One of the uses of DTO is to transfer data between different software layers.
Related patterns:

Some of the following design patterns are not related to the Data Transfer Object design pattern, but to implement this design pattern, checking the following design patterns will be useful:

Remote facade
Lazy load
Conclusion
In this chapter, you got acquainted with the distribution design patterns, such as remote facades and data transfer object design patterns. You also learned how to design communication in distributed software suitably and efficiently and move the data across this software. In the next chapter, you will learn offline concurrency design patterns.

Join our book's Discord space

Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com