Author Archives: user

C# 六种方式实现精确计时

C# 六种方式实现精确计时

根据综合网上的一些文章,精确计时主要有以下几种方式

1 调用WIN API中的GetTickCount

[DllImport("kernel32")]static extern uint GetTickCount();

从操作系统启动到现在所经过的毫秒数,精度为1毫秒,经简单测试发现其实误差在大约在15ms左右

缺点:返回值是uint,最大值是2的32次方,因此如果服务器连续开机大约49天以后,该方法取得的返回值会归零

用法:

uint s1 = GetTickCount();
Thread.Sleep(2719);
Console.WriteLine(GetTickCount() - s1);  //单位毫秒

2 调用WIN API中的timeGetTime 推荐

[DllImport("winmm")]
static extern uint timeGetTime();

常用于多媒体定时器中,与GetTickCount类似,也是返回操作系统启动到现在所经过的毫秒数,精度为1毫秒。

一般默认的精度不止1毫秒(不同操作系统有所不同),需要调用timeBeginPeriod与timeEndPeriod来设置精度

[DllImport("winmm")]
static extern void timeBeginPeriod(int t);
[DllImport("winmm")]
static extern void timeEndPeriod(int t);

缺点:与GetTickCount一样,受返回值的最大位数限制。

用法:

timeBeginPeriod(1);uint start = timeGetTime();
Thread.Sleep(2719);
Console.WriteLine(timeGetTime() - start);  //单位毫秒timeEndPeriod(1);

3 调用.net自带的方法System.Environment.TickCount

获取系统启动后经过的毫秒数。经反编译猜测它可能也是调用的GetTickCount,但是它的返回值是int,而GetTickCount与timeGetTime方法的原型中返回值是DWORD,对应C#中的uint,难道.NET对System.Environment.TickCount另外还做了什么处理么?
缺点:与GetTickCount一样,受返回值的最大位数限制。

用法:

int aa = System.Environment.TickCount;
Thread.Sleep(2719);
Console.WriteLine(System.Environment.TickCount - aa); //单位毫秒

注:经过测试,发现GetTickCount、System.Environment.TickCount也可以用timeBeginPeriod与timeEndPeriod来设置精度,最高可将精度提高到1毫秒。不知是什么原因?

4 调用WIN API中的QueryPerformanceCounter

[DllImport("kernel32.dll ")]
static extern bool QueryPerformanceCounter(ref   long lpPerformanceCount);

用于得到高精度计时器(如果存在这样的计时器)的值。微软对这个API解释就是每秒钟某个计数器增长的数值。
如果安装的硬件不支持高精度计时器,函数将返回false需要配合另一个API函数QueryPerformanceFrequency。

[DllImport("kernel32")]
static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency);
 QueryPerformanceFrequency返回硬件支持的高精度计数器的频率,如果安装的硬件不支持高精度计时器,函数将返回false。

用法:

long a = 0;
QueryPerformanceFrequency(ref a);long b = 0, c = 0;
QueryPerformanceCounter(ref b);
Thread.Sleep(2719);
QueryPerformanceCounter(ref c);
Console.WriteLine((c - b) / (decimal)a);  //单位秒

精度为百万分之一秒。而且由于是long型,所以不存在上面几个API位数不够的问题。

缺点:在一篇文章看到,该API在节能模式的时候结果偏慢,超频模式的时候又偏快,而且用电池和接电源的时候效果还不一样(笔记本)
原文地址:http://delphi.xcjc.net/viewthread.php?tid=1570
未经过超频等测试,如果是真的,那该API出来的结果就可能不准。

5 使用.net的System.Diagnostics.Stopwatch类 推荐

Stopwatch 在基础计时器机制中对计时器的刻度进行计数,从而测量运行时间。如果安装的硬件和操作系统支持高分辨率性能的计数器,则 Stopwatch 类将使用该计数器来测量运行时间;否则,Stopwatch 类将使用系统计数器来测量运行时间。使用 Frequency 和 IsHighResolution 两个静态字段可以确定实现 Stopwatch 计时的精度和分辨率。

实际上它里面就是将QueryPerformanceCounter、QueryPerformanceFrequency两个WIN API封装了一下,如果硬件支持高精度,就调用QueryPerformanceCounter,如果不支持就用DateTime.Ticks来计算。

用法:

Stopwatch sw = new Stopwatch();
sw.Start();
Thread.Sleep(2719);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks / (decimal)Stopwatch.Frequency);

6 使用CPU时间戳进行更高精度计时

原文地址:http://www.chinaunix.net/jh/23/110190.html

该方法的原理我不是很明白,硬件知识太匮乏了。精度是ns

在C#中要用该方法必须先建立一个托管C++项目(因为要内嵌汇编),编译成DLL供c#调用,有点麻烦。

C++代码:

// MLTimerDot.h
 #pragma once 
