C# 基础:Generic泛型,Delegate委托,Event事件,Extension Method方法扩展,Linq,Nullable,异常处理,Async-await

目录

Generic(泛型)

泛型类的基本用法

泛型方法

Delegate

委托的声明

委托的实例化

委托的调用

委托的链式调用

委托与事件

示例

Lambda

Lambda表达式的基本语法

Lambda表达式与委托

Lambda表达式在委托链式调用中的应用

总结

Event

事件的声明

事件的订阅和取消订阅

注意事项

Entension methods

如何定义扩展方法

使用场景

示例 1-String entension

示例 2-.NET 6 API -servicecollections (DI)

注意事项

Linq

LINQ 的类型

LINQ 查询语法

示例:LINQ to Objects

LINQ 特性

LINQ 常见用法

1. 筛选(Filtering)

2. 投影(Projection)

3. 排序(Sorting)

4. 分组(Grouping)

5. 聚合(Aggregation)

6. 查找(Finding)

7. 连接(Joining)

8. 分页(Paging)

9. 转换为其他类型

10. 自定义扩展方法

11. 使用LINQ查询并返回前100条记录 (EF Core)

Nullable type

可空值类型(Nullable Value Types)

可空引用类型(Nullable Reference Types, C# 8.0及更高版本)

启用和禁用可空引用类型

Dynamic

使用 dynamic 的例子1

使用 dynamic 的例子2

注意事项

Exception Handling

基本结构

抛出异常

捕获特定类型的异常

使用finally块

异常过滤

注意事项

async/await

基本概念

使用示例

继续学习


Generic(泛型)

C# 中的泛型(Generics)是 .NET Framework 2.0 引入的一个非常强大的特性,它允许你定义类、接口、方法、委托等时,指定一个或多个类型参数(Type Parameters)。这些类型参数在类、接口、方法等的声明中用作占位符,在实例化泛型类型或调用泛型方法时,必须用具体的类型替换这些占位符。这样做的好处包括代码复用、类型安全以及更好的性能。

泛型类的基本用法

假设我们有一个简单的类,用于存储两个相等的对象,我们可以使用泛型来创建这个类,使得它可以存储任何类型的两个对象。

public class Pair<T>  
{  
    public T First { get; set; }  
    public T Second { get; set; }  
  
    public Pair(T first, T second)  
    {  
        First = first;  
        Second = second;  
    }  
}  
  
// 使用泛型类  
Pair<int> intPair = new Pair<int>(1, 2);  
Pair<string> stringPair = new Pair<string>("Hello", "World");

在这个例子中,Pair<T> 是一个泛型类,其中 T 是一个类型参数。在创建 Pair 类的实例时,我们可以指定 T 的具体类型,比如 int 或 string

泛型方法

除了泛型类,C# 还支持泛型方法。泛型方法允许你在方法签名中指定一个或多个类型参数。

public class GenericMethods  
{  
    // 泛型方法  
    public static void Foo<T>(ref T lhs, ref T rhs)  
    {  
        T temp = lhs;  
        lhs = rhs;  
        rhs = temp;  
    }  
  
    // 使用泛型方法  
    static void Main()  
    {  
        int a = 1, b = 2;  
        Foo(ref a, ref b);  
        Console.WriteLine($"a = {a}, b = {b}");  
  
        string str1 = "Hello", str2 = "World";  
        Foo(ref str1, ref str2);  
        Console.WriteLine($"str1 = {str1}, str2 = {str2}");  
    }  
}

在这个例子中,Foo<T> 是一个泛型方法,它接受两个类型为 T 的参数(通过 ref 关键字传递),并在方法内部交换它们的值。由于 T 是一个类型参数,Swap 方法可以用于任何类型的变量。 

    public class Utilities<T> where T : IComparable, new()
    {
        public int Max(int a, int b)
        {
            return a > b ? a : b;
        }

        public void DoSomething(T value)
        {
            var obj = new T();
        }

        public T Max(T a, T b)
        {
            return a.CompareTo(b) > 0 ? a : b;
        }
    }

    public class Product
    {
        public string Title { get; set; }
        public float Price { get; set; }
    }

    public class DiscountCalculator<TProduct> where TProduct : Product
    {
        public float CalculateDiscount(TProduct product)
        {
            return product.Price;
        }
    }

Delegate

C# 中的委托(Delegate)是一种类型,它定义了方法的类型,使得可以将方法当作另一个方法的参数、返回类型或变量类型。委托是 C# 中实现事件和回调的基础。它们提供了一种类型安全的方式来封装方法引用,使得可以在运行时动态地调用这些方法。

委托的声明

委托的声明类似于方法的声明,但它前面有一个 delegate 关键字。委托可以指向具有相同签名(即相同返回类型和参数列表)的任何方法。

public delegate void MyDelegate(string message);

这个声明定义了一个名为 MyDelegate 的委托类型,它可以指向任何接受一个 string 参数并返回 void 的方法

委托的实例化

委托对象可以使用 new 关键字后跟委托类型和要引用的方法名来实例化。

MyDelegate del = new MyDelegate(MyMethod);

//或者,从 C# 2.0 开始,可以使用更简洁的语法(称为匿名方法或方法组转换):
MyDelegate del = MyMethod; // 方法组转换

//或者,使用 Lambda 表达式(从 C# 3.0 开始):
MyDelegate del = message => Console.WriteLine(message);

委托的调用

委托对象可以像方法一样被调用,并且会执行它所引用的方法。

del("Hello, Delegate!");

委托的链式调用

委托还支持链式调用,即一个委托可以引用多个方法。当调用这样的委托时,它会按顺序调用所有引用的方法。

MyDelegate del1 = MyMethod1;  
MyDelegate del2 = MyMethod2;  
  
// 将两个委托组合成一个委托  
MyDelegate delChain = del1 + del2;  
  
// 调用时,会先调用 MyMethod1,然后调用 MyMethod2  
delChain("Hello, Chain!");

委托与事件

在 C# 中,事件通常是通过委托来实现的。事件是一种使类或对象能够提供通知的机制。当类中的某些事情发生时(例如,按钮被点击),它可以触发一个事件,并且任何对该事件感兴趣的对象都可以订阅该事件并在事件发生时收到通知。

示例

下面是一个简单的委托示例,展示了如何声明委托、实例化委托以及调用委托引用的方法。

using System;  
  
delegate void MyDelegate(string message);  
  
class Program  
{  
    static void Main(string[] args)  
    {  
        MyDelegate del = DisplayMessage;  
        del("Hello, Delegate!");  
  
        // 使用 Lambda 表达式  
        MyDelegate lambdaDel = message => Console.WriteLine($"Lambda: {message}");  
        lambdaDel("Hello, Lambda!");  
    }  
  
    static void DisplayMessage(string message)  
    {  
        Console.WriteLine($"DisplayMessage: {message}");  
    }  
}

Lambda

在C#中,Lambda表达式是委托的一种非常便捷和强大的表示方式。Lambda表达式通常用于编写内联的匿名方法,它们可以分配给委托类型的变量或者作为方法的参数传递给期望委托作为参数的方法。

Lambda表达式的基本语法

Lambda表达式的基本语法如下:

(参数列表) => 表达式体

 或者,如果Lambda表达式包含多条语句,则可以使用大括号来定义语句块,并且需要显式地指定返回类型(尽管在某些情况下,编译器可以推断出返回类型):

(参数列表) => {  
    // 语句块  
    return 表达式;  
}

Lambda表达式与委托

Lambda表达式经常与委托一起使用,因为它们提供了一种简洁的方式来创建委托实例。由于Lambda表达式本质上是一个匿名方法,因此它们可以直接赋值给任何与之兼容的委托类型的变量。

Lambda表达式在委托链式调用中的应用

在委托的链式调用中,Lambda表达式同样可以发挥重要作用。通过Lambda表达式,我们可以轻松地将多个操作组合成一个委托链,从而在单个委托调用中执行多个操作。

例如,假设我们有一个委托类型MyDelegate,它定义了一个接受string参数并返回void的方法签名。我们可以使用Lambda表达式来创建多个这样的方法,并将它们链接在一起:

delegate void MyDelegate(string message);  
  
// 创建委托实例  
MyDelegate del1 = message => Console.WriteLine($"Del1: {message}");  
MyDelegate del2 = message => Console.WriteLine($"Del2: {message.ToUpper()}");  
  
// 将两个委托链接在一起  
MyDelegate delChain = del1 + del2;  
  
// 调用链式委托  
delChain("Hello, Lambda Chain!");

在这个例子中,delChain委托在调用时会先执行del1引用的Lambda表达式(打印原始消息),然后执行del2引用的Lambda表达式(打印大写消息)。 

总结

Lambda表达式是C#中一种非常强大且灵活的特性,它们与委托的结合使用使得我们能够以简洁而富有表达力的方式编写代码。在委托的链式调用中,Lambda表达式提供了一种便捷的方式来定义和操作多个操作序列,从而提高了代码的可读性和可维护性。

 

Event

在C#中,事件(Event)是一种使类或对象能够提供通知的机制。当类中的某些事情发生时(例如,按钮被点击、数据发生更改等),它可以触发一个事件,并且任何对该事件感兴趣的对象都可以订阅(或监听)该事件,并在事件发生时收到通知。这种机制允许类与其使用者之间实现松耦合,因为类的使用者不需要知道类的内部实现细节,只需订阅它们感兴趣的事件即可。

事件的声明

在C#中,事件通常是通过声明一个事件成员来定义的,该成员使用event关键字,并且其类型是一个委托。这个委托定义了可以订阅该事件的方法的签名。

public delegate void MyEventHandler(object sender, MyEventArgs e);  
  
public class MyEventArgs : EventArgs  
{  
    // 可以添加一些属性或字段来传递事件相关的数据  
    public string Message { get; set; }  
  
    public MyEventArgs(string message)  
    {  
        Message = message;  
    }  
}  
  
public class MyClass  
{  
    // 声明事件成员  
    public event MyEventHandler MyEvent;  
  
    // 触发事件的方法  
    protected virtual void OnMyEvent(MyEventArgs e)  
    {  
        MyEventHandler handler = MyEvent;  
        if (handler != null)  
        {  
            handler(this, e);  
        }  
    }  
  
    // 当某个条件满足时,触发事件  
    public void DoSomething()  
    {  
        // ... 执行一些操作  
        OnMyEvent(new MyEventArgs("Hello, Event!"));  
    }  
}

事件的订阅和取消订阅

任何外部类或对象都可以订阅一个类的事件,方法是将一个与事件委托兼容的方法(或Lambda表达式)附加到事件上。同样,也可以随时取消订阅事件。

class Program  
{  
    static void Main(string[] args)  
    {  
        MyClass myObject = new MyClass();  
  
        // 订阅事件  
        myObject.MyEvent += MyEventHandlerMethod;  
          
        // 或者使用Lambda表达式  
        myObject.MyEvent += (sender, e) => Console.WriteLine($"Lambda: {e.Message}");  
  
        // 触发事件  
        myObject.DoSomething();  
  
        // 取消订阅事件  
        myObject.MyEvent -= MyEventHandlerMethod;  
  
        // 再次触发事件,这次不会调用已取消订阅的方法  
        myObject.DoSomething();  
    }  
  
    static void MyEventHandlerMethod(object sender, MyEventArgs e)  
    {  
        Console.WriteLine($"Method: {e.Message}");  
    }  
}

注意事项

  • 在触发事件时,最好先检查事件委托是否为null,以避免在没有任何订阅者的情况下抛出NullReferenceException
  • 委托的+=-=操作符用于订阅和取消订阅事件。最好在嗲用+=前先调用-=避免重复注册事件。
  • 事件是特殊的多播委托,它们只能由声明它们的类在其内部触发。外部代码不能“触发”事件,但可以订阅或取消订阅它们。
  • 事件参数类(如上面的MyEventArgs)通常继承自System.EventArgs,并用于向事件处理程序传递额外信息。
  • 从C# 6.0开始,可以使用null条件运算符来简化事件触发逻辑,例如MyEvent?.Invoke(this, e);,但这并不是标准的做法,因为Invoke方法并不是事件触发时的推荐方式(直接调用委托通常更简洁)。然而,在一些特殊情况下,Invoke方法可能更合适。

Entension methods

C# 中的扩展方法(Extension Methods)是一种特殊的静态方法,它们允许你为现有的类型添加新的方法,而无需创建新的派生类型、修改原始类型或使用任何特殊的工具(如 ILWeaving)。这极大地提高了代码的复用性和灵活性。

如何定义扩展方法

  1. 静态类:扩展方法必须定义在静态类中。
  2. 静态方法:扩展方法是静态的。
  3. 第一个参数:扩展方法的第一个参数必须包括 this 关键字,以指示该方法是对该类型的扩展。此参数类型是你想要扩展的类型。
  4. 访问级别:扩展方法可以有与其他静态成员相同的访问级别。

使用场景

扩展方法非常适合于为现有类型添加辅助方法,特别是当你没有权限修改这些类型或者这些类型来自第三方库时。通过为这些类型添加扩展方法,你可以提高代码的可读性和可维护性,同时避免创建大量继承自这些类型的子类。

示例 1-String entension

假设我们想要为 string 类型添加一个 ReverseWords 方法,该方法将字符串中的单词顺序反转,但保持每个单词内的字符顺序不变。

using System;  
using System.Collections.Generic;  
using System.Linq;  
  
public static class StringExtensions  
{  
    // 扩展string类型  
    public static string ReverseWords(this string str)  
    {  
        // 使用LINQ来反转单词顺序  
        return string.Join(" ", str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  
                                    .Reverse());  
    }  
}  
  
class Program  
{  
    static void Main()  
    {  
        string sentence = "Hello World from C#";  
        string reversedWords = sentence.ReverseWords(); // 使用扩展方法  
        Console.WriteLine(reversedWords); // 输出: "C# from World Hello"  
    }  
}

示例 2-.NET 6 API -servicecollections (DI)

using Demo.Data.DapperRepository;
using Demo.Domain.Dtos;
using Demo.Infrastructure;
using Demo.Infrastructure.Interfaces;
using Demo.Infrastructure.Tokens;
using Demo.Interfaces;
using Demo.Managers;

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;

namespace Demo
{
    public static class UseCustomServiceExtension
    {
        /// <summary>
        /// Register all services/managers
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void UseCustomServices(this IServiceCollection services, IConfiguration configuration)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (configuration == null) throw new ArgumentNullException(nameof(configuration));

            //Below services used to get token
            services.AddHttpContextAccessor();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddSingleton<IPrincipalAccessor, PrincipalAccessor>();

            services.Configure<MongoDbSettings>(configuration.GetSection("MongoDb"));

            // Register MongoDbContext as a singleton
            services.AddSingleton<MongoDbContext>();


            services.AddScoped<IAppSettingHelper, AppSettingHelper>();
            services.AddScoped<IJWTTokenHelper, JWTTokenHelper>();

            services.AddMemoryCache();

            services.AddTransient<ISqlConnectionResolver, SqlConnectionResolver>();

            //Register Dapper Repository
            services.AddTransient<IReadWriteDapperRepository, ReadWriteDapperRepository>();
            services.AddTransient<IReadOnlyDapperRepository, ReadOnlyDapperRepository>();


            services.AddTransient<IAccountManager, AccountManager>();
           
            services.AddTransient<IDealDetailsManager, DealDetailsManager>();
            
            services.AddTransient<IEmailTemplateManager, EmailTemplateManager>();
                      
        }
    }
}

