C# 试题 40:dynamic 与表达式树
0781 以下代码输出什么?
难度: 进阶
dynamic d = null;
try
{
d.ToString();
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().Name);
}
- A. 抛
NullReferenceException - B. 编译失败,不能对 null 调用成员
- C. 无异常,输出空行
- D. 抛
RuntimeBinderException,dynamic 在 null 引用上无法完成绑定
查看答案与解析
正确答案D
解析: dynamic 的成员访问在运行时绑定,目标为 null 时绑定无法完成,抛 RuntimeBinderException(消息为 “Cannot perform runtime binding on a null reference”)。只有静态类型为 null 的引用调用实例成员才抛 NullReferenceException。
关键点: dynamic 对 null 调用成员抛 RuntimeBinderException,不是 NullReferenceException。
0782 以下代码输出什么?
难度: 进阶
dynamic d = 42;
var v = d;
v += 1;
Console.WriteLine(v);
- A. 输出
43,var推断出的类型是 dynamic,v += 1走运行时绑定 - B. 编译失败,
var无法从 dynamic 推断类型 - C. 输出
421 - D. 运行抛
RuntimeBinderException
查看答案与解析
正确答案A
解析: var 推断的是初始化表达式的静态类型,d 是 dynamic,所以 v 也是 dynamic;v += 1 在运行时按真实类型 int 绑定加法,输出 43。若先 object o = d; var v = o;,则 v 是 object,加法将无法编译。
关键点: dynamic 会“传染”给 var,var 不会把 dynamic 拆成 object。
0783 以下代码能否编译?若能,输出什么?
难度: 实战
object o = 10;
dynamic d = 10;
Console.WriteLine(o + 1);
Console.WriteLine(d + 1);
- A. 两行都编译失败
- B. 两行都输出
11 - C. 第一行编译失败,第二行输出
11 - D. 第一行输出
10,第二行输出101
查看答案与解析
正确答案C
解析: o 的静态类型是 object,+ 运算符对 object 未定义,o + 1 在编译期报错;d 的绑定推迟到运行时,按真实类型 int 执行加法,输出 11。
关键点: dynamic 与 object 的差别是绑定时机,不是运行时类型。
0784 以下代码输出什么?
难度: 进阶
class Program
{
static void M(int x) => Console.WriteLine("int");
static void M(long x) => Console.WriteLine("long");
static void M(object x) => Console.WriteLine("object");
static void Main()
{
dynamic d = 1;
M(d);
}
}
- A. 编译失败,dynamic 不能参与重载决议
- B. 输出
int,运行时按实参真实类型选择最合适重载 - C. 输出
object - D. 输出
long
查看答案与解析
正确答案B
解析: dynamic 实参把重载决议推迟到运行时,运行时按实参真实类型 int,在所有候选重载中选择最具体的 M(int),因此输出 int。决议规则与编译期一致,只是时机不同。
关键点: dynamic 参与“运行时重载决议”,规则与编译期相同。
0785 以下代码输出什么?
难度: 实战
dynamic items = new List<int> { 1, 2, 3 };
Console.WriteLine(items.Sum());
- A. 抛
RuntimeBinderException,dynamic 调用不解析扩展方法 - B. 输出
6 - C. 编译失败
- D. 输出
0
查看答案与解析
正确答案A
解析: 扩展方法靠编译期 using 查找并静态解析;dynamic 接收者把所有绑定推迟到运行时,而运行时只搜索实例成员,List<int> 没有实例 Sum(),于是抛 RuntimeBinderException。
关键点: dynamic 调用永远不会命中扩展方法。
0786 以下代码能否编译?
难度: 进阶
dynamic d = 1;
Expression<Func<int, int>> e = x => x + d;
- A. 编译通过,d 在运行时求值
- B. 编译通过,但 e 编译时抛异常
- C. 编译失败,表达式树不能包含 dynamic 操作
- D. 编译通过,d 被当作 object 装箱
查看答案与解析
正确答案C
解析: 表达式树只能用节点描述可静态分析的表达式,dynamic 操作的绑定无法用表达式树节点表达,编译器直接报“表达式树不能包含 dynamic 操作”。
关键点: 表达式树与 dynamic 互斥。
0787 以下代码能否编译?
难度: 进阶
Expression<Func<int, int>> e = x => { return x + 1; };
- A. 编译通过,语句体自动转为表达式
- B. 编译失败,语句体 lambda 不能转换为表达式树
- C. 编译通过,运行时抛
NotSupportedException - D. 编译通过,结果为 null
查看答案与解析
正确答案B
解析: 表达式树只支持表达式体 lambda;带花括号的语句体 lambda 无法转换为表达式树,这是编译期规则。
关键点: 转表达式树的 lambda 必须是表达式体。
0788 以下代码输出什么?
难度: 进阶
Expression<Func<int, int>> e = x => x + 1;
var f1 = e.Compile();
var f2 = e.Compile();
Console.WriteLine(ReferenceEquals(f1, f2));
- A. 编译失败
- B. 输出
True,同一表达式 Compile 结果被缓存 - C. 输出
True,委托是值类型 - D. 输出
False,每次 Compile 都生成新委托
查看答案与解析
正确答案D
解析: Expression.Compile() 每次都会完整走一遍代码生成并返回新委托,框架不缓存编译结果。性能敏感代码必须自己缓存编译后的委托。
关键点: Compile 有成本且结果不缓存。
0789 以下代码输出什么?
难度: 实战
int y = 5;
Expression<Func<int, int>> e = x => x + y;
y = 10;
Console.WriteLine(e.Compile()(1));
- A. 输出
11,表达式树按引用捕获变量,编译后读取当前值 - B. 输出
6,创建表达式树时对 y 做了快照 - C. 输出
5 - D. 抛异常
查看答案与解析
正确答案A
解析: 表达式树与委托一样通过闭包字段按引用捕获外部变量,编译后访问的是闭包字段的当前值(y=10),因此输出 11;它不会在构建树时复制数值。
关键点: 表达式树捕获变量是“按引用”,不是按值快照。
0790 以下代码输出什么?
难度: 进阶
dynamic d = 3.14;
int i = d;
Console.WriteLine(i);
- A. 编译失败,dynamic 不能隐式转换为 int
- B. 运行抛
RuntimeBinderException,double 到 int 不存在隐式转换 - C. 输出
3 - D. 输出
3.14
查看答案与解析
正确答案B
解析: 从 dynamic 出发的转换在编译期一律放行(推迟到运行时判定);运行时 double→int 只有显式转换,隐式转换不成立,抛 RuntimeBinderException。
关键点: dynamic 转换“能编译”不代表“能运行”。
0791 以下代码输出什么?
难度: 进阶
Expression<Func<int, int>> e = x => x * 2;
Console.WriteLine(e.NodeType);
Console.WriteLine(e.Body.NodeType);
- A.
Lambda与Add - B.
LambdaExpression与Binary - C.
Lambda与Multiply - D.
Multiply与Lambda
查看答案与解析
正确答案C
解析: 根节点是 Lambda(NodeType 为 ExpressionType.Lambda),主体 x * 2 是乘法二元表达式,NodeType 为 Multiply。
关键点: 表达式树是节点化的数据,NodeType 描述每个节点的语义。
0792 关于表达式树的 Compile 成本,哪项正确?
难度: 进阶
- A. 同一 Expression 多次 Compile 自动复用委托,无需缓存
- B. Compile 只是遍历语法树,成本可忽略
- C. .NET Core 3.0+ 默认用解释器执行表达式树,不生成委托
- D. 每次 Compile 都生成新委托,热点应缓存;.NET Core 3.0+ 已移除解释器,
preferInterpretation被忽略
查看答案与解析
正确答案D
解析: 每次 Compile 都生成新的委托,重复调用会重复付出代码生成成本;.NET Core 3.0 起表达式树解释器被移除,Compile(preferInterpretation: true) 也会编译为委托。高频路径必须缓存编译结果。
关键点: 缓存 Compile 结果是表达式树高性能使用的关键。
0793 以下代码中 r1 与 r2 分别绑定到哪个扩展方法?
难度: 实战
IQueryable<int> q = new List<int> { 1, 2, 3 }.AsQueryable();
IEnumerable<int> e = q;
var r1 = q.Where(x => x > 1);
var r2 = e.Where(x => x > 1);
- A. r1 绑定
Queryable.Where(表达式树),r2 绑定Enumerable.Where(委托),取决于变量的静态类型 - B. 两者都绑定
Enumerable.Where - C. 两者都绑定
Queryable.Where - D. 编译失败,不能对同一对象同时使用两套扩展
查看答案与解析
正确答案A
解析: 扩展方法决议看变量的静态类型:q 是 IQueryable<int>,r1 绑定 Queryable.Where(构建表达式树);e 是 IEnumerable<int>,r2 绑定 Enumerable.Where(编译成委托)。底层对象相同不影响绑定结果。
关键点: Queryable 与 Enumerable 的选择由静态类型决定,与运行时对象无关。
0794 关于 IQueryable 与 LINQ 提供程序,哪项正确?
难度: 实战
- A.
IQueryable.Where立即执行并返回结果集 - B. 查询在构造 IQueryable 时就已执行
- C. IQueryable 承载表达式树,由提供程序翻译后执行,查询延迟到枚举才执行,且不是所有方法都能翻译
- D. 提供程序在编译期校验所有方法调用
查看答案与解析
正确答案C
解析: IQueryable 的核心是“表达式树 + 提供程序翻译 + 延迟执行”:Where 只往树里追加节点,枚举(foreach/ToList/Count 等)时才真正执行;能否翻译取决于提供程序,翻译不了的方法在执行期报错。
关键点: 表达式树与延迟执行是 LINQ 提供程序的两大支柱。
0795 以下代码输出什么?
难度: 实战
IQueryable<int> q = new List<int> { 1, 2, 3 }.AsQueryable();
q = q.Where(x => x > 1);
Console.WriteLine("构建完成");
Console.WriteLine(q.Count());
- A. 输出
构建完成,然后抛NotSupportedException - B. 输出
构建完成与2,Where 只构建表达式树,Count() 才触发执行 - C. 只输出
构建完成,Where 与 Count 都未执行 - D. 输出
2与构建完成
查看答案与解析
正确答案B
解析: Where 只是给表达式树追加节点,不触碰数据;Count() 触发执行并返回 2,因此先输出“构建完成”再输出 2。顺序本身也说明 Where 没有立即执行。
关键点: 构建查询不执行,枚举/聚合才执行。
0796 关于表达式树的数据结构特性,哪项正确?
难度: 进阶
- A. 表达式树是不可变数据结构,需要改造时必须用 ExpressionVisitor 重建新树
- B. 表达式树节点创建后可以原地修改
- C. 表达式树等价于已编译的委托
- D. 表达式树只能表示语句块
查看答案与解析
正确答案A
解析: 表达式树的所有节点创建后不可变,想要变换(如把某个节点换成另一个表达式)必须用 ExpressionVisitor 遍历并重建新树。
关键点: 不可变 + 可遍历是表达式树变换的基础。
0797 以下代码能否编译?
难度: 进阶
Expression<Func<Task<int>>> e = async () => 1;
- A. 编译通过,运行时返回 1
- B. 编译通过,返回 null
- C. 编译通过,运行时抛
NotSupportedException - D. 编译失败,表达式树不能包含异步操作
查看答案与解析
正确答案D
解析: async lambda 会生成状态机,无法用表达式树节点表达,编译器报“表达式树不能包含异步操作”。
关键点: async 与表达式树不兼容。
0798 以下代码输出什么?
难度: 实战
dynamic obj = new ExpandoObject();
obj.Name = "Tom";
Console.WriteLine(obj.Name);
- A. 编译失败
- B. 抛
RuntimeBinderException,ExpandoObject 不支持动态添加成员 - C. 输出
Tom,ExpandoObject 通过 IDynamicMetaObjectProvider 接受动态成员 - D. 输出空行
查看答案与解析
正确答案C
解析: ExpandoObject 实现 IDynamicMetaObjectProvider,运行时成员的新增、读取与删除都由它接管;动态绑定给 obj 添加 Name 属性后,读取输出 Tom。
关键点: IDynamicMetaObjectProvider 让对象可以在运行时拥有可变成员集合。
0799 以下代码输出什么?
难度: 实战
var param = Expression.Parameter(typeof(C), "c");
var getter = Expression.Lambda<Func<C, int>>(
Expression.Property(param, nameof(C.Value)), param).Compile();
var c = new C { Value = 7 };
Console.WriteLine(getter(c));
class C { public int Value { get; set; } }
- A. 输出
0 - B. 输出
7,表达式树把属性访问编译成强类型委托 - C. 抛
MissingMethodException - D. 编译失败
查看答案与解析
正确答案B
解析: 表达式树把属性读取编译为强类型委托,调用 getter 相当于直接调用属性 getter,输出 7。这是用表达式树替代反射 GetValue、避免反射开销的典型用法。
关键点: Compile 出的委托是强类型、可直接调用的。
0800 以下代码输出什么?
难度: 实战
dynamic d = new[] { 1, 2, 3 };
Console.WriteLine(d[1]);
- A. 输出
2,索引器访问也参与动态绑定 - B. 编译失败,dynamic 不支持索引器
- C. 抛
RuntimeBinderException - D. 输出
1
查看答案与解析
正确答案A
解析: 索引器访问是完整的动态绑定操作:运行时按真实类型 int[] 解析索引器并取值,输出 2。
关键点: dynamic 支持索引器、运算、转换等全套绑定操作。