using namespace System; 
namespace MLTimerDot { 
        //得到计算机启动到现在的时钟周期
        unsigned __int64 GetCycleCount(void)
        {
                _asm  _emit 0x0F
                _asm  _emit 0x31
        } 

        //声明 .NET 类
        public __gc class MLTimer
        {        public:
                MLTimer(void)
                {

                } 
                //计算时钟周期
                UInt64 GetCount(void)
                {                        return GetCycleCount();
                }

        };
}

C#调用:

long a = 0;
QueryPerformanceFrequency(ref a);

MLTimerDot.MLTimer timer = new MLTimerDot.MLTimer();ulong ss= timer.GetCount();
Thread.Sleep(2719);
Console.WriteLine((timer.GetCount() - ss) / (decimal)a);

缺点:和QueryPerformanceCounter一样,结果不太稳定。

我的结论:常规应用下timeGetTime完全够用了,将精度调到1毫秒,大部分境况都够用。System.Diagnostics.Stopwatch由于调用方便,也推荐使用

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650849163&idx=1&sn=07ef704fdcdef7a44d2239ec72503fda&chksm=f1451263c6329b75cc6c8e21db0a720bf6ba0e32b3dadd219e3a65f7073e92e463059ca6ee2c&cur_album_id=1924294891300290563&scene=190#rd

C#获取文件信息大全

C#获取文件信息大全

在.NET开发中,用C#获取文件信息是比较常见的操作。通过获取文件信息,我们可以了解文件的属性、大小、创建日期、修改日期等各种元数据信息。比如我们网站需要上传文件,可以用扩展名称控制上传的类型,获取文件的大小来限制上传文件等。本文将介绍一系列C#中获取文件信息的方法,帮助你轻松获取所需信息并进行文件处理。

1、使用FileInfo类获取文件信息

C#中提供了FileInfo[1]类,它封装了文件的信息和操作。通过实例化FileInfo对象,可以轻松获取文件的各种信息。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "C:\\Users\\username\\Documents\\example.txt";
        FileInfo fileInfo = new FileInfo(filePath);

        Console.WriteLine("文件名称:" + fileInfo.Name);
        Console.WriteLine("文件全路径:" + fileInfo.FullName);
        Console.WriteLine("文件大小(字节):" + fileInfo.Length);
        Console.WriteLine("文件创建时间:" + fileInfo.CreationTime);
        Console.WriteLine("文件最后修改时间:" + fileInfo.LastWriteTime);
        // 可以根据需要获取更多属性
    }
}

2、使用File类获取文件信息

除了FileInfo类,C#中的File[2]类也提供了获取文件信息的一些静态方法。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "C:\\Users\\username\\Documents\\example.txt";
        // 获取文件大小(字节)
        long fileSize = new FileInfo(filePath).Length;
        Console.WriteLine("文件大小(字节):" + fileSize);
        // 获取文件创建时间
        DateTime creationTime = File.GetCreationTime(filePath);
        Console.WriteLine("文件创建时间:" + creationTime);
        // 获取文件最后修改时间
        DateTime lastWriteTime = File.GetLastWriteTime(filePath);
        Console.WriteLine("文件最后修改时间:" + lastWriteTime);

        // 获取文件属性
        FileAttributes attributes = File.GetAttributes(filePath);
        Console.WriteLine("文件属性:" + attributes);
    }
}

3、使用Directory类获取目录信息

如果需要获取目录下的文件信息,可以使用Directory[3]类。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string directoryPath = "C:\\Users\\username\\Documents";
        // 获取目录下的文件列表
        string[] files = Directory.GetFiles(directoryPath);
        foreach (string file in files)
        {
            Console.WriteLine("文件名:" + Path.GetFileName(file));
            Console.WriteLine("文件大小(字节):" + new FileInfo(file).Length);
            Console.WriteLine("文件创建时间:" + File.GetCreationTime(file));
        }
    }
}

4、获取文件的扩展名和类型

如果你要获取文件的扩展名或类型(MIME类型)等,可以使用Path[4]类的一些静态方法。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "C:\\Users\\username\\Documents\\example.txt";
        //获取文件的全路径
        Console.WriteLine("获取文件的全路径:" + Path.GetFullPath(filePath););
        //获取文件所在的目录
        Console.WriteLine("获取文件所在的目录:" + Path.GetDirectoryName(filePath));
        //获取文件的名称含有后缀  example.txt
        Console.WriteLine("获取文件的名称含有后缀:" + Path.GetFileName(filePath));
         //获取文件的名称没有后缀 example
        Console.WriteLine("获取文件的名称没有后缀:" + Path.GetFileNameWithoutExtension(filePath));
        //获取路径的后缀扩展名称(包含点号)  .txt
        Console.WriteLine("获取路径的后缀扩展名称:" + Path.GetExtension(filePath));
        //获取路径的根目录 C:\
        Console.WriteLine("获取路径的根目录:" + Path.GetPathRoot(filePath));

        // 获取文件类型(MIME类型)
        string mimeType = "application/unknown"; // 默认值
        if (!string.IsNullOrEmpty(extension))
        {
            Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(extension);
            if (regKey != null && regKey.GetValue("Content Type") != null)
            {
                mimeType = regKey.GetValue("Content Type").ToString();
            }
        }
        Console.WriteLine("文件类型:" + mimeType);
    }

}

