个人总结,内容相较于官方文档有较大程度删减,不适合学习使用
C#
C#11
泛型属性
泛型可用于属性
public class GenericAttribute<T> : Attribute { }
有点奇怪,反编译成7版本,好像没啥变化?
原始字符串文本
以至少三个"""开始、结束,相当于@$,接受换行、{}嵌入参数
反编译
结构自动初始化
以往版本,要求在值类型构造函数中必须初始化所有成员变量,现在不会要求显式初始化所有变量,没有显式初始化的会被设为默认值
required
将required修饰符添加到字段或属性,表示必须在构造函数中对其进行初始化
反编译结果
ref 字段和 ref scoped 变量
从效果来看,类似于指针 ref字段和ref scoped变量 | Easy-DotNET
ref struct
可以在结构类型的声明中使用 ref 修饰符。 ref struct 类型的实例是在堆栈上分配的,不能转义到托管堆。 为了确保这一点,编译器将 ref struct 类型的使用限制如下:
ref struct不能是数组的元素类型。ref struct不能是类或非ref struct的字段的声明类型。ref struct不能实现接口。ref struct不能被装箱为 System.ValueType 或 System.Object。ref struct不能是类型参数。ref struct变量不能由 Lambda 表达式或本地函数捕获。ref struct变量不能在async方法中使用。 但是,可以在同步方法中使用ref struct变量,例如,在返回 Task 或 Task<TResult> 的方法中。ref struct变量不能在迭代器中使用。
可以定义一次性的 ref struct。 为此,请确保 ref struct 符合一次性模式。 也就是说,它有一个实例 Dispose 方法,该方法是可访问、无参数的并且具有 void 返回类型。 可以将 using 语句或声明与可释放的 ref struct 的实例一起使用。
行内容(包括插值的开始位置)必须以与最后一行相同的空格开头
行内容(包括插值的开始位置)必须以与最后一行相同的空格开头
var x = $"""
Hello
{1 + 1} 这行是违法的,
World
""";
var x = $"""
Hello
{1 + 1} 正确
World
""";
params 在委托类型转换的问题
早期版本中
void Method(int i = 0, params int[] xs) { }
var action = Method;//var推断为 System.Action<int, int[]>
但在.NET SDK 7.0.200 或更高版本中,此类方法被推断为匿名合成委托类型 具有相同的默认参数值和修饰符。
void Method(int i = 0, params int[] xs) { }
var action = Method; // delegate void <anonymous delegate>(int arg1 = 0, params int[] arg2)
推断得到的类型不一致
局部变量必须在当前线程中赋值才能被使用
以前可以,现在不行(不见兔子不撒鹰)
public async Task M()
{
bool a;
await M1();
Console.WriteLine(a); //现在不行,报错 error CS0165: Use of unassigned local variable 'a'
async Task M1()
{
if ("" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
无法通过引用返回 out 参数
对于语言版本 C# 11 或更高版本,或者 .NET 7.0 或更高版本,无法通过引用返回参数。out
static ref T ReturnOutParamByRef<T>(out T t)
{
t = default;
return ref t; // error CS8166: Cannot return a parameter by reference 't' because it is not a ref parameter
}
上下文关键字 var 不能用作显式 lambda 返回类型。
C#12
主构造函数
public class NamedItem(string parama,string paramb,String paramc)
{
}
相当于
public class NamedItem
{
public NamedItem(string parama, string paramb, string paramc)
{
}
}
此外,任何显式编写的构造函数都必须使用 this(...) 初始化表达式语法来调用主构造函数。 这可确保主构造函数参数绝对由所有构造函数分配。
如果对记录类型使用主构造函数,会自动生成字段
集合表达式
// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];
// Create a list:
List<string> b = ["one", "two", "three"];
// Create a span
Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
// Create a jagged 2D array:
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// Create a jagged 2D array from variables:
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] twoDFromVariables = [row0, row1, row2];
int[] single = [.. row0, .. row1, .. row2];
foreach (var element in single)
{
Console.Write($"{element}, ");
}
// output:
// 1, 2, 3, 4, 5, 6, 7, 8, 9,
ref readonly
不太能理解,并且这不应该作为一个新功能而是应该作为建议出现
labmda 中的参数列表可以像方法一样设置默认参数
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
可以使用using为元组、数组等类型设置别名
太强了
using Point = (int x, int y);
内联数组
简单来说,预先设好一个长度固定的值数组类型,如果不是对性能有极高的要求好像没必要使用
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _element0;
}
var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
buffer[i] = i;
}
foreach (var i in buffer)
{
Console.WriteLine(i);
}
ref参数可以传递给参数in
早期版本不可以,但现在支持,注意因此变动,导致的调用多态方法可能发生的变动
var i = 5;
System.Console.Write(new C().M(ref i)); // prints "E" in C# 11, but "C" in C# 12
System.Console.Write(E.M(new C(), ref i)); // workaround: prints "E" always
class C
{
public string M(in int i) => "C";
}
static class E
{
public static string M(this C c, ref int i) => "E";
}
var i = 5;
System.Console.Write(C.M(null, ref i)); // prints "1" in C# 11, but fails with an ambiguity error in C# 12
System.Console.Write(C.M((I1)null, ref i)); // workaround: prints "1" always
interface I1 { }
interface I2 { }
static class C
{
public static string M(I1 o, ref int x) => "1";
public static string M(I2 o, in int x) => "2";
}
.NET
.NET7
ASP.NET Core 7.0
速率限制中间件
ASP.NET Core 中的速率限制中间件 | Microsoft Learn
顾名思义,限制某个路径上请求数量,超速会阻塞而不是返回错误信息
身份验证
现在如果只注册了单个身份验证方案,该方案会自动用作 DefaultScheme,而无需指定。
DI范围扩大
当类型被配置为服务时,不只是构造函数,API控制器中方法的参数也默认通过DI注入实例,相当于标注了FromServices
若要禁用,在配置中写入
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
验证错误中的 JSON 属性名
默认情况下,当发生验证错误时,模型验证会生成一个 ModelStateDictionary,其中属性名用作错误键。 某些应用(例如单页应用)受益于使用 JSON 属性名来处理 Web API 生成的验证错误。
builder.Services.AddControllers(options => {
options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});
不懂有什么作用,测试了几次,开了提示信息中的属性名都被转换为小写,不开都被转换为大写,没其他区别???
最小 API 筛选器应用
最小 API 筛选器应用 | Microsoft Learn
个人认为,功能和行为筛选器功能重复了,甚至和中间件一个粒度,和中间件相比唯一特殊的点在保证在终结点方法前后执行
支持将查询字符串绑定到基元类型、字符串数组和 StringValues 数组
// Bind query string values to a primitive type array.
// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");
// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
将请求正文绑定为 Stream 或 PipeReader
todo
IResult改为共用类型
在.NET 7中,实现IResult的接口或类型是公共的,这意味着它可以从其他程序集中被引用和使用。这使得其他开发者或测试框架可以轻松地利用IResult来扩展或测试其功能
这里不是太理解这个单元测试的含义
运行时检测ITesult类型
HttpResult 接口提供了六个接口,对应五种返回值类型,用于通过模式在运行时检测IResult类型
app.MapGet("/weatherforecast", (int days) =>
{
if (days <= 0)
{
return Results.BadRequest();
}
var forecast = Enumerable.Range(1, days).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Cool"))
.ToArray();
return Results.Ok(forecast);
}).
AddEndpointFilter(async (context, next) =>
{
var result = await next(context);
return result switch
{
IValueHttpResult<WeatherForecast[]> weatherForecastResult => new WeatherHttpResult(weatherForecastResult.Value),
_ => result
};
});
使用参数调用 WithOpenApi
首先下载Microsoft.AspNetCore.OpenApiNuGet包,WithOpenApi 方法接受可用于修改 OpenAPI 注释的函数。
app.MapPost("/todoitems/{id}", async (int id) =>
{
return "";
})
.WithOpenApi(generatedOperation =>
{
var parameter = generatedOperation.Parameters[0];
parameter.Description = "The ID associated with the created Todo";
return generatedOperation;
});
文件上传
最小 API 现在支持使用 IFormFile 和 IFormFileCollection 上传文件。 以下代码使用 IFormFile 和 IFormFileCollection 上传文件:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapPost("/upload", async (IFormFile file) =>
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});
app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});
app.Run();
AsParametersAttribute
AsParameters应用示例
将多个参数统合到一个对象中,简化参数列表
路由组
用于为一组route统一配置前缀、EndPointFilter等组件
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
Json序列化
1). 可自定义json协定,比较复杂
自定义序列化和反序列化协定 - .NET | Microsoft Learn
What's new in System.Text.Json in .NET 7 - .NET Blog (microsoft.com)
2). 用户定义类型层次结构的多态序列化
[JsonDerivedType(typeof(calss))]标记的类,在序列化时会尝试序列化为目标class,同时指定多个的情况下,从子类向父类遍历,逐个尝试序列化
[JsonDerivedType(typeof(WeatherForecastBase), typeDiscriminator: "base")]序列化后加一个值为base的$type字段,在反序列化时如果包含这个字段,会尝试反序列化为目标class
若要处理未知派生类型,必须使用基类型上的注释[JsonPolymorphic( UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]来加入此类支持,否则将会导致运行异常。
该枚举类提供三个枚举值:
- 失败 FailSerialization
- 回退到基本类型 FallBackToBaseType
- 回退到最近的被支持的父类 FallBackToNearestAncestor
3). [JsonRequired]属性标注的属性序列化时不能为空
中断性更新
以 ASPNET 为前缀的环境变量优先级
ASPNET_ 为前缀的环境变量和以 DOTNET_ 为前缀的环境变量优先级做了调整。读取 WebApplicationBuilder 的默认主机配置时,命令行参数和以 DOTNET_ 为前缀的环境变量将替代以 ASPNET_ 为前缀的环境变量。
.NET8
ASP.NET Core8
自定义区域性格式
app.UseRequestLocalization(options =>
{
options.CultureInfoUseUserOverride = false;
});
当 CultureInfoUseUserOverride 设置为 true 时,ASP.NET Core 应用程序会考虑用户的区域性设置来格式化日期、时间和数字。这允许应用程序根据用户的区域性偏好(例如美国或法国)来显示日期、时间或数字。
反之,如果设置为 false(默认值),则应用程序将使用应用程序的默认区域性设置来格式化这些值。
示例
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddRequestLocalization(options => { options.DefaultRequestCulture = new RequestCulture("en-US"); // 默认文化设置为美国英语
options.CultureInfoUseUserOverride = true; // 启用用户覆盖
});
// 其他配置...
}
public class MyController : ControllerBase
{
private readonly IStringLocalizer<MyController> _localizer;
public MyController(IStringLocalizer<MyController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
var formattedDate = DateTime.Now.ToString("d"); // 格式化日期为短日期格式(例如:"09/13/2023")
var dateString = _localizer["The current date is {0}.", formattedDate]; // 这里将根据用户的区域性设置显示日期格式(例如:美国英语格式为 "The current date is September 13, 2023.")
return View(dateString);
}
}
表单参数绑定
1).现在支持使用 [FromForm] 属性显式绑定到表单值。 通过 [FromForm] 绑定到请求的参数包括防伪令牌。 处理请求时会验证防伪令牌。
没看懂,和前面区别不大?????
2).支持使用 IFormCollection、IFormFile 和 IFormFileCollection 类型推断绑定到表单。 OpenAPI 元数据针对表单参数进行推断,以支持与 Swagger UI 集成。
最小 API 应用程序中的参数绑定 | Microsoft Learn
反伪令牌的中间件
此版本添加了用于验证反伪令牌的中间件,用于缓解跨站点请求伪造攻击。 调用 AddAntiforgery 以在 DI 中注册防伪服务。 在 DI 容器中注册防伪服务时,WebApplicationBuilder 会自动添加防伪中间件。 防伪令牌用于减少跨站点请求伪造攻击。
ASP.NET Core 8.0 的新增功能 | Microsoft Learn
builder.Services.AddAntiforgery();
优化ObjectPool
在ObjectPool中新建了IResttable接口,提供可重用类型返回对象池时,自动重置状态功能 通过 ASP.NET Core 中的 ObjectPool 重用对象 | Microsoft Learn
支持自定义IOC的建
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));
IExceptionHander
IExceptionHandler is a new interface that gives the developer a callback for handling known exceptions in a central location.
和异常过滤器功能上有些重合
但粒度上
exceptionHander是一个中间件,粒度是整个请求
异常过滤器是最后一个中间件的组件,粒度是终结点方法
IExceptionHandler是在请求处理管道中处理异常的中间件组件,而异常Filter是在Action执行过程中处理异常的组件。两者都可以用于处理异常,但它们的执行时机和方式有所不同。
好像区别也不太大?为什么不能都用IEctptionHander,异常过滤器既不是方法级aop,又不是功能强大的全局异常处理器,多少有点不伦不类
短路
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
只要与这个map匹配,配置在其前后的中缉拿缉拿都不会被执行,直接执行该map
序列化
处理缺失的成员
提供了[JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]属性,skip表示跳过,disallow表示引发异常
接口层次序列化
没啥说的,当一个新功能看,本该如此的感觉
.NET 8 的新增功能 | Microsoft Learn
自定义格式策略
.NET 8 的新增功能 | Microsoft Learn
序列化只读属性或字段
.NET 8 的新增功能 | Microsoft Learn
也可以使用 JsonIncludeAttribute 和 JsonConstructorAttribute 特性注释将非公共成员加入到给定类型的序列化协定中。