Call demo in Program.cs in .NET6 API

builder.Services.UseCustomServices(builder.Configuration);

注意事项

  • 扩展方法是静态解析的,即它们是在编译时绑定到类型的,而不是在运行时。这意味着你不能通过重写扩展方法来“覆盖”它们,也不能在扩展方法中使用虚调用(virtual/override)。
  • 扩展方法不能访问它们扩展的类型的私有成员。
  • 如果在类型自身中定义了同名方法,则类型自身的方法会隐藏扩展方法。
  • 可以在任何命名空间中使用扩展方法,但为了使扩展方法可见,需要导入定义它们的命名空间。

 

Linq

C# 中的 LINQ(Language Integrated Query)是微软在.NET Framework 3.5中引入的一项技术,它允许开发者以声明性方式编写查询,这些查询可以针对多种数据源执行,包括但不限于数组、集合、XML 文档、数据库等。LINQ 提供了统一的查询语法,让开发者能够以一种更加自然和直观的方式来操作数据集合。

LINQ 的类型

LINQ 主要分为以下几种类型:

  1. LINQ to Objects:用于查询和操作内存中的集合,如数组、List<T> 等。
  2. LINQ to SQL(现已被 Entity Framework 替代):用于将 LINQ 查询直接转换为 SQL 查询,从而实现对数据库的操作。
  3. LINQ to XML:用于在内存中创建、修改和查询 XML 数据。
  4. LINQ to DataSet:用于在 DataSet 和 DataTable 上执行查询。
  5. LINQ to Entities(Entity Framework 的一部分):用于在 Entity Framework 实体上执行查询。