5、结语

无论是使用FileInfo类、File类、Directory类还是Path类,C#提供了多种获取文件信息的方法,可以根据具体需求选择合适的方法来获取文件的属性、大小、时间等信息。更多信息大家可以参考官网。希望本文对你在C#文件处理中有所帮助,欢迎留言或者吐槽。

参考:
[1] https://learn.microsoft.com/zh-cn/dotnet/api/system.io.fileinfo
[2]https://learn.microsoft.com/zh-cn/dotnet/api/system.io.file
[3]https://learn.microsoft.com/zh-cn/dotnet/api/system.io.directory
[4]https://learn.microsoft.com/zh-cn/dotnet/api/system.io.path

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650851338&idx=1&sn=3aa89d0ad0b0a89b083dc7fc30a7d074&chksm=f14565e2c632ecf47c7fd358187bee7b3e1477659ff7a199e385e0e66ad4de71a0c6fb06aed0&cur_album_id=1924294891300290563&scene=190#rd

C# 中 decimal、float 和 double 的使用指南

不再犯错!C# 中 decimal、float 和 double 的使用指南(基础)

前言

在 C# 编程中,我们常常需要处理带小数点的数值,比如在计算商品价格、科学计算或是处理坐标点时,这时就会用到三种常见的浮点数类型:decimal、float 和 double。虽然它们看起来很相似,但在使用时,它们的精度、存储和适用场景都有显著区别。今天我们就来聊聊这三种数据类型的区别,看看它们分别适合在哪些场景中使用。

1. 基本概念

  • float(单精度浮点数):占用 32 位存储空间,通常用于那些对精度要求不高但需要节省内存的场景,比如游戏开发中的坐标计算。定义时需要在数字后加 f 或 F,比如:float x = 3.14f;。
  • double(双精度浮点数):占用 64 位存储空间,是 C# 中的默认浮点类型,精度比 float 高,适用于大部分需要小数计算的场合。通常不需要加后缀,但可以用 d 或 D 后缀来标识,比如:double y = 3.14;。
  • decimal(高精度浮点数):占用 128 位存储空间,专为财务和货币计算设计,精度远高于 float 和 double,适用于对精度要求极高的场景。定义时需要在数字后加 m 或 M,比如:decimal z = 3.14m;。

2. 精度与范围

理解它们的精度和范围很重要,这直接影响到我们选用哪种数据类型。

  • float:约有 6-7 位有效数字,数值范围大,但精度较低。
  • double:约有 15-16 位有效数字,是 C# 中的默认小数类型,平衡了范围和精度。
  • decimal:约有 28-29 位有效数字,专为处理极高精度要求的情况,比如金融应用中的货币计算。

3. 实际使用场景

  • float 使用场景:适合对精度要求不高的图形计算,如 3D 游戏开发中的坐标和颜色计算。比如,我们在渲染一个复杂的 3D 模型时,坐标的轻微误差是可以接受的,而使用 float 可以减少内存消耗,提高性能。
  • double 使用场景:适用于科学计算、物理仿真、天体运算等。大部分需要小数的场景都可以用 double,比如计算一个物体的轨迹,这时候既需要较大的数值范围,也需要一定的精度。
  • decimal 使用场景:适合金融计算、会计系统等对数值精度要求极高的情况。比如,在计算商品的总价时,我们需要确保每一分钱的精度,不然就可能导致财务计算错误。

4. 计算案例对比

让我们来看一个简单的计算案例来感受这些类型之间的差异。

class Program
{
    static void Main()
    {
        float floatResult = 1.0f / 3.0f;
        double doubleResult = 1.0 / 3.0;
        decimal decimalResult = 1.0m / 3.0m;

        Console.WriteLine($"Float: {floatResult}");   // 输出:0.3333333
        Console.WriteLine($"Double: {doubleResult}"); // 输出:0.3333333333333333
        Console.WriteLine($"Decimal: {decimalResult}"); // 输出:0.3333333333333333333333333333
    }
}

通过这个简单的 1 除以 3 的例子可以看到:

  • float 保留的位数很少,仅仅 6-7 位有效数字,足够简单的图形运算。
  • double 保留了大约 15-16 位有效数字,可以满足大部分科学计算。
  • decimal 则几乎没有精度损失,保留了 28-29 位有效数字,非常适合货币计算。

5. 为什么选择不同的数据类型?

选择 float 是因为它占用内存少,速度快,非常适合大量数据的简单计算,如游戏中的渲染。

选择 double 是因为它是大部分应用的默认选择,提供了足够的精度和计算效率,适合通用的数学计算。

