随着net10新版本的发布,有很多新特性也随之而来 这里只讲几个常用的新特性
1. 字段式属性(Field-Backed Properties)
// 手动维护私有字段 + 属性,4行起,字段命名易混乱(_name/_Name/name)
private string _name;
public string Name
{
get => _name;
set => _name = value?.Trim() ?? string.Empty; // 加空值兜底仍需多行
}
// 若需多个带逻辑的属性,重复代码会指数级增加
private int _age;
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value; // 年龄范围校验
}
-
告别手动写 backing field,直接用
field关键字访问自动生成的字段
// 一行实现带 Trim 的属性,编译器自动生成私有字段
public string Name { get => field; set => field = value?.Trim(); }
单纯看 “生成私有字段” 这个点,prop 快捷指令(自动属性)public string Name { get; set; } 看起来更简洁,但 字段式属性(field-backed properties) 的核心价值不是 “生成字段”,而是在不手动写私有字段的前提下,给属性的 get/set 加自定义逻辑
1. 传统手动写法(.NET 10 前)
要实现 “赋值时 Trim”,必须手动声明私有字段,代码冗余:
// 手动写私有字段 + 属性,4行代码
private string _name;
public string Name
{
get => _name;
set => _name = value?.Trim();
}
2. 自动属性(prop 快捷指令)
只能生成 “无逻辑” 的属性,无法加 Trim 逻辑:
// 1行,但无法加 Trim 逻辑,达不到需求
public string Name { get; set; }
3. .NET 10/C# 14 字段式属性
1 行代码 既实现了自定义逻辑,又不用手动写私有字段:
// 1行 = 自动生成字段 + 自定义 Trim 逻辑,完美兼顾简洁与需求
public string Name { get => field; set => field = value?.Trim(); }
核心优势:兼顾 “简洁” 与 “自定义逻辑”
| 写法 | 代码行数 | 是否手动写私有字段 | 是否支持自定义逻辑(如 Trim) |
|---|---|---|---|
| 传统手动写法 | 4 | ✅ 必须写 | ✅ 支持 |
| 自动属性(prop) | 1 | ❌ 不用写 | ❌ 不支持 |
| 字段式属性(C#14) | 1 | ❌ 不用写 | ✅ 支持 |
2. 空条件赋值(?.=,C# 14)
传统写法(.NET 10 前)
对可空对象赋值时,需显式判空,多层嵌套时代码嵌套深、可读性差:
// 单层判空:2行代码,需显式 if 判断
var user = GetUser();
if (user != null)
{
user.Name = "张三";
}
// 多层嵌套:嵌套层级随属性深度增加,易出错
if (order != null && order.User != null && order.User.Address != null)
{
order.User.Address.Detail = "北京市海淀区";
}
// 三元表达式简化(仍需重复写判空逻辑)
user?.Name = user != null ? "李四" : user?.Name;
新特性适用场景
-
对可空引用类型 / 可空值类型赋值,需 “仅当左侧对象非空时执行赋值”
-
多层嵌套属性赋值(如
obj?.prop?.subProp = value) -
避免空引用异常(NullReferenceException),简化判空逻辑
.NET 10/C# 14 语法
使用 ?.= 运算符,一行完成 “判空 + 赋值”,多层嵌套无需额外判断:
// 单层场景:仅当 user 非空时赋值
user?.Name = "张三";
// 多层场景:仅当 order、User、Address 都非空时赋值
order?.User?.Address?.Detail = "北京市海淀区";
// 扩展场景:结合字段式属性使用
public class User
{
public string Name { get => field; set => field = value?.Trim(); }
}
var user = null;
user?.Name = " 李四 "; // 无空引用异常,赋值不执行
using System;
class Program
{
static void Main(string[] args)
{
// 场景1:创建完整的嵌套对象(所有层级都不为null)
var order = new Order
{
User = new User
{
Address = new Address { Detail = "默认地址" }
}
};
// 用?.= 赋值:所有层级都不为null,赋值成功
order?.User?.Address?.Detail = "北京市海淀区";
Console.WriteLine("赋值后地址:" + order.User.Address.Detail); // 输出:北京市海淀区
// 场景2:创建不完整的对象(User为null)
var emptyOrder = new Order(); // User属性默认是null
// 用?.= 赋值:User为null,跳过赋值,不报错
emptyOrder?.User?.Address?.Detail = "上海市浦东新区";
Console.WriteLine("emptyOrder的User是否为null:" + (emptyOrder.User == null)); // 输出:True
// 场景3:错误示范(试图判断变量,编译器直接报错)
string myVar = null;
// myVar?. = "测试"; // ❌ 报错:语法错误,?. 不能直接跟变量赋值
// 正确的变量判空赋值:用普通if
if (myVar != null)
{
myVar = "测试";
}
}
}
// 第一层:订单类
public class Order
{
// 包含User属性(引用类型,默认null)
public User User { get; set; }
}
// 第二层:用户类
public class User
{
// 包含Address属性(引用类型,默认null)
public Address Address { get; set; }
}
// 第三层:地址类
public class Address
{
// 具体的地址详情字段(值类型/字符串)
public string Detail { get; set; }
}
核心优势
| 写法 | 代码行数 | 显式判空 | 多层嵌套可读性 | 空异常风险 |
|---|---|---|---|---|
| 传统手动写法 | 2+ | ✅ 必须 | ❌ 嵌套深 | ✅ 较高 |
| 空条件赋值(C#14) | 1 | ❌ 无需 | ✅ 扁平化 | ❌ 极低 |
3. 扩展属性(Extension Properties,C# 14)
扩展属性不是 “新增功能”,而是 “语法糖”——功能和以前的扩展方法完全一样,只是调用时不用加括号,更像 “真属性”。
| 场景 | .NET 10 前(扩展方法) | .NET 10 后(扩展属性) | 核心区别 |
|---|---|---|---|
| 判断字符串是不是邮箱 | "test@xx.com".IsEmail() |
"test@xx.com".IsEmail |
少写 () |
| 统计单词数量 | "hello world".WordCount() |
"hello world".WordCount |
少写 () |
| 显示用户昵称 | user.DisplayName() |
user.DisplayName |
少写 () |
传统写法(.NET 10 前)
要为现有类型(如 string、int、第三方库类型)添加 “属性式” 能力,只能通过扩展方法模拟,语义不直观:
// 扩展方法模拟“属性”,调用时需加括号,不符合属性使用习惯
public static class StringExtensions
{
public static bool IsEmail(this string s)
{
return !string.IsNullOrEmpty(s) && s.Contains("@") && s.Contains(".");
}
public static int WordCount(this string s)
{
return s?.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries).Length ?? 0;
}
}
// 调用:像方法而非属性,语义割裂
var isEmail = "test@example.com".IsEmail(); // 需加()
var count = "hello world".WordCount();
新特性适用场景
-
为现有类型(基础类型 / 第三方库类型 / 接口)添加属性,而非方法,语义更贴合 “属性” 场景
-
无需继承 / 包装原有类型,保持类型体系简洁
-
适用于通用工具类、业务规则校验、数据格式化等场景
.NET 10/C# 14 语法
通过 this 关键字定义扩展属性,调用方式与原生属性完全一致:
// 扩展属性定义
public static class StringExtensions
{
// 布尔型扩展属性:判断是否为邮箱
public static bool IsEmail(this string s)
{
return !string.IsNullOrEmpty(s) && s.Contains("@") && s.Contains(".");
}
// 数值型扩展属性:统计单词数量
public static int WordCount(this string s)
{
return s?.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries).Length ?? 0;
}
// 扩展接口属性(对所有实现类生效)
public static string DisplayName(this IUser user) => user.Name + "(" + user.Id + ")";
}
// 调用:直接作为属性使用,无需括号,语义统一
var isEmail = "test@example.com".IsEmail; // 无(),像原生属性
var count = "hello world".WordCount;
var displayName = new User { Id = 1, Name = "张三" }.DisplayName;
| 写法 | 调用方式 | 语义贴合度 | 类型侵入性 | 代码可读性 |
|---|---|---|---|---|
| 传统扩展方法 | 加 () | ❌ 方法模拟属性 | ❌ 无侵入 | ❌ 一般 |
| 扩展属性(C#14) | 无 () | ✅ 原生属性体验 | ❌ 无侵入 | ✅ 优秀 |
4. LINQ Contains 专项优化(.NET 10 运行时)
传统写法(.NET 10 前)
对 Distinct/OrderBy/Reverse 后的集合调用 Contains,底层仍线性遍历,性能极低,手动优化需额外转集合:
// 基础场景:Distinct 后 Contains,10万数据耗时 ~17ms
var list = Enumerable.Range(1, 100000).Select(i => i.ToString()).ToList();
var isExist = list.Distinct().Contains("50000"); // 线性遍历,耗时 ~16967ns
// 手动优化:转 HashSet,多1行代码,且需额外内存
var hashSet = new HashSet<string>(list.Distinct());
var isExistOpt = hashSet.Contains("50000"); // 耗时 ~50ns,但代码冗余
新特性适用场景
-
对
Distinct/OrderBy/Reverse处理后的IEnumerable<T>调用Contains -
高频查询场景(如接口参数校验、数据过滤、权限判断)
-
无需手动优化代码,零成本提升性能
.NET 10 语法(无语法变化,底层优化)
语法完全不变,运行时自动识别查询意图,跳过不必要遍历,直接使用即可:
// 基础场景:Distinct 后 Contains,性能提升 99%+
var list = Enumerable.Range(1, 100000).Select(i => i.ToString()).ToList();
var isExist = list.Distinct().Contains("50000"); // 耗时 ~47ns
// 扩展场景1:OrderBy 后 Contains
var isExist2 = list.OrderBy(x => x).Contains("80000"); // 耗时 ~50ns(原 ~12884ns)
// 扩展场景2:Reverse 后 Contains
var isExist3 = list.Reverse().Contains("10000"); // 耗时 ~45ns(原 ~11990ns)
// 扩展场景3:组合操作后 Contains
var isExist4 = list.Distinct().OrderBy(x => x).Reverse().Contains("30000"); // 仍保持高性能
| 写法 | 额外代码 | 10 万数据耗时 | 内存占用 | 开发成本 |
|---|---|---|---|---|
| .NET 9 及以前 | ❌ 无 | ~17ms | 高 | ✅ 低 |
| .NET 10(优化后) | ❌ 无 | ~0.05ms | 低 | ✅ 低 |
| 手动转 HashSet | ✅ 需 1 行 | ~0.05ms | 中 | ❌ 高 |
5. EF Core 10 原生 LeftJoin
传统写法(.NET 10 前)
EF Core 无原生 LeftJoin 方法,需通过 GroupJoin + SelectMany + DefaultIfEmpty 模拟,逻辑反直觉、易出错:
// 模拟左连接:4行代码,逻辑绕,新手易写错
var query = dbContext.Users
.GroupJoin(
dbContext.Orders,
user => user.Id,
order => order.UserId,
(user, orders) => new { user, orders }
)
.SelectMany(
x => x.orders.DefaultIfEmpty(), // 关键:模拟左连接的空值兜底
(x, order) => new { x.user.Name, OrderNo = order?.OrderNo ?? "无订单" }
);
新特性适用场景
-
EF Core 查询中需要左外连接(Left Outer Join)场景
-
替代复杂的
GroupJoin + SelectMany写法,降低查询编写难度 -
适用于关联查询(如用户 - 订单、商品 - 分类),需保留左表全部数据的场景
.NET 10/EF Core 10 语法
新增 LeftJoin 扩展方法,语法与 SQL 左连接逻辑完全对齐,简洁直观:
// 基础场景:用户左连接订单,保留所有用户(含无订单用户)
var query = dbContext.Users
.LeftJoin(
dbContext.Orders,
user => user.Id, // 左表关联键
order => order.UserId, // 右表关联键
(user, order) => new {
UserName = user.Name,
OrderNo = order?.OrderNo ?? "无订单",
OrderTime = order?.CreateTime ?? DateTime.MinValue
}
);
// 扩展场景:多表左连接
var query2 = dbContext.Users
.LeftJoin(dbContext.Orders, u => u.Id, o => o.UserId, (u, o) => new { u, o })
.LeftJoin(dbContext.OrderItems, x => x.o.Id, oi => oi.OrderId, (x, oi) => new {
x.u.Name,
x.o?.OrderNo,
oi?.ProductName
});