LINQ 查询语法

LINQ 查询使用类似 SQL 的查询语法,但它是以方法调用的形式嵌入到 C# 语言中的。查询操作包括 fromwhereselectgroup byorder by 等子句,这些子句可以链式调用以构建复杂的查询。

示例:LINQ to Objects

假设我们有一个整数列表,并希望找到其中所有的偶数:

using System;  
using System.Collections.Generic;  
using System.Linq;  
  
class Program  
{  
    static void Main()  
    {  
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  
  
        // 使用 LINQ 查询语法  
        var evenNumbers = from n in numbers  
                          where n % 2 == 0  
                          select n;  
  
        // 或者使用方法语法  
        var evenNumbersMethodSyntax = numbers.Where(n => n % 2 == 0);  
  
        foreach (var num in evenNumbers)  
        {  
            Console.WriteLine(num);  
        }  
  
        // 方法语法同样有效  
        foreach (var num in evenNumbersMethodSyntax)  
        {  
            Console.WriteLine(num);  
        }  
    }  
}

LINQ 特性

  • 延迟执行:大多数 LINQ 查询在调用 ToList()ToArray()First()Single() 等方法之前都不会执行。这称为“延迟执行”,它允许你构建复杂的查询,只有在真正需要结果时才执行它们。
  • 链式操作:LINQ 查询是可组合的,你可以将多个查询操作链接在一起,以构建复杂的查询逻辑。
  • 强类型:LINQ 查询在编译时会进行类型检查,这有助于在编写代码时捕获错误。
  • 表达能力:LINQ 提供了丰富的查询操作,几乎可以表达任何类型的数据转换和查询需求。