选择 decimal 则是因为它能处理非常精确的小数运算,尤其是在涉及财务和货币的场合,避免了浮点数的精度问题。

总结

在 C# 中,float、double 和 decimal 三种数据类型各有其用途。理解它们的精度和使用场景,能够帮助你在编程中做出更合适的选择。希望这篇文章能帮助你更好地理解和使用这三种数据类型,写出更加可靠的代码!大家使用float、double 和 decimal 场景有那些?欢迎留言讨论!

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650859746&idx=1&sn=53123b677fa22fb9454df73623ddf3c2&chksm=f145450ac632cc1ca8c20de66439fd926238f7dbd102c3b66e847301d600e884e86838f38175&cur_album_id=1924294891300290563&scene=190#rd

揭秘:C#语法中“_ =” 是什么?

揭秘:C#语法中“_ =” 是什么?

在C#编程世界中,语法的精妙之处往往体现在那些看似微小却极具影响力的符号与结构之中。其中,“_ =” 这一组合突然出现还真不知道什么意思。本文将深入剖析“_ =” 的含义、工作原理及其在实际编程中的广泛应用,揭示其作为C#语法奇兵的重要角色。

一、下划线 _:神秘的弃元符号

下划线 _ 在C#中并非默默无闻的配角,而是一个承载特殊使命的标识符,被称为“弃元”(Discard)。顾名思义,弃元符号用于表示在特定代码上下文中,程序员对某个变量或表达式的具体值并无兴趣。它充当了一个占位符,传递出“此处无需关注值”的明确信号。

二、“_ =”:赋值与弃置的巧妙融合
当弃元符号 _ 与赋值操作符 = 相遇,便形成了独特的“_ =” 结构。这一结构的核心功能是对右侧表达式的结果执行显式赋值,同时声明程序员不打算在后续代码中使用这个赋值结果。这一语法构造巧妙地结合了赋值动作与弃置意图,赋予了C#代码更强的表现力和更高的意图透明度。

三、“_ =” 的实战应用
1. 抑制编译器警告
C#编译器以其严谨的类型检查和代码质量提示著称。当一个函数调用或表达式计算后返回一个值,而程序员未对其进行任何处理时,编译器通常会发出“未使用返回值”的警告。这一警告旨在提醒开发者可能忽视了预期的行为或资源未得到妥善处理。

通过使用 “_ =” 结构,开发者可以优雅地消除此类警告。它明确地接收并忽略返回值,向编译器传达了开发者知晓该返回值存在但选择不使用的意图。如此一来,既避免了因警告干扰开发者的注意力,又保持了代码的整洁,无需引入不必要的变量赋值。

示例:

public void LogMessage(string message)
{
    _ = WriteToLogFile(message); // 不关心 WriteToLogFile 返回的写入状态,仅关注日志记录行为本身
}

2. 确保副作用的执行
在某些情况下,方法或运算符的主要价值不在于其返回值,而在于执行的副作用,如触发事件、更新内部状态、发送网络请求等。尽管这些操作可能伴随有返回值,但程序员的关注焦点往往在于它们的实际执行而非返回值本身。

通过 “_ =” 显式赋值,即使不关心返回值,相关的副作用也能得到确保执行。此举强化了代码的意图表达,使读者一眼就能识别出哪些操作是为副作用服务,而非依赖其返回结果。

示例:

public void NotifySubscribers(EventData data)
{
    _ = eventPublisher.Publish(data); // 重点在于发布事件,不在乎 Publish 方法返回的发布结果
}

3. 模式匹配中的显式丢弃
虽然在模式匹配语句(如 switch 表达式或 is 检查)中,“_ =” 结构并未直接出现,但弃元符号 _ 依然发挥着关键作用。在这里,它表示匹配成功后并不关心绑定到变量的具体值,仅关注是否符合某种类型或模式。

示例:

if (obj is IDisposable _)
{
    Console.WriteLine("The object is disposable.");
}

// 或者在 switch 表达式中
switch (input)
{
    case int _ when input > 0:
        Console.WriteLine("Positive integer");
        break;
    // ...
}

四、结语
C# 语法奇兵 “_ =” 以其简洁而有力的存在,为开发者提供了清晰表达舍弃意图的利器。无论是抑制编译器警告、确保副作用执行,还是在模式匹配中显式丢弃无关信息,“_ =” 都展现出强大的实用性与表达力,其实就是不想使用的元素变量。掌握并适时运用这一语法特性,无疑能助力开发者编写出更加精炼、意图鲜明的C#代码,提升整体编程体验与代码质量。

希望本文对大家有所收获,大家用过“_ =”吗?或对它有啥看法?欢迎留言讨论!

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650856700&idx=1&sn=75b3d78799f0e84c80294069a5f0bfa4&chksm=f1457114c632f802f770a801a473289664a42f5b7605b8339ef2b5be601008134ce80843399a&cur_album_id=1924294891300290563&scene=190#rd

