C# 笔记 ongoing
更新来自 nuget 的第三方库
可以修改 xxx.csproj 这个工程配置文件, 然后 dotnet restore.
然而为了降低出错的可能性, 更推荐使用以下命令, 效果是:
先尝试下载新版本至本地, 下载成功后则自动修改 .csproj 中的版本.
sh
dotnet add package [package-name]sh
dotnet add package [package-name] -v [version] # 短选项
dotnet add package [package-name] --version [version] # 长选项将异常封装成方法
cs
using System.Diagnostics.CodeAnalysis;
[DoesNotReturn] void Throw() => throw new Exception("这是一个故意被抛出的异常");
// 显然, 将抛出异常的语句封装的方法可以是任意类型的返回值, 例如 float
[DoesNotReturn] float ThrowOutOfRange() => throw new IndexOutOfRangeException();
void Run()
{
// ...
// 将抛出异常的语句分装成一个方法来调用,
// 可以使得此方法更容易被内联编译, 性能更好.
Throw();
}返回 ref readonly 类型
cs
static void Run()
{
// 方法的返回值可以被 ref 或 ref readonly 修饰,
// 返回引用或只读的引用.
static ref readonly int Foo(int[] array)
{
return ref array[3];
}
int[] array = new[] { 1, 2, 3, 4, 5 };
ref readonly int x = ref Foo(array);
Console.WriteLine($"x is {x}");
}返回数组中某个元素的引用
cs
static void Run()
{
int[] array = new[] { 1, 2, 3, 4, 5 };
// 通过 ref 关键字返回数组类型变量中某个元素的引用
ref readonly int readerVar = ref array[1];
Console.WriteLine($"readVar is {readerVar}");
ref int writerVar = ref array[3];
writerVar = 666;
Console.WriteLine($"array[3] is {array[3]}");
}指针的简单使用案例
cs
// 使用指针访问数组元素的简单示范
static unsafe void Run()
{
int[] array = new[] { 1, 2, 3, 4, 5 };
fixed (int* p = array)
{
Console.WriteLine(*(p + 3));
}
}函数指针的用法
cs
unsafe
{
// 函数指针的用法示例
delegate* managed<int, int, float> addFunc = &Add;
Console.WriteLine(addFunc(1, 2));
static float Add(int x, int y) => x + y + 0.1f;
}Span 的用法示例
cs
using System.Runtime.InteropServices;
unsafe void Run()
{
// 申请一段非托管堆内存, 并使用指针记录起始地址
void* ptr = NativeMemory.Alloc(1024);
// 使用 Span 来表示这一段内存
Span<int> y = new(ptr, 1024 / sizeof(int));
y[4] = 42;
Console.WriteLine($"y.Length is {y.Length}");
Console.WriteLine($"y[4] is {y[4]}");
NativeMemory.Free(ptr);
}指定结构体的内存排列
cs
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
// 演示 StructLayout 的用法
[StructLayout(LayoutKind.Explicit)]
struct Foo
{
[FieldOffset(0)] public int Int;
[FieldOffset(0)] public float Float;
// 声明一个固定长度的数组
[FieldOffset(0)] public unsafe fixed byte Bytes[4];
}
static unsafe void Run()
{
int data = 1065353216;
Foo foo = Unsafe.AsRef<Foo>(&data);
Console.WriteLine($"foo.Float: {foo.Float}");
Console.WriteLine($"foo.Int: {foo.Int}");
for(int i=0; i < 4; i++)
Console.WriteLine($"foo.Bytes[{i}]: {foo.Bytes[i]}");
// Foo foo = new() { Float = 1.0F };
// Console.WriteLine(foo.Int);
}跳过局部变量的初始化
cs
using System.Runtime.CompilerServices;
static unsafe void Run()
{
Guid guid;
Console.WriteLine(*(Guid*)&guid);
}
// 跳过对局部变量默认的初始化过程, 性能更优
[SkipLocalsInit]
static unsafe void RunSkip()
{
Guid guid;
Console.WriteLine(*(Guid*)&guid);
}用特性修饰自动属性的字段
例如在 Unity 开发中, 我们希望某个字段被 SerializeField 修饰 (为了能在 Inspector 中编辑),
同时也希望对它的可访问性做限制, 使得外部类型访问时只读. 比较常规的写法是:
cs
[SerializeField] private Button _niceButton;
public Button niceButton
{
get { return _niceButton; }
private set { _niceButton = value; }
}以上代码可以简化成:
cs
[field: SerializeField]
public Button niceButton { get; private set; }dotnet restore 时间过长
dotnet restore 命令用于: 下载 .NET 工程所依赖的 nuget package.
在使用 dotnet build 命令时, 也默认会执行 dotnet restore.
在大多数情况下, restore 应该会在几秒到十几秒内完成.
然而在通过 docker 构建镜像时, 可能会遇到无限等待的问题.
此时多半是因为正在尝试 构建多平台容器镜像, 更准确地说是 "交叉编译".
从 .NET 8 开始, 工具链是支持构建多平台镜像的, 只是需要注意 Dockerfile.
dockerfile
# 这里的 "--platform=$BUILDPLATFORM" 必不可少, 否则会在 dotnet restore 时无限等待.
# 这是 Docker 的功能, 用于指明: 无论目标平台是什么, 构建时的镜像都采用当前设备的原生架构.
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS builder上述命令中的 BUILDPLATFORM 属于 Automatic platform ARGs.