通过使用 LINQ,C# 开发者能够以更加简洁和表达力强的方式处理数据集合,从而提高开发效率和代码质量。

LINQ 常见用法

1. 筛选(Filtering)

使用 Where 方法根据特定条件筛选集合中的元素。

var filteredList = list.Where(item => item.Condition == true).ToList();

2. 投影(Projection)

使用 Select 方法从集合中选择某些属性或进行转换。

var projection = list.Select(item => new { Name = item.Name, Age = item.Age }).ToList();

3. 排序(Sorting)

使用 OrderBy 或 OrderByDescending 方法对集合进行排序。

var sortedList = list.OrderBy(item => item.Name).ToList();  
var sortedListDesc = list.OrderByDescending(item => item.Age).ToList();

4. 分组(Grouping)

使用 GroupBy 方法根据一个或多个键将集合中的元素分组。

var grouped = list.GroupBy(item => item.Category).ToList();

5. 聚合(Aggregation)

使用 CountSumAverageMinMax 等方法进行聚合计算。

var count = list.Count();  
var total = list.Sum(item => item.Value);  
var average = list.Average(item => item.Age);  
var min = list.Min(item => item.Value);  
var max = list.Max(item => item.Age);

6. 查找(Finding)

使用 FirstFirstOrDefaultSingleSingleOrDefaultAnyAll 等方法查找或检查集合中的元素。