C#重构代码的8种基本方法

C#重构代码的8种基本方法

重构是指在不改变代码行为的情况下,提高代码的可读性、可维护性和效率的过程。本文介绍8种重构C#代码的基本方法。

  1. 删除冗余代码
    重构前

    List<int> userIds = new List<int>();
    userIds.AddRange(output.Select(s => s.UserId).Distinct().ToList());
    userIds = userIds.Distinct().ToList();

    重构后

    var userIds = output.Select(s => s.UserId).Distinct().ToList();
  2. 使用LINQ替代循环
    重构前

    List<int> evenNumbers = new List<int>();
    foreach (var num in numbers) {
    if (num % 2 == 0) {
        evenNumbers.Add(num);
    }
    }

    重构后

    var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
  3. 避免重复代码(封装方法)
    重构前

    var segmentIDs = output.Select(s => s.SegmentID).Distinct().ToList();
    if (segmentIDs.Any()) {
    segments = db.CheckCode
        .Where(c => segmentIDs.Contains(c.CheckCodeID))
        .ToList();
    }

    重构后

    List<int> GetDistinctIDs<T>(IEnumerable<T> source, Func<T, int?> selector) =>
    source.Select(selector).Where(id => id.HasValue).Select(id => id.Value).Distinct().ToList();
  4. 使用空合并和三元运算符
    重构前

    string name;
    if (user != null && user.Name != null) {
    name = user.Name;
    } else {
    name = "Unknown";
    }

    重构后

    string name = user?.Name ?? "Unknown";
  5. 使用var提升可读性
    重构前

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

    重构后

    var numbers = new List<int> { 1, 2, 3, 4 };
  6. 避免嵌套的if语句
    重构前

    if (user != null) {
    if (user.Age > 18) {
        Console.WriteLine("Adult");
    }
    }

    重构后

    if (user == null) return;
    if (user.Age <= 18) return;
    Console.WriteLine("Adult");
  7. 使用字符串插值
    重构前

    string message = string.Format("Hello, {0}! You have {1} messages.", name, count);

    重构后

    string message = $"Hello, {name}! You have {count} messages.";
  8. 使用async/await提升性能
    重构前

    public List<User> GetUsers() {
    return db.Users.ToList();
    }

    重构后

    public async Task<List<User>> GetUsersAsync() {
    return await db.Users.ToListAsync();
    }

总结
在代码重构时,应该始终遵循以下原则:

可读性:代码应当易于理解。
可维护性:让未来的开发者能够轻松修改代码。
性能:避免不必要的计算和冗余操作。
DRY(不要重复自己):提取可复用的逻辑到方法中,提高代码复用性。
译文:csharp.com/article/learn-c-sharp-refactor-code

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650862370&idx=1&sn=d5a8c6b375dc6a9b0f122ffe65593a72&chksm=f1455ecac632d7dc3fd7e2fe0daaefd16ea2ca25f1e7782b3c6527e0af055b22d3a5f33d7818&cur_album_id=1924294891300290563&scene=190#rd

C#之 Dictionary 详解

C#之 Dictionary 详解

基本概念

Dictionary<TKey, TValue>是C#中用于存储键值对集合的泛型类,属于System.Collections.Generic命名空间。它允许使用键(Key)来访问与其关联的值(Value)。其中,TKey表示字典中键的类型,TValue表示字典中值的类型。

Dictionary的基本结构

  • 键(Key):唯一标识集合中的一个元素。键是唯一的,不能有重复。
  • 值(Value):与键相关联的数据。值可以是任意类型,并且可以有重复。
  • 键值对(KeyValuePair):键和值的组合,表示Dictionary中的一个元素。

Dictionary的主要特性

  • 快速访问:通过键可以快速检索到对应的值,平均时间复杂度接近O(1),因为Dictionary<TKey,TValue>类是作为哈希表实现。
  • 唯一键(Key):每个键在Dictionary中都是唯一的,不能重复。
  • 动态大小:Dictionary的大小可以动态调整,当元素数量超过容量时,它会自动扩容。
  • 无序集合:Dictionary中的元素是无序的,不能通过索引来访问它们。

Dictionary的常用操作

以下是C#中Dictionary的常用操作完整代码,其中包括添加元素、访问元素、修改元素、删除元素、检查键或值是否存在,以及遍历元素:

