DYNAMIC EXPLAINER / ARTICLE FLOW

把章节变成可单步观察的过程

01 / 10
STEP 01 / CODE / LOGIC

先说结论

var、泛型、out、lambda 和 foreach 不是五套孤立语法,它们经常共同出现在一次集合查询中:泛型约束元素类型,lambda 描述条件,out 返回额外结果,foreach 顺序消费集合,var 让局部类型由右侧表达式推导。

敌人字典:{ 42 -> HP 20, 77 -> HP 80 }
TryGetValue(42, out enemy) -> true,enemy.HP = 20
lambda:enemy => enemy.HP < 30 -> true
结果:42 号敌人进入低血量集合

分类:CSharp 基础数据结构

先说结论

var、泛型、out、lambda 和 foreach 不是五套孤立语法,它们经常共同出现在一次集合查询中:泛型约束元素类型,lambda 描述条件,out 返回额外结果,foreach 顺序消费集合,var 让局部类型由右侧表达式推导。

看一条完整数据流

敌人字典:{ 42 -> HP 20, 77 -> HP 80 }
TryGetValue(42, out enemy) -> true,enemy.HP = 20
lambda:enemy => enemy.HP < 30 -> true
结果:42 号敌人进入低血量集合

这里 out enemy 不是创建另一个敌人,lambda 也不会自动执行;它们只有在对应方法被调用时才参与这条数据流。

这些语法为什么重要

List<T>Dictionary<TKey, TValue>HashSet<T> 这些集合经常会和一些 C# 语法一起出现:

var
泛型 <T>
out
lambda =>
foreach

这些不是数据结构本身,但看集合代码时会反复遇到。

var

var 不是“没有类型”,而是让编译器自动推断类型。

var name = "Alice";
var score = 90;
var names = new List<string>();

等价于:

string name = "Alice";
int score = 90;
List<string> names = new List<string>();

一旦推断完成,类型就是固定的:

var score = 90;
score = "hello"; // 错,score 已经是 int

泛型 <T>

<T> 表示“这个集合里存什么类型”。

List<int> nums = new();
List<string> names = new();
Queue<GameObject> effects = new();

Dictionary<string, int> 有两个类型:

string:key 的类型
int:value 的类型
Dictionary<string, int> scores = new();
scores["Alice"] = 90;

含义:

用 string 当 key,找到 int 分数。

out

out 常见于 TryGetValue

if (scores.TryGetValue("Alice", out int score))
{
    Debug.Log(score);
}

out int score 的意思是:

给方法一个变量位置;
如果方法找到了结果,就把结果填到 score 里面。

可以拆开理解:

int score;
bool found = scores.TryGetValue("Alice", out score);

if (found)
{
    Debug.Log(score);
}

这种 TryXxx(out value) 模式很常见:

int.TryParse("123", out int number);
float.TryParse("3.14", out float value);
dict.TryGetValue(key, out var item);

它的特点是:

不靠异常处理失败;
而是返回 true / false。

lambda =>

lambda 是临时写在调用处的小函数。

例如:

names.RemoveAll(name => name == "Alice");

name => name == "Alice" 的意思是:

给我一个 name;
我判断它是不是 Alice;
是就返回 true,不是就返回 false。

等价理解:

bool IsAlice(string name)
{
    return name == "Alice";
}

常见写法:

names.RemoveAll(name => name.StartsWith("A"));
items.Find(item => item.Id == 1001);
items.Exists(item => item.Count > 0);
items.RemoveAll(item => item.IsExpired);

foreach

遍历集合:

foreach (string name in names)
{
    Debug.Log(name);
}

含义:

从 names 里一个一个拿出 string,临时叫 name。

遍历字典:

foreach (var pair in scores)
{
    Debug.Log($"{pair.Key}: {pair.Value}");
}

pairKeyValuePair<string, int>

完整写法:

foreach (KeyValuePair<string, int> pair in scores)
{
    Debug.Log(pair.Key);
    Debug.Log(pair.Value);
}

foreach 里不要修改集合

不要这样写:

foreach (var name in names)
{
    if (name == "Alice")
    {
        names.Remove(name); // 危险
    }
}

通常会报:

Collection was modified

要删除多个元素,用:

names.RemoveAll(name => name == "Alice");

或者倒序 for

for (int i = names.Count - 1; i >= 0; i--)
{
    if (names[i] == "Alice")
    {
        names.RemoveAt(i);
    }
}

组合例子

Dictionary<string, List<int>> skillsByUnit = new();

if (!skillsByUnit.TryGetValue("monster_01", out var skills))
{
    skills = new List<int>();
    skillsByUnit["monster_01"] = skills;
}

skills.Add(1001);
skills.RemoveAll(skillId => skillId <= 0);

foreach (var skillId in skills)
{
    Debug.Log(skillId);
}

这里同时用了:

Dictionary<string, List<int>>:泛型
new():目标类型推断
out var skills:从字典取值
lambda:RemoveAll 条件
foreach:遍历列表

最重要的收获

var:让编译器推断类型。
<T>:告诉集合里面存什么类型。
out:让方法把结果填出来。
=>:临时写一个小判断/小函数。
foreach:逐个遍历集合。

这些语法熟了之后,看集合代码会顺很多。