var firstItem = list.First();  
var firstItemMatching = list.FirstOrDefault(item => item.Name == "John");  
var anyMatching = list.Any(item => item.Value > 100);  
var allMatching = list.All(item => item.Active);

7. 连接(Joining)

使用 Join 或 GroupJoin 方法来合并两个集合中的数据,类似于 SQL 中的 JOIN 操作。

var query = from customer in customers  
            join order in orders on customer.CustomerID equals order.CustomerID  
            select new { CustomerName = customer.Name, OrderID = order.OrderID };  
  
// 或者使用 GroupJoin  
var groupedJoin = customers.GroupJoin(orders, c => c.CustomerID, o => o.CustomerID, (c, os) => new { Customer = c, Orders = os.ToList() });

8. 分页(Paging)

使用 Skip 和 Take 方法实现分页功能。

var page = list.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();

9. 转换为其他类型

使用 ToList()ToArray()ToDictionary()ToLookup() 等方法将查询结果转换为不同的集合类型。

var dictionary = list.ToDictionary(item => item.Id, item => item.Name);  
var lookup = list.ToLookup(item => item.Category);

10. 自定义扩展方法

通过定义自己的扩展方法,为任何类型添加额外的 LINQ 功能。

public static class MyExtensions  
{  
    public static IEnumerable<T> MyCustomMethod<T>(this IEnumerable<T> source, Func<T, bool> condition)  
    {  
        // 自定义逻辑  
        return source.Where(condition); // 例如,这里只是包装了 Where 方法  
    }  
}

