C# 试题 17:委托与 Lambda
0321 以下代码输出什么?
难度: 实战
Action a = () => Console.Write("x");
Action b = () => Console.Write("x");
Console.WriteLine(a == b);
- A.
False,两个 lambda 表达式生成不同的委托实例 - B.
True,方法体相同所以相等 - C. 编译失败
- D. 运行抛异常
查看答案与解析
正确答案A
解析: 每个 lambda 表达式都会编译成独立的(编译器生成的)方法与独立委托实例;委托相等比较的是目标方法加目标对象,两个不同方法自然不相等。即使方法体完全一样,a == b 也是 False。
关键点: 相同方法体的不同 lambda 表达式互不相等。
0322 以下代码输出什么?
难度: 进阶
Action a = M;
Action b = M;
Console.WriteLine(a == b);
static void M() { }
- A.
False - B.
True,同一方法组、同一目标的方法产生相等委托 - C. 编译失败
- D. 运行抛异常
查看答案与解析
正确答案B
解析: 方法组转换的委托相等性取决于目标方法与目标对象:两个转换都指向静态方法 M(目标为 null),语义上相等,输出 True。这与上题”相同方法体的 lambda 不相等”形成对比——lambda 每次生成新方法,方法组指向同一方法。
关键点: 方法组转换产生指向同一方法的委托,比较相等。
0323 以下代码输出什么?
难度: 实战
Action a = () => Console.Write("1");
Action b = a;
a += () => Console.Write("2");
a();
b();
- A.
12和12 - B.
1和1 - C.
12和1,+=生成新的多播委托,b仍指向原委托 - D. 编译失败
查看答案与解析
正确答案B
解析: 委托是不可变的:a += ... 创建新的多播委托并重新赋值给 a,b 仍持有旧的单播委托。因此 a() 输出 12,b() 输出 1。把委托当可变列表改是常见误解。
关键点: 委托不可变,+=/-= 返回新实例,旧引用不受影响。
0324 以下代码能否编译?
难度: 进阶
int x = 42;
Func<int> f = static () => x;
- A. 编译通过,调用输出
42 - B. 编译通过,但调用时抛异常
- C. 编译通过,
static修饰被忽略 - D. 编译失败,静态 lambda 不能捕获局部变量
查看答案与解析
正确答案D
解析: C# 9 的静态 lambda(static () => ...)禁止捕获实例状态与局部变量,引用 x 直接编译失败。它的价值是消除闭包对象的分配,但代价是只能用参数和静态成员。
关键点: 静态 lambda 不能捕获,捕获就编译失败。
0325 关于静态 lambda(static () => ...),哪项正确?
难度: 进阶
- A. 不能捕获实例状态与局部变量,但可以使用参数和静态成员
- B. 与普通 lambda 完全相同,
static只是装饰 - C. 只能用于无参 lambda
- D. 静态 lambda 不能访问静态字段
查看答案与解析
正确答案A
解析: static 修饰只限制捕获:实例成员、局部变量、this 都不能引用;参数与静态成员不受限。例如 static (int y) => y + StaticField 合法。它主要用于避免闭包分配,适合高频调用路径。
关键点: 静态 lambda = 无捕获的 lambda,可用参数与静态成员。
0326 以下代码输出什么?
难度: 实战
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.Write(i));
}
foreach (var a in actions)
{
a();
}
- A.
333 - B.
012,C# 5 起 for 循环变量每次迭代都是新变量 - C.
222 - D. 编译失败
查看答案与解析
正确答案B
解析: C# 5 起 for 循环的迭代变量在每次迭代都生成新变量,每个闭包捕获各自的一份,输出 012。若手写 while 共享同一个变量,仍会得到 333——问题在变量是否每轮新建,而非”循环自动安全”。
关键点: for/foreach 的迭代变量按迭代隔离,闭包捕获的是各自副本。
0327 以下代码输出什么?
难度: 实战
int x = 1;
Func<int> f = () => x;
x = 2;
Console.WriteLine(f());
- A.
1 - B.
0 - C.
2,闭包按引用捕获变量而非值快照 - D. 编译失败
查看答案与解析
正确答案C
解析: 闭包捕获的是变量本身(编译器提升为闭包对象的字段),调用时读取的是当前值。捕获发生在创建时,但读值发生在调用时,所以先改 x 再调用得到 2。“捕获即快照”是常见误判。
关键点: 闭包引用变量,不复制值;值在调用时读取。
0328 以下代码输出什么?
难度: 进阶
int x = 0;
Func<int> a = () => ++x;
Func<int> b = () => ++x;
a();
b();
Console.WriteLine(x);
- A.
0 - B.
1 - C. 编译失败
- D.
2,两个闭包共享同一个被捕获变量
查看答案与解析
正确答案D
解析: 两个 lambda 捕获同一个 x,编译器只创建一个闭包对象,x 是其共享字段;a() 与 b() 各自自增一次,最终 x == 2。多个闭包共享捕获变量时,它们的读写互相可见。
关键点: 捕获同一变量的多个闭包共享该变量,不是各自副本。
0329 以下代码输出什么?
难度: 实战
var f = (int x) => x * 2;
Console.WriteLine(f(21));
- A.
42,自然类型推断为Func<int, int> - B.
21 - C. 编译失败,
var不能用于 lambda - D. 运行抛异常
查看答案与解析
正确答案A
解析: C# 10 起 lambda 有自然类型:参数全部显式声明类型时,编译器能推断出 Func<int, int>,var f = (int x) => x * 2; 合法并输出 42。C# 10 之前 var 声明 lambda 无法编译。
关键点: 显式参数类型的 lambda 可用 var 获得自然类型。
0330 以下代码能否编译?
难度: 进阶
var f = x => x * 2;
- A. 编译通过,
x推断为int - B. 编译失败,参数无类型时无法推断自然委托类型
- C. 编译通过,
x推断为object - D. 编译通过,但运行时抛异常
查看答案与解析
正确答案B
解析: 自然类型要求参数类型可知:x => ... 中 x 没有显式类型,也没有可套用的目标委托类型(var 不提供目标类型),编译器无法推断,报错。这与 Func<int,int> f = x => x * 2;(有目标类型,合法)不同。
关键点: 隐式参数类型需要目标类型,var 推断自然类型时参数必须显式声明。
0331 以下代码输出什么?
难度: 进阶
Expression<Func<int, int>> e = x => x + 1;
var f = e.Compile();
Console.WriteLine(f(1));
- A. 编译失败
- B.
1 - C.
2,表达式树可编译为可调用委托 - D. 运行抛异常
查看答案与解析
正确答案C
解析: 赋给 Expression<Func<int,int>> 时 lambda 被表示为表达式树,Compile() 把树编译为委托,f(1) = 1 + 1 = 2。表达式树不是”存了 lambda”,而是把逻辑拆成可检查的树节点。
关键点: Expression<...> 存的是树,Compile() 之后才可调用。
0332 以下代码能否编译?
难度: 实战
Expression<Func<int, int>> e = x => { return x + 1; };
- A. 编译通过
- B. 编译通过,运行抛异常
- C. 编译通过,但编译后返回 null
- D. 编译失败,语句体 lambda 不能转换为表达式树
查看答案与解析
正确答案D
解析: 表达式树只能表示表达式,不能表示语句块;{ return x + 1; } 是语句体 lambda,赋给 Expression<...> 报 CS0834。这是 ORM 场景”为什么必须写表达式写法”的原因之一。
关键点: 表达式树要求表达式体 lambda,语句体一律编译失败。
0333 以下代码能否编译?
难度: 进阶
int y = 0;
Expression<Func<int>> e = () => y++;
- A. 编译失败,表达式树不能包含赋值/自增操作
- B. 编译通过,调用后
y == 1 - C. 编译通过,调用后
y == 0 - D. 编译通过,运行抛异常
查看答案与解析
正确答案A
解析: 表达式树不允许赋值类操作(=、++、-- 等),报 CS0832。注意表达式树允许引用被捕获的变量(会编译成字段访问),所以”表达式树不能捕获变量”也是误区——不能的是赋值,不是读取。
关键点: 表达式树可读捕获变量,但不可写。
0334 以下代码输出什么?
难度: 实战
Action a = () => Console.Write("1");
a += () => throw new InvalidOperationException();
a += () => Console.Write("3");
try
{
a();
}
catch (InvalidOperationException)
{
}
- A. 输出
13 - B. 输出
1后抛异常,“3”不会执行 - C. 输出
123 - D. 编译失败
查看答案与解析
正确答案B
解析: 多播委托按顺序调用订阅者,一旦某个订阅者抛异常,调用立即中止,后续订阅者(“3”)不会执行,异常沿调用方传播。没有”事件框架帮你跳过异常订阅者”这回事。
关键点: 多播委托遇到异常即中止,后续订阅者不执行。
0335 以下代码输出什么?
难度: 进阶
Action a = null;
a += () => Console.Write("1");
a -= () => Console.Write("1");
a?.Invoke();
- A. 什么都不输出
- B. 运行抛异常
- C. 输出
1,-=中的新 lambda 是不同委托实例,无法匹配移除 - D. 编译失败
查看答案与解析
正确答案C
解析: -= 右边的 lambda 是全新表达式,编译成不同方法、不同委托实例,与已订阅的委托不相等,Delegate.Remove 找不到匹配项,原多播委托原样保留,输出 1。匿名 lambda 无法退订,必须保存委托引用或使用方法组。
关键点: 用新 lambda 退订等于没退订。
0336 关于委托实例的相等性,哪项正确?
难度: 进阶
- A. 只要方法体相同,委托就相等
- B. 委托相等性没有定义
- C. 委托总是按引用比较,内容无关
- D. 委托相等取决于委托类型、目标方法与目标对象;同一实例的同一方法才相等
查看答案与解析
正确答案D
解析: Delegate.Equals 比较类型、目标方法(Method)与目标对象(Target):obj.M 与 obj.M 相等,obj1.M 与 obj2.M 不相等(目标不同)。lambda 每次生成新方法,所以相同方法体的两个 lambda 委托不相等。多播委托还要比较整个调用列表。
关键点: 委托相等 = 类型 + 方法 + 目标对象,不是方法体文本。
0337 以下代码输出什么?
难度: 进阶
static Func<int> Make()
{
int x = 0;
return () => x;
}
var f1 = Make();
var f2 = Make();
Console.WriteLine(f1 == f2);
- A.
False,每次调用Make创建新的闭包对象 - B.
True,编译器缓存了闭包 - C. 编译失败
- D. 运行抛异常
查看答案与解析
正确答案A
解析: 捕获变量的 lambda 需要闭包对象承载变量;Make 每次调用都 new 一个闭包实例,委托目标(Target)不同,因此不相等。只有不捕获的 lambda 才会被缓存为单一静态委托实例。
关键点: 捕获型闭包每次调用新建,无捕获 lambda 才可能缓存复用。
0338 以下代码能否编译?
难度: 进阶
object o = (int x) => x;
- A. 编译通过,
o得到Func<int, int> - B. 编译失败,lambda 不能直接转换为
object - C. 编译通过,但
o为 null - D. 编译通过,运行抛异常
查看答案与解析
正确答案B
解析: lambda 只能转换为委托类型或表达式树类型,即使它有自然类型,也不能隐式转换为 object(CS1660)。必须先赋给委托再装箱,如 object o = (Func<int,int>)(x => x);。
关键点: 有自然类型 ≠ 能转 object,lambda 的目标类型必须是委托/表达式树。
0339 以下代码输出什么?
难度: 实战
Action a = null;
a += M1;
a += M2;
a += M1;
a -= M1;
a();
static void M1() => Console.Write("1");
static void M2() => Console.Write("2");
- A.
112 - B.
1 - C.
12,-=移除的是最后一个匹配项 - D.
21
查看答案与解析
正确答案C
解析: 调用列表是 [M1, M2, M1];-= 用 Delegate.Remove 移除最靠后的匹配项,得到 [M1, M2],调用输出 12。多播列表允许重复项,“移除一个”不等于”清空所有匹配项”。
关键点: -= 移除最后一个匹配项,重复订阅需要逐个移除。
0340 关于 Lambda 与委托,哪项正确?
难度: 进阶
- A. Lambda 表达式是值类型
- B. 委托实例总是互不相等
- C. 不同 lambda 表达式生成不同方法/委托;方法组转换的同一方法与同一目标则产生相等委托
- D. 委托只能在栈上使用
查看答案与解析
正确答案C
解析: lambda 是语法糖,每个表达式编译成独立方法(捕获时外加闭包类型),其委托自然互不相等;方法组转换直接指向既有方法,同一方法同一目标时相等。委托是引用类型、分配在堆上,不是值类型。
关键点: lambda 生成新方法 ⇒ 委托不相等;方法组指向既有方法 ⇒ 可相等。