public static void DictionaryOperation()
{
    //创建一个Dictionary来存储学生学号ID和姓名
    Dictionary<int, string> studentDic = new Dictionary<int, string>();

    #region 添加元素

    // Add方法(键必须唯一)
    studentDic.Add(1, "大姚");
    studentDic.Add(2, "小袁");
    studentDic.Add(3, "Edwin");

    // 索引器语法(键不存在时添加,存在时更新)
    studentDic[4] = "Charlie";
    studentDic[5] = "追逐时光者";

    // 安全添加(避免异常)
    bool isAdded = studentDic.TryAdd(6, "小明"); // 返回 false,因键已存在

    #endregion

    #region 访问元素

    // 直接访问(键必须存在,否则会有异常)
    var currentUserName = studentDic[1];
    Console.WriteLine($"当前学生姓名: {currentUserName}");

    // 安全访问(避免异常)
    if (studentDic.TryGetValue(5, outvar getUserName))
    {
        Console.WriteLine($"UserName:{getUserName}");
    }
    else
    {
        Console.WriteLine("当前学生ID不存在");
    }

    #endregion

    #region

    // 修改元素
    studentDic[2] = "大西瓜";

    Console.WriteLine($"修改后的名称:{studentDic[2]}");

    #endregion

    #region 删除元素

    // 删除元素
    bool isRemoved = studentDic.Remove(3);

    Console.WriteLine($"删除结果:{isRemoved}");

    #endregion

    #region 检查键或值是否存在

    // 检查键是否存在
    if (studentDic.ContainsKey(1))
    {
        Console.WriteLine("存在");
    }
    else
    {
        Console.WriteLine("不存在");
    }

    bool isExistcontainsValue = studentDic.ContainsValue("追逐时光者");

    Console.WriteLine($"是否存在:{isExistcontainsValue}");

    #endregion

    #region 遍历元素

    // 遍历元素
    foreach (KeyValuePair<int, string> student in studentDic)
    {
        Console.WriteLine($"ID: {student.Key}, Name: {student.Value}");
    }

    // 使用键的枚举器
    foreach (var key in studentDic.Keys)
    {
        Console.WriteLine($"Key: {key}, Value: {studentDic[key]}");
    }

    // 使用值的枚举器
    foreach (varvaluein studentDic.Values)
    {
        // 注意:这种方式不能直接获取键,只能获取值
        Console.WriteLine($"Value: {value}");
    }

    #endregion
}

参考文章
https://learn.microsoft.com/zh-cn/dotnet/api/system.collections.generic.dictionary-2?view=net-9.0

https://mp.weixin.qq.com/s?__biz=MzIxMTUzNzM5Ng==&mid=2247510982&idx=1&sn=40926d914ed235d887ad0b5cde7db93f&scene=21#wechat_redirect

C#数组集合常用排序方式,你想要的都在这儿

C#数组集合常用排序方式,你想要的都在这儿

前言

程序员的世界,有两件事是永远绕不开的:一个是“加班”,另一个就是“排序”。面对一堆无序的数据,就像看见桌上一堆乱糟糟的电线,心里总是不踏实。而C#给我们提供了非常简洁的工具,让排序变成一种轻松愉快的工作。今天,我们就一起来探索这些C#常用的排序方式吧!别紧张,咱们要用幽默的方式聊聊排序,保证让你笑着学会!

1. 使用 Sort 排序

先来个简单点的,C#里最常用的Array.Sort()。这是C#官方指定的“整理大师”,几乎适用于所有数组场景,调用之后它会直接帮你把数据从小到大排好,速度飞快,还不用操心细节。

代码示例:

int[] numbers = { 5, 1, 4, 3, 2 };
Array.Sort(numbers);
Console.WriteLine(string.Join(", ", numbers)); // 输出: 1, 2, 3, 4, 5

Array.Sort()是那种“你只要给我一个数组,我就给你一个结果”的排序方式,简单粗暴。无论你是多么懒的程序员,都能愉快使用。

2. 使用 LINQ 语句进行排序

如果你觉得Sort太基础,不够炫酷,那么LINQ一定能让你眼前一亮。LINQ不仅让代码更简洁,还能优雅地处理排序逻辑,比如我们用OrderBy对数组进行排序。

代码示例:

int[] numbers = { 5, 1, 4, 3, 2 };
var sortedNumbers = numbers.OrderBy(n => n).ToArray();
Console.WriteLine(string.Join(", ", sortedNumbers)); // 输出: 1, 2, 3, 4, 5

LINQ的语法不仅清晰直观,还带着一种“高大上”的气质。就像你在写诗一样,每一行代码都充满了优雅。

3. 多条件排序

有时候,事情并不是那么简单。比如你想给学生按成绩排序,但是分数相同的还要按名字排,这就是传说中的“多条件排序”。C#的ThenBy可以轻松搞定这种复杂情况。

代码示例:

var students = new[]
{
    new { Name = "Alice", Score = 85 },
    new { Name = "Bob", Score = 90 },
    new { Name = "Charlie", Score = 85 }
};

var sortedStudents = students.OrderBy(s => s.Score).ThenBy(s => s.Name).ToArray();

foreach (var student in sortedStudents)
{
    Console.WriteLine($"{student.Name}: {student.Score}");
}
// 输出:
// Alice: 85
// Charlie: 85
// Bob: 90

看,多条件排序就像比赛中的附加赛,决胜的时候,总有更多规则来决定最终排名。而LINQ的ThenBy正是为这种情况设计的。