11. 使用LINQ查询并返回前100条记录 (EF Core)

 以下是一个使用Entity Framework Core(EF Core)作为示例,从数据库中返回前100条记录的例子。假设我们有一个名为DbContext的类(EF Core的上下文类),它表示我们的数据库连接,并且我们有一个名为Blogs的表(或对应的实体类)。

using (var context = new MyDbContext())  
{  
    var top100Blogs = context.Blogs  
                            .OrderBy(b => b.BlogId) // 假设我们按BlogId排序  
                            .Take(100) // 取前100条记录  
                            .ToList(); // 执行查询并返回结果列表  
  
    // 现在你可以遍历top100Blogs来查看结果  
    foreach (var blog in top100Blogs)  
    {  
        Console.WriteLine($"Blog ID: {blog.BlogId}, URL: {blog.Url}");  
    }  
}

Nullable type

在C#中,Nullable类型允许变量存储其基础值类型的值,或者存储null来表示没有值。这是C# 2.0引入的一个特性,它允许开发者为非引用类型(如intfloatstruct等)分配null值。在C# 8.0及更高版本中,可空引用类型(nullable reference types)的引入进一步增强了这一特性,允许开发者指定引用类型(如stringobject、类实例等)是否可以为null

可空值类型(Nullable Value Types)

对于值类型(如intdouble等),在它们前面加上?来创建它们的Nullable类型。例如,int?表示一个可以为null的整数。

int? nullableInt = 100; // 分配一个值  
nullableInt = null; // 分配null  
  
if (nullableInt.HasValue)  
{  
    Console.WriteLine(nullableInt.Value); // 输出100
}  
else  
{  
    Console.WriteLine("nullableInt is null");  
}

在上面的例子中,nullableInt是一个可以为null的整数。使用HasValue属性可以检查nullableInt是否包含值,如果包含值,则可以通过Value属性访问该值。

