C#技巧记录——持续更新
C# 学习笔记
一名非主修 C# 的程序员,在此记录学习与工作中遇到的 C# 有用内容,持续更新。
目录
- 泛型
- using 用法
- 日期时间处理
- 字符串
- 条件编译
- 方法参数
- 类与成员
- 匿名类型
- 运算符
- 数组与集合
- LINQ
- 委托与 Lambda
- 反射与枚举
- 类型判断与转换
- 其他关键字
- 文件与 IO
- 路径与编码
- WPF
- Entity Framework
- 工程与工具链
- 杂项
泛型
类型约束
// 正确写法:
where T : class // T 必须是引用类型
where T : struct // T 必须是值类型(小写 struct)
where T : new() // T 必须具有无参公共构造函数,且必须放在约束列表最后
where T : IUser // T 必须实现 IUser 接口
where T : BaseClass // T 必须继承自 BaseClass
// 组合示例
public T Create<T>() where T : class, IUser, new()
{
return new T();
}
class与struct互斥,不能同时使用;new()必须是最后一个约束。
using 用法
创建别名(类似 C 的 typedef)
using MyInt = System.Int32; // 为 Int32 定义别名
using static(导入静态成员)
using static System.Console; // ⚠️ 需要写完整命名空间
class Program
{
static void Main()
{
Write("Hello"); // 已导入,无需加 Console 前缀
}
}
日期时间处理
当日 00:00:00
DateTime date = DateTime.Now;
date = date.Date; // Date 属性返回该时间的日期部分,时分秒置为 0
当月第一天 ~ 最后一天
DateTime start = DateTime.Now.AddDays(-(int)DateTime.Now.Day + 1).Date;
DateTime end = start.AddMonths(1).AddDays(-1);
当年第一天 ~ 最后一天
DateTime start = new DateTime(DateTime.Now.Year, 1, 1);
DateTime end = start.AddYears(1).AddDays(-1);
当季度第一天 ~ 最后一天
int quarter = (DateTime.Now.Month - 1) / 3 + 1; // 1 ~ 4
int startMonth = (quarter - 1) * 3 + 1; // 1 / 4 / 7 / 10
DateTime start = new DateTime(DateTime.Now.Year, startMonth, 1);
DateTime end = start.AddMonths(3).AddDays(-1);
按周分组(周日划归上一周)
data.GroupBy(f => f.Day.AddDays(f.Day.DayOfWeek == DayOfWeek.Sunday
? -7
: -(int)f.Day.DayOfWeek));
字符串
逐字字符串(包含特殊字符)
string longStr = @"\[][;\&"; // @ 后所有字符按字面处理,转义符失效
插值字符串
int a = 9;
string b = $"a:{a}"; // 使用变量或表达式填充
分割:匹配多个字符
str.Split('a', 'b', 'c');
分割:使用 \r\n
var infoStr = examInfo.Split(new string[] { "\r\n" },
StringSplitOptions.RemoveEmptyEntries);
Base64 转图片
public static void SaveImage(string logoBase64, string path, string imageName)
{
string finalPath = Path.Combine(path, imageName);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
File.WriteAllBytes(finalPath, Convert.FromBase64String(logoBase64));
}
条件编译
// DEBUG 是编译器预定义的特殊变量,表示是否为调试模式
// 也可以自定义:使用 #define,且必须声明在文件开始处,同 C/C++
#if DEBUG
Console.Write("debug");
#else
Console.Write("un debug");
#endif
#if aa
Console.WriteLine("我是A");
#else
Console.WriteLine("我是B");
#endif
方法参数
ref / out
// ref:传递引用,实参必须先初始化
void Show(ref int x) => Console.Write(x);
int i = 9;
Show(ref i);
// out:解决 ref 需要初始化的问题,实参可以不初始化,但方法内必须赋值
void Show(out int x) => Console.Write(x);
int j;
Show(out j);
命名参数
// 可以随意交换实参顺序:冒号前指定参数名,冒号后为值(与 Swift 一致)
public static void Show(int x, int y) => Console.Write("{0},{1}", x, y);
Show(y: 9, x: 20);
可变个数参数 params
public static void G(params int[] d)
{
foreach (var item in d)
{
Console.Write(item);
}
}
类与成员
自动实现属性
public int Age { get; set; }
静态构造函数
// 在第一次引用该类型之前自动调用,且只调用一次
class Test
{
static int count;
static Test()
{
count = 1;
}
public Test() { }
public void Show() => Console.Write(count);
}
readonly
class Test
{
readonly int count = 0; // 只允许在声明处初始化或在构造函数中赋值
public Test()
{
count = 1;
}
public void Show()
{
// count = 3; // ❌ 编译错误
Console.Write(count);
}
}
构造函数初始化器
class A
{
A(int i)
{
B = i;
}
// 自动调用相应构造函数,且在本构造函数体之前执行
A() : this(1)
{
}
}
表达式体属性(类成员使用 lambda 语法)
class A
{
int a;
int b;
int c => a + b;
}
override 与 new
- 使用
override:无论通过基类还是派生类引用调用TEST(),都会执行实际(派生)类的实现。 - 使用
new:只是隐藏基类方法,通过类型转换到基类即可调用到基类的TEST()。
使用保留关键字作为变量名
// 变量名前加 @,为跨平台 / 跨语言互操作提供便利
public class Test
{
public string @class { get; set; }
public bool @checked { get; set; }
}
匿名类型
// 常用于描述 JSON 对象,免去每次都新建类的麻烦
var a = new { age = 0, name = "lilei" };
Console.Write(a.age + " " + a.name);
// ⚠️ 成员之间用逗号分隔,不是分号
var zhangsan = new
{
name = "zhangsan",
age = 20
};
运算符
?? 空合并运算符
int? x = null;
// 传统写法
int a;
if (x == null) { /* ... */ } else { /* ... */ }
// 合并运算符:x 非空取 x 的值,x 为空取 0
a = x ?? 0;
?. 空值传播与 ?? 空值合并
// 空指针异常写法
// string i = null;
// Console.WriteLine(i.ToString());
// 安全写法:i 为 null 时直接返回 null,输出空白
string i = null;
Console.WriteLine(i?.ToString());
Console.WriteLine("end");
var x = new { a = "a", b = "b" };
var xx = x?.a ?? ""; // x 为空则整个表达式返回 null,再由 ?? 兜底为 ""
!. 空包容运算符(null-forgiving)
List<int> x = new();
x!.First(); // 告诉编译器「这里一定不为空」,抑制可空性警告(运行时不保证)
数组与集合
多维数组定义
// 与 C 风格不同,方括号写在类型后面
int[,] users = new int[2, 2];
排序
// 数组自带的快速排序,要求元素类型已实现 IComparable 接口
int[] te = { 555, 6, 2, 1, 65, 99, 45, 63 };
Array.Sort(te);
foreach (var item in te)
{
Console.Write(item + "\n");
}
NameValueCollection
// 与字典不同:同一个键可以添加多次
NameValueCollection nvc = new NameValueCollection();
nvc.Add("s", "a");
nvc.Add("s", "b");
// 遍历某个键下的所有值
foreach (var item in nvc.GetValues(0))
{
Console.WriteLine(item);
}
C# 12+ 集合表达式
string[] result = [..a, ..b]; // 展开并合并两个集合
LINQ
// 语法与 SQL 类似,不同之处在于 from 提前(便于类型推断),select 放最后
// d 为查询后的对象,in 之后为数据来源
int[] ary = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from d in ary
where d == 1
select d;
foreach (var item in result)
{
Console.WriteLine(item.ToString());
}
// 可以为查询变量显式指定类型:类型错误会产生运行时异常
var result2 = from int d in ary
where d == 1
select d;
随机取 N 条数据
public static IQueryable<T> RandTake<T>(this IQueryable<T> query,
Expression<Func<T, bool>> predicate,
int count)
{
return query.Where(predicate)?.OrderBy(a => Guid.NewGuid())?.Take(count);
}
委托与 Lambda
Action / Func
void a() { }
int b() { return 0; }
void Test()
{
Action ax = new Action(a);
Func<int> af = new Func<int>(b);
}
Lambda 表达式
int GetDouble(int x) => x * x; // 这是「表达式体方法」,不是局部 lambda
[InstantHandle]
标识该 lambda 是立即执行的,可以用此特性通过 lambda 把运行时参数传递给后续回调(JetBrains 注解特性)。
反射与枚举
获取枚举的 [Description("")]
public static class EnumHelper
{
public static string GetDescription(this Enum enumeration)
{
Type type = enumeration.GetType();
MemberInfo[] memInfo = type.GetMember(enumeration.ToString());
if (null != memInfo && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (null != attrs && attrs.Length > 0)
return ((DescriptionAttribute)attrs[0]).Description;
}
return enumeration.ToString();
}
}
获取枚举的所有值
public static Array GetAll(this Enum e)
{
return Enum.GetValues(e.GetType());
}
获取对象的 Description
public static string Description(this object AEnum)
{
Type type = AEnum.GetType();
MemberInfo[] memInfo = type.GetMember(AEnum.ToString());
if (null != memInfo && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (null != attrs && attrs.Length > 0)
return ((DescriptionAttribute)attrs[0]).Description;
}
return AEnum?.ToString();
}
类型判断与转换
is / as
// is:返回判断结果 true / false
// as:返回转换后的引用,转换失败返回 null
// 两者失败时都不抛异常
类型转换
Convert.ChangeType(value, property.PropertyType);
static 与 const
static:表示静态的。const:表示静态的常量。
其他关键字
nameof
// 获取方法、类、变量的名称(编译期常量,重命名时自动同步)
nameof(SomeMethod);
default 获取类型默认值
int i = default(int); // C# 7.1+ 可简写为:int i = default;
checked / unchecked
// 检测代码块在计算过程中是否发生溢出
// 默认为不检测(unchecked),一般不需要显式写 unchecked
checked
{
int x = int.MaxValue + 1; // 抛出 OverflowException
}
checked 作为属性名(关键字冲突)
public bool @checked { get; set; }
文件与 IO
共享模式写文件
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.ReadWrite))
{
fs.SetLength(0);
using (StreamWriter writer = new StreamWriter(fs, Encoding.Default))
{
string infos = JsonConvert.SerializeObject(info.data);
writer.Write(infos);
writer.Flush();
}
}
共享模式读文件
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.ReadWrite))
{
// ⚠️ 读取时不要 SetLength(0),否则内容会被清空
using (StreamReader reader = new StreamReader(fs))
{
string lifeStr = reader.ReadToEnd();
}
}
using块结束时会自动 Dispose,无需手动调用writer.Dispose()/fs.Dispose()。
随机文件名与临时目录
Console.WriteLine(Path.GetRandomFileName()); // 随机文件名
Console.WriteLine(Path.GetTempFileName()); // 临时文件(会自动创建文件)
Console.WriteLine(Path.GetTempPath()); // 临时目录
路径与编码
获取运行路径
Directory.GetCurrentDirectory(); // 当前工作路径(可能变化)
Environment.CurrentDirectory; // 当前工作路径
AppDomain.CurrentDomain.BaseDirectory; // 程序文件所在路径(推荐)
AppDomain.CurrentDomain.SetupInformation.ApplicationBase; // 程序文件所在路径
指定字符集报错
'GB2312' is not a supported encoding name. For information on defining a custom encoding,
see the documentation for the Encoding.RegisterProvider method.
// .NET Core 默认不含 GB2312 等编码,需先注册
Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
var bytes = Encoding.GetEncoding("GB2312").GetBytes(str);
MIME 类型预定义字符串
系统已自带,无需自定义:见 System.Net.Mime.MediaTypeNames(MediaTypeNames.Text.Plain、MediaTypeNames.Application.Json、MediaTypeNames.Image.Jpeg 等)。
WPF
ShowActivated
指示窗口在第一次显示时是否处于激活状态(ShowActivated="False" 可避免抢焦点)。
获取控件所属的窗口
Button button = sender as Button;
Window targetWindow = Window.GetWindow(button);
获取主窗口
Application.Current.MainWindow
检测窗口是否已经打开过
// ⚠️ 窗口对象引用在窗口关闭后并不会变成 null,不能用 null 判断
public static bool IsAlive(this Window window)
{
return Application.Current.Windows.OfType<Window>().Contains(window);
}
Entity Framework
// 关闭状态追踪
.AsNoTracking()
// 关闭状态同步
context.ChangeTracker.AutoDetectChangesEnabled = false;
EF 中跟踪的数据都记录在 db.Set<T>().Local 对象中。如果同一事务中修改并保存了变更,后续再次变更同一实体时不得重复跟踪,需先移除跟踪:
public T Update(T entity)
{
var localData = db.Set<T>().Local.FirstOrDefault(e => e.Id == entity.Id);
if (localData != null)
{
db.Entry(localData).State = EntityState.Detached;
}
db.Set<T>().Attach(entity).State = EntityState.Modified;
return entity;
}
Migration 指定上下文
Update-Database -Context BookStoreDbContext
工程与工具链
指定 C# 语言版本
默认语言版本与目标框架(TFM)保持一致(如 .NET 9 → C# 13);不建议设 latest,因为该值随机器安装的编译器变化,会导致构建不可靠[citation:2]。
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
| 值 | 含义 |
|---|---|
preview |
编译器接受最新预览版中的所有有效语言语法 |
latest |
编译器接受最新发布的编译器版本(含次要版本)中的语法 |
latestMajor(默认) |
编译器接受最新发布的编译器主要版本中的语法 |
10.0 |
只接受 C# 10 或更低版本中的语法 |
9.0 |
只接受 C# 9 或更低版本中的语法 |
8.0 |
只接受 C# 8.0 或更低版本中的语法 |
7.3 / 7.2 / 7.1 / 7 |
只接受对应版本或更低版本中的语法 |
6 / 5 / 4 / 3 |
只接受对应版本或更低版本中的语法 |
ISO-2(或 2) |
只接受 ISO/IEC 23270:2006 C# (2.0) 中的语法 |
ISO-1(或 1) |
只接受 ISO/IEC 23270:2003 C# (1.0/1.2) 中的语法 |
不支持使用比目标 TFM 关联版本更新的 C# 语言版本[citation:2]。在代码中添加 #error version 可让编译器报告当前语言版本[citation:2]。
参考:配置语言版本 - C# reference[citation:2]
发布单个 exe
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true
发布指定操作系统、架构到指定路径
dotnet publish D:\WebAPI.sln --os win -a x64 -c Release --property:PublishDir=D:\publish
配置 NuGet 源
dotnet nuget add source [仓库地址] -n [仓库名称]
命令行更新项目中的单个 NuGet 包
nuget.exe update X.csproj -Id PackageA
项目版本号
<PropertyGroup>
<VersionPrefix>1.2.0</VersionPrefix>
</PropertyGroup>
控制台程序隐藏执行
修改项目文件(.csproj),或将项目输出类型改为「Windows 应用程序」:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType> <!-- 而不是 Exe / Console -->
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
</Project>
编译后复制文件夹(生成事件)
xcopy "$(ProjectDir)\Assets" "$(TargetDir)\Assets\" /S /Y
显示 TODO 标签
视图 → 任务列表
VS 配置命名规范
工具 → 选项 → 文本编辑器 → C# → 代码样式 → 命名
ILDASM 工具位置
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools
杂项
AppContext(动态开关)
// 设置开关,可用于实现动态切换:如配置文件为 DEBUG 模式则自动添加测试数据
AppContext.SetSwitch("Switch.AmazingLib.ThrowOnException", true);
// 读取开关
public class AmazingLib
{
private bool shouldThrow;
public void PerformAnOperation()
{
if (!AppContext.TryGetSwitch("Switch.AmazingLib.ThrowOnException", out shouldThrow))
{
// 应用未设置该开关,库可选择其他方式获取,默认 false(即最新行为)
}
if (shouldThrow)
{
// old code
}
else
{
// new code
}
}
}
Lazy<T> 延迟加载
public class Student
{
public Student()
{
this.Name = "DefaultName";
this.Age = 0;
Console.WriteLine("Student is init...");
}
public string Name { get; set; }
public int Age { get; set; }
}
Lazy<Student> stu = new Lazy<Student>();
if (!stu.IsValueCreated)
Console.WriteLine("isn't init!");
Console.WriteLine(stu.Value.Name); // 首次访问 Value 时才真正构造
stu.Value.Name = "Tom";
stu.Value.Age = 21;
Console.WriteLine(stu.Value.Name);
WeakReference 弱引用
弱引用:在引用对象的同时,允许 GC 回收该对象。适用于「创建便宜但占用大量内存」、既希望保留又希望在内存紧张时被回收的对象。
自宿主 WebAPI(Minimal API)
Task.Run(() =>
{
var builder = WebApplication.CreateBuilder(new string[] {
"--urls", "http://localhost:8888",
});
var app = builder.Build();
app.MapGet("/", () => "Hello .net mini api");
app.Run();
});
Socket 发送文件(打印)
string fileName = "PDF_2224101MV.pdf";
string filePath = Path.Combine(Environment.CurrentDirectory, fileName);
byte[] buffer;
using (Stream stream = new FileStream(filePath, FileMode.OpenOrCreate))
{
buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
}
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.NoDelay = true;
IPAddress ip = IPAddress.Parse("10.159.4.28");
IPEndPoint endPoint = new IPEndPoint(ip, 9100);
clientSocket.Connect(endPoint);
clientSocket.Send(buffer);
clientSocket.Close();
byte / sbyte
byte:无符号(0 ~ 255)sbyte:有符号(-128 ~ 127)
Microsoft.Windows.Compatibility
从 Framework 迁移到 .NET Core 时缺失了一部分 API(如 Drawing、EventLog、WMI、Performance Counters),该兼容包中基本都有补充。
联系我:ivesbao@163.com

浙公网安备 33010602011771号