4. 多维数据排序

如果你认为一维数组已经足够复杂,那多维数组可能会让你眉头一皱。不过别担心,C#的LINQ依然可以施展魔法,帮助我们轻松排序。

代码示例:

int[,] numbers = { { 3, 5 }, { 1, 4 }, { 2, 6 } };

var sortedNumbers = numbers.Cast<int>().OrderBy(n => n).ToArray();

foreach (var num in sortedNumbers)
{
    Console.Write(num + " "); // 输出: 1 2 3 4 5 6
}

使用Cast和OrderBy,你可以把多维数组“展平”后轻松排序,就像把一盘打结的耳机线理顺,瞬间舒心!

5. 自定义排序规则

排序不只是按照数字大小,也可以基于你自己的规则。比如说,你要按字符串的长度排序?没问题!C#允许你通过委托来自定义排序规则。

代码示例:

string[] fruits = { "apple", "banana", "pear", "grape" };
Array.Sort(fruits, (x, y) => x.Length.CompareTo(y.Length));
Console.WriteLine(string.Join(", ", fruits)); // 输出: pear, apple, grape, banana

这里的自定义排序规则,就像你自己定了个“游戏规则”:谁名字短,谁优先。你要的排序规则,你说了算!

6. 倒序排序

有时候,我们不想从小到大,而是从大到小排列。比如你想按分数从高到低排个榜单,C#同样可以做到!只需在OrderBy后面加个Descending。

代码示例:

int[] numbers = { 5, 1, 4, 3, 2 };
var sortedNumbers = numbers.OrderByDescending(n => n).ToArray();
Console.WriteLine(string.Join(", ", sortedNumbers)); // 输出: 5, 4, 3, 2, 1

想让一切“反其道而行之”?用OrderByDescending,轻松实现逆序排列。

总结

看吧,C#的排序简直是一场“美食大餐”,从Sort到LINQ,从单条件到多条件,还有自定义规则和倒序排列,各种方式应有尽有。无论你面对的是数字、字符串,还是自定义对象,C#都给了你足够的武器,让你在排序的世界里游刃有余。最重要的是,代码不光要写得正确,还要写得优雅、有趣!希望本文对大家有所收获,大家还遇到那些排序,欢迎留言讨论。

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650861003&idx=2&sn=ed064a5c51c9e131443f4a52c68a8929&chksm=f1454023c632c93548ca5dd80c8411259a1e88f78332c44ab545c3e4fdac07c6a58b33d0c8cf&cur_album_id=1924294891300290563&scene=190#rd

C#语言怎样的水平达才能到专家级

C#语言怎样的水平达才能到专家级

某.NET开发小白问:怎样才算精通C#语言?关于精通这个词语比较大,怎么算精没有定论,那就叫专家级别吧!通于是小编查阅各种资料和文章,归纳如下精通C#语言所需要掌握的技能。C#是一种强大的编程语言,被广泛应用于桌面应用程序、Web开发、移动开发和游戏开发等领域。精通C#不仅仅是掌握其语法和功能,而是要深入理解和灵活应用各个方面的知识。精通C#语言需要在多个方面具备深厚的知识和技能。以下从不同知识点详细阐述一个精通C#语言的开发者应具备的能力。

能徒手写出无语法错误的代码

一个精通C#的开发者应当能够在没有IDE辅助的情况下,徒手编写出大部分无语法错误的代码。这要求对C#的语法规则、数据类型、控制结构、类和对象的定义及使用等有深刻理解和熟练掌握。

了解大多数C#关键词(Keywords)

C#包含一系列关键词,如abstract, async, await, base, const, delegate, event, extern, lock, sealed, yield等。精通C#意味着了解这些关键词的含义和使用场景,能够在代码中灵活应用。

了解掌握Compiler Error

精通C#的开发者需要了解常见的编译错误及其解决方法。包括但不限于:

  • 语法错误:如缺少分号、花括号不匹配等。
  • 类型错误:如类型不匹配、无法转换类型等。
  • 访问错误:如尝试访问私有成员、未初始化变量等。
  • 逻辑错误:如使用未赋值的局部变量、死循环等。

掌握.NET异常(Exception)

异常处理是C#编程的重要部分。精通C#需要了解常见的异常类型及其处理方法,如NullReferenceException, IndexOutOfRangeException, InvalidOperationException, ArgumentNullException, IOException等,并能够自定义和抛出异常。

了解大多数.NET特性(Attribute)

特性(Attributes)是C#中的元数据。精通C#需要了解常用的特性及其应用场景,如[Obsolete], [Serializable], [DllImport], [TestMethod], [DataMember]等,并能够定义和使用自定义特性。

了解大多数.NET命名空间(Namespace)

.NET框架和.NET Core包含大量命名空间,精通C#需要了解常见命名空间及其功能,如System, System.Collections.Generic, System.Linq, System.IO, System.Net, System.Threading等,并能够在项目中正确引用和使用。