可空引用类型(Nullable Reference Types, C# 8.0及更高版本)

在C# 8.0及更高版本中,引入了可空引用类型的功能。这允许开发者在编译时更精确地控制引用类型的null状态。默认情况下,在C# 8.0及更高版本的启用项目中,所有引用类型都被视为非可空的,但你可以通过在类型后面添加?来显式声明它们可以为null

string? nullableString = "Hello World"; // 声明一个可以为null的字符串  
nullableString = null; // 分配null  
  
if (nullableString is not null)  
{  
    Console.WriteLine(nullableString); // 输出Hello World
}  
else  
{  
    Console.WriteLine("nullableString is null");  
}

在这个例子中,nullableString是一个可以为null的字符串。C# 8.0及更高版本还引入了is not null模式,用于更简洁地检查引用类型是否不为null

启用和禁用可空引用类型

在C#项目中,你可以通过修改项目文件(.csproj)来启用或禁用可空引用类型。例如,要启用可空引用类型,你可以在.csproj文件中添加以下元素:

<PropertyGroup>  
  <Nullable>enable</Nullable>  
</PropertyGroup>

<!--要禁用可空引用类型(这将是C# 7.3及更低版本的默认行为),可以使用:-->
<PropertyGroup>  
  <Nullable>disable</Nullable>  
</PropertyGroup>

然而,请注意,从C# 8.0开始,enable是默认设置,因此通常不需要显式启用它,除非你正在从早期版本的C#迁移项目并希望保持旧的行为。

Dynamic

在C#中,dynamic 关键字是一个静态类型,但在运行时它是动态的。使用 dynamic 类型的变量可以绕过编译时类型检查,其类型检查被延迟到运行时。这意味着你可以将 dynamic 类型的变量赋值为几乎任何类型的值,并且在编译时不会报错,因为编译器不会检查这些变量的类型兼容性。然而,如果在运行时尝试执行与变量实际类型不兼容的操作,则会抛出 RuntimeBinderException 异常。

dynamic 类型主要用于与动态语言(如Python、JavaScript等)的互操作性,以及在某些情况下简化代码,特别是当你不确定对象的确切类型,但你知道你将如何与之交互时。然而,过度使用 dynamic 可能会导致代码难以理解和维护,因为它减少了编译时类型检查的好处。

使用 dynamic 的例子1

假设你有一个返回 dynamic 类型对象的函数,这个函数可能返回不同类型的对象,具体取决于某些运行时条件:

public dynamic GetDynamicObject()  
{  
    // 假设这里有一些逻辑来决定返回什么类型的对象  
    // 这里为了简单起见,我们直接返回一个字符串  
    return "Hello, Dynamic world!";  
}  
  
// 使用该函数  
public void UseDynamicObject()  
{  
    dynamic obj = GetDynamicObject();  
    // 这里编译器不会检查 obj 是否具有 Length 属性  
    // 如果在运行时 obj 实际上是一个字符串,那么这将正常工作  
    // 如果不是,将会抛出 RuntimeBinderException  
    Console.WriteLine(obj.Length); // 输出字符串的长度  
  
    // 你也可以调用方法,即使编译器不知道这个方法是否存在  
    // obj.SomeMethod(); // 如果 SomeMethod 在运行时对象的类型中存在,这将工作  
}

使用 dynamic 的例子2

 ((dynamic)selectedItem).IsEnabled = false;


//verify whether existed the field
if (selectedItem.DynamicProperties.ContainsKey("isenabled "))
{
    Console.WriteLine("existed");
}

注意事项

  1. 性能:使用 dynamic 可能会稍微影响性能,因为类型检查是在运行时进行的,而不是在编译时。
  2. 可读性和可维护性:过度使用 dynamic 会使代码更难理解和维护,因为它减少了编译时类型检查的好处。
  3. 错误处理:由于类型检查是在运行时进行的,因此错误(如类型不匹配)可能会在运行时而不是编译时发生,这可能会使调试更加困难。

因此,尽管 dynamic 在某些情况下很有用,但应该谨慎使用,并尽量在可以使用静态类型的地方使用静态类型。

Exception Handling

在C#中,异常处理是一种非常重要的编程实践,它允许你优雅地处理运行时错误,而不是让程序突然崩溃。C#通过trycatchfinally块来支持异常处理。

基本结构

异常处理的基本结构如下:

try  
{  
    // 尝试执行的代码块,可能会抛出异常  
}  
catch (ExceptionType1 ex)  
{  
    // 当在try块中抛出ExceptionType1类型的异常时,执行这里的代码  
}  
catch (ExceptionType2 ex)  
{  
    // 当在try块中抛出ExceptionType2类型的异常时,执行这里的代码  
    // 可以有多个catch块来处理不同类型的异常  
}  
finally  
{  
    // 无论是否发生异常,finally块中的代码都会被执行  
    // 通常用于清理资源,如关闭文件、数据库连接等  
}

抛出异常

你可以使用throw关键字来抛出异常。如果抛出的异常是自定义的,你需要先定义一个继承自Exception的类。

throw new Exception("发生了一个错误");  
  
// 或者抛出自定义异常  
throw new MyCustomException("发生了一个自定义错误");

捕获特定类型的异常

catch块中,你可以指定要捕获的异常类型。如果抛出的异常与catch块中指定的类型不匹配,则执行下一个catch块(如果有的话)。如果没有任何catch块匹配,异常将被传递到调用堆栈中的上一个方法。

使用finally

finally块是可选的,但它非常有用,因为它无论是否发生异常都会执行。这使其成为执行清理代码(如关闭文件流或数据库连接)的理想位置。

异常过滤

从C# 6.0开始,你可以在catch块中添加一个条件表达式(也称为异常过滤器),以决定是否捕获特定的异常。

try  
{  
    // 尝试执行的代码  
}  
catch (Exception ex) when (ex.Message.Contains("特定条件"))  
{  
    // 仅当异常消息包含"特定条件"时,才执行这里的代码  
}

注意事项

  • 尽量避免在catch块中吞没异常(即捕获异常后不进行任何处理或记录)。这会使调试和错误跟踪变得更加困难。
  • 尽可能具体地捕获异常类型,而不是总是捕获Exception类型。这有助于你更准确地了解发生了什么问题,并相应地处理它。
  • 在可能的情况下,考虑将异常信息记录到日志中,以便稍后进行问题诊断。
  • 谨慎使用finally块中的代码,确保它不会抛出新的异常,因为这可能会掩盖原始异常。如果finally块中的代码确实可能抛出异常,请考虑在try块中捕获并处理它,或者确保finally块中的代码足够健壮,能够处理潜在的异常。

async/await

C# 中的 async 和 await 关键字是 C# 5.0 引入的,它们用于简化异步编程模型。这些关键字使得编写异步代码变得更为直观和易于理解,同时避免了传统的回调和事件处理模型的复杂性。

基本概念

  • async:这是一个修饰符,用于标记一个方法、lambda 表达式或匿名方法是异步的。在方法声明中使用 async 关键字会允许你在该方法的体内使用 await 关键字。async 方法会隐式地返回一个 Task 或 Task<T> 对象,其中 T 是方法的返回类型。如果方法没有返回值,则返回 Task;如果有返回值,则返回 Task<T>

  • await:这个关键字用于等待异步操作的完成。它只能用在被 async 修饰的方法内部。await 表达式会挂起 async 方法的执行,直到等待的异步操作完成,然后继续执行 async 方法中 await 表达式后面的代码。在等待过程中,async 方法会立即返回其 Task 或 Task<T> 对象,而不会阻塞调用线程。

使用示例

假设我们有一个异步的 HTTP 请求方法,我们想要调用它并等待其完成:

public async Task<string> GetWebPageAsync(string url)  
{  
    using (HttpClient client = new HttpClient())  
    {  
        HttpResponseMessage response = await client.GetAsync(url); // 等待异步操作完成  
        if (response.IsSuccessStatusCode)  
        {  
            string content = await response.Content.ReadAsStringAsync(); // 再次等待异步操作完成  
            return content;  
        }  
        throw new HttpRequestException("Request failed with status code: " + response.StatusCode);  
    }  
}  
  
public async Task UseWebPageAsync()  
{  
    string webPage = await GetWebPageAsync("https://example.com"); // 等待 GetWebPageAsync 完成  
    Console.WriteLine(webPage);  
}

在这个例子中,GetWebPageAsync 是一个异步方法,它使用 HttpClient 来发送 HTTP GET 请求并获取网页内容。await client.GetAsync(url); 等待 HTTP 请求的完成,而不会阻塞调用线程。同样,await response.Content.ReadAsStringAsync(); 等待响应内容的读取完成。

UseWebPageAsync 方法也是异步的,它调用 GetWebPageAsync 并等待其完成,然后打印返回的网页内容。

  • 异步方法通常包含至少一个 await 表达式。如果一个方法被标记为 async 但不包含 await 表达式,编译器将发出警告,因为该方法的异步性可能不是必需的。
  • await 只能在 async 方法内部使用。
  • 使用 await 时,当前方法的执行会在 await 表达式处暂停,直到等待的异步操作完成,然后继续执行 await 表达式后面的代码。但是,调用该方法的线程(通常是 UI 线程或 ASP.NET 上下文中的请求线程)不会被阻塞。
  • 异步方法返回 Task 或 Task<T>,这允许调用者使用 await 等待异步操作的完成,或者使用 .Result.GetAwaiter().GetResult()(不推荐,因为它们可能导致死锁)或其他方法来同步地等待结果。但是,通常建议尽可能使用 await 来保持代码的异步性。

继续学习

​​​​​​What is new in C# 7,8,9,10-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值