C# – 冷知识 (新手)
闭包
C# 比较少用匿名函数,所以闭包坑没用像早年 JavaScript 那么明显,但它还是有相同的坑哦。
参考:博客园 小林野夫 – C# 闭包
List<Action> actions = []; for (var index = 0; index < 3; index++) { actions.Add(() => Console.WriteLine(index)); } actions.ForEach(action => action()); // 3 ... 3 ... 3
输出的结果是三个 3。
因为匿名函数里的 index 是对外部变量的引用。
当匿名函数被执行时,此时的 index 已经 for loop 结束,变成 3 了。
要避开闭包的坑,可以使用 foreach
List<Action> actions = []; foreach (var index in Enumerable.Range(0, 3)) { actions.Add(() => Console.WriteLine(index)); } actions.ForEach(action => action()); // 0 ... 1 ... 2
因为 C# 对 foreach 有特殊处理。
或者使用 local variable 把 index 保存起来
List<Action> actions = []; for (var index = 0; index < 3; index++) { var localIndex = index; actions.Add(() => Console.WriteLine(localIndex)); } actions.ForEach(action => action()); // 0 ... 1 ... 2
替 Action/Func Parameter 设置名字
public static void MatchBracket(string value, string bracket, Action<int, int, string> action) { }
Action/Func 的 parameter 是不可以设置名字的, 只能声明类型, 对调用的人不友好.
Can you name the parameters in a Func<T> type?
有 2 个方法可以让它好一些.
1. 用 delegate 声明
public delegate void Action(int start, int end, string valueInBracket); public static void MatchBracket(string value, string bracket, Action action) { }
虽然在调用的时候依然无法智能提示, 但至少有个地方可以找到.
2. 用 Tuple
public static void MatchBracket1(string value, string bracket, Action<(int Start, int End, string ValueInBracket)> action) { }
调用时可以看到提示
缺点就是结构换了. 可能不习惯.
使用的时候要解构 (而且不能直接在 parameter 里解, 要拿出来才能解), 或者干脆把它当对象用会更好一些.
MatchBracket1(value, "{}", matchInfo => { var (start, end, valueInBracket) = matchInfo; });
它也不支持写 params
隐式类型转换
有一个 class Person
public class Person { }
我们尝试拿一个 int 强转到 Person
它会直接报错。
加上隐式类型转换操作符
public class Person { public static implicit operator Person(int number) { return new Person(); } }
这样就不会再报错
逻辑很简单,当强转的时候,操作符方法会被调用,参数是 55,然后返回一个 Person 实例。
日常例子
[ApiController] [Route("api")] public class PersonController( ApplicationDbContext db ) : ControllerBase { [ODataAttributeRouting] [EnableQuery] [HttpGet("people")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult<IQueryable<Person>> GetPeople() { return db.People; } }
GetPeople 方法要求返回 ActionResult 实例,但是代码最终返回的是 IQueryable,结果没有报错。
原因就是 ActionResult 内部实现了隐式类型转换操作符