了解大多数注释标记

适当的注释能够提高代码的可读性和维护性。精通C#需要了解并能正确使用各种注释标记,如///用于XML文档注释,//用于单行注释,/ /用于多行注释等。

了解与其他平台的互操作

精通C#需要了解与其他平台的互操作性,包括:

  • C++:通过P/Invoke调用C++库。
  • Java:使用IKVM.NET或其他桥接工具。
  • Python:通过Python.NET实现互操作。这些互操作技术扩展了C#的应用范围。

掌握NuGet打包和使用

NuGet是.NET平台的包管理器,精通C#需要了解如何创建、发布和使用NuGet包。能够在项目中有效管理依赖关系,提高开发效率。

了解大多数.NET CLI命令

.NET CLI(命令行接口)是.NET开发的重要工具。精通C#需要了解并能熟练使用常见的CLI命令,如dotnet new, dotnet build, dotnet run, dotnet test, dotnet publish等。

开发工具的熟练度

精通C#还包括对开发工具的熟练使用,如Visual Studio。熟悉其菜单项、功能、快捷键能够提高开发效率。此外,还应了解一些开源的、轻量的IDE,如Visual Studio Code、Rider等,以及LinqPad、CS-Script、.NET Interactive等工具。

基础类型和集合

精通C#需要熟练掌握基础类型和集合类,如int, double, string, List, Dictionary<TKey, TValue>等,能够灵活运用这些类型和集合进行数据处理。

反射和动态编程

反射和动态编程是C#的高级特性。精通C#需要了解如何使用反射获取类型信息、调用方法和访问字段,以及如何使用动态编程实现灵活的代码。

多线程编程

多线程编程是提升应用性能的重要手段。精通C#需要了解多线程的基本概念和实现方法,如Thread, Task, async/await等,能够有效地管理线程,避免常见的并发问题。

表达式树

表达式树是C#的一项强大特性。精通C#需要了解如何创建和操作表达式树,应用于动态查询、编译和执行代码等场景。

正则表达式

正则表达式是处理字符串的强大工具。精通C#需要了解正则表达式的基本语法和使用方法,能够在代码中进行复杂的字符串匹配和替换操作。

加密解密

数据安全是现代应用程序开发的重要部分。精通C#需要了解常见的加密解密算法和技术,如对称加密(AES),非对称加密(RSA),哈希算法(SHA-256)等。

IO操作和资源文件

精通C#需要了解如何进行文件和流的操作,如读取和写入文本文件、二进制文件等。还需要了解如何使用资源文件进行多语言支持和资源管理。

编译机制、IL、GC

C#代码最终编译成中间语言(IL),然后由CLR执行。精通C#需要了解编译过程和IL的基本结构,理解垃圾回收(GC)的工作原理,能够进行性能优化和内存管理。

动态编译和反编译

动态编译和反编译是C#的高级应用。精通C#需要了解如何在运行时动态编译代码(如使用System.CodeDom.Compiler或Roslyn),以及如何反编译和分析程序集。

MSBuild配置和自定义Build过程

MSBuild是.NET项目的构建工具。精通C#需要了解如何配置和修改MSBuild脚本,自定义Build过程,以满足复杂的构建需求。

源代码分析(Code Analysis)

精通C#需要了解如何进行源代码分析,使用工具(如FxCop、SonarQube)进行代码质量检查,识别潜在问题和优化代码结构。

跨平台和跨架构开发

.NET Core和.NET 5+支持跨平台开发。精通C#需要了解.NET在不同操作系统(Windows、Linux、MacOS)和CPU架构(x86、x64、ARM)下的差异和兼容性问题,能够编写和部署跨平台应用。

单元测试、基准测试、性能分析

测试和性能分析是保证代码质量的重要手段。精通C#需要了解如何编写单元测试(如使用xUnit、NUnit),进行基准测试(如使用BenchmarkDotNet)和性能分析(如使用dotTrace)。

长时间运行程序

一些应用需要长时间运行。精通C#需要了解如何编写和优化这些程序,特别是资源回收和控制能力,确保程序的稳定性和性能。

结语

精通C#语言需要在多个方面深入学习和实践,从基础语法到高级特性,从开发工具到测试和性能分析,涵盖广泛的知识领域。通过不断学习和积累实际开发经验,你将逐步达到精通C#语言的水平,成为一名高效、专业的C#开发者。大家觉得精通C#语言或成为专家还需要那些牛逼的技能呢?欢迎留言讨论。

https://mp.weixin.qq.com/s?__biz=MzI2NDE1MDE1MQ==&mid=2650858151&idx=1&sn=ea28a5ce9edba0094b8c0bf01b36f35d&chksm=f1454f4fc632c659d9357916392b709f9ceb33e268813babe02e737e41e151888084d93a97b7&cur_album_id=1924294891300290563&scene=190#rd

解密 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