C# 试题 43:比较、格式化与解析
0841 以下代码输出什么?
难度: 实战
var list = new List<Point> { new Point { X = 1 } };
Console.WriteLine(list.Contains(new Point { X = 1 }));
struct Point : IEquatable<Point>
{
public int X;
public bool Equals(Point other) => X == other.X;
}
- A. 编译失败,struct 不能实现 IEquatable
- B. 输出
False - C. 输出
True,List<T>.Contains 通过EqualityComparer<T>.Default 调用IEquatable<T>.Equals - D. 抛
InvalidOperationException
查看答案与解析
正确答案C
解析: EqualityComparer<T>.Default 在 T 实现 IEquatable<T> 时优先使用它,List<T>.Contains 因此走 Point.Equals(Point),对值类型无需装箱,两个 X=1 的 Point 相等,输出 True。
关键点: 泛型容器默认比较优先走 IEquatable<T>。
0842 以下代码输出什么?
难度: 进阶
var a = new Person { Name = "Ada" };
var b = new Person { Name = "Ada" };
var list = new List<Person> { a };
Console.WriteLine(list.Contains(b));
Console.WriteLine(ReferenceEquals(a, b));
class Person : IEquatable<Person>
{
public string Name = "";
public bool Equals(Person? other) => other is not null && Name == other.Name;
}
- A. 输出
True与False,Contains 走IEquatable<T>,而引用比较仍基于 ReferenceEquals - B. 输出
False与False - C. 输出
True与True - D. 编译失败,未重写 Equals(object)
查看答案与解析
正确答案A
解析: 类实现 IEquatable<T> 后,EqualityComparer<T>.Default 在 Contains 中调用它,因此两个 Name 相同的实例相等;但 ReferenceEquals 依然为 False。若同时用到 HashSet/Dictionary,还必须重写 GetHashCode,否则哈希结构行为错乱。
关键点: IEquatable<T> 与引用比较可以并存且结论不同。
0843 以下代码输出什么?
难度: 进阶
var a = new Person { Name = "Ada" };
var b = new Person { Name = "Ada" };
Console.WriteLine(a == b);
class Person : IEquatable<Person>
{
public string Name = "";
public bool Equals(Person? other) => other is not null && Name == other.Name;
}
- A. 输出
True,IEquatable 让 == 生效 - B. 输出
True,字符串相同所以 == 为 true - C. 编译失败
- D. 输出
False,== 默认做引用比较,实现IEquatable<T>不会自动重载 ==
查看答案与解析
正确答案D
解析: IEquatable<T>、Equals(object) 与 operator == 是三个独立的契约:只实现 IEquatable 不会自动生成 ==,自定义类型默认的 == 是引用相等,因此两个不同实例比较为 False。
关键点: 需要 == 必须显式重载,IEquatable 不会代劳。
0844 以下代码输出什么?
难度: 进阶
string a = "a";
string? b = null;
Console.WriteLine(a.CompareTo(b));
Console.WriteLine(b.CompareTo(a));
- A. 输出
1与-1 - B. 输出
1,然后抛NullReferenceException;任意非 null 实例与 null 比较时大于 null,而 null 上调用实例方法抛 NRE - C. 输出
0与0 - D. 抛
ArgumentNullException
查看答案与解析
正确答案B
解析: IComparable<T>.CompareTo 的契约规定“任意非 null 实例大于 null”,所以 a.CompareTo(null) 返回 1;但 b 本身是 null,调用其实例方法 CompareTo 时抛 NullReferenceException。
关键点: “与 null 比较”和“在 null 上调用”是两种完全不同的结果。
0845 以下代码输出什么?
难度: 实战
var items = new List<(int K, string V)> { (1, "a"), (1, "b"), (0, "c") };
items.Sort((x, y) => x.K.CompareTo(y.K));
Console.WriteLine(string.Join(",", items.Select(i => i.V)));
- A. 一定是
c,a,b - B. 一定是
c,b,a - C. 可能是
c,a,b或c,b,a,List<T>.Sort 不保证稳定排序 - D. 编译失败
查看答案与解析
正确答案C
解析: List<T>.Sort 使用不稳定的排序算法,键相同时(两个 K=1)元素的相对顺序不保证;需要稳定排序应使用 LINQ 的 OrderBy(稳定排序)。
关键点: Sort 不稳定、OrderBy 稳定,需要保持原顺序时别用 Sort。
0846 以下代码输出什么?
难度: 实战
var items = new List<Item> { new Item { V = 2 }, new Item { V = 1 } };
items.Sort();
Console.WriteLine(items[0].V);
class Item : IComparable<Item>
{
public int V;
public int CompareTo(Item? other) => other is null ? 1 : V.CompareTo(other.V);
}
- A. 输出
1,Sort 默认使用Comparer<T>.Default,走IComparable<T> - B. 输出
2 - C. 抛
ArgumentException - D. 编译失败
查看答案与解析
正确答案A
解析: List<T>.Sort() 默认比较器是 Comparer<T>.Default,它优先使用 IComparable<T>.CompareTo,因此按 V 升序排列后第一项是 V=1。
关键点: 排序默认走 IComparable<T>,而不是 Equals。
0847 以下代码输出什么?
难度: 进阶
var list = new List<Item> { new Item { V = 1 }, new Item { V = 2 }, new Item { V = 3 } };
Console.WriteLine(list.BinarySearch(new Item { V = 2 }) >= 0);
class Item : IComparable<Item>
{
public int V;
public int CompareTo(Item? other) => other is null ? 1 : V.CompareTo(other.V);
public override bool Equals(object? obj) => false;
public override int GetHashCode() => V;
}
- A. 输出
False,Equals 不一致导致找不到 - B. 抛
InvalidOperationException - C. 编译失败
- D. 输出
True,BinarySearch 只依赖比较器(CompareTo),不调用 Equals
查看答案与解析
正确答案D
解析: List<T>.BinarySearch 只使用 Comparer<T>.Default(即 CompareTo)做比较,从不调用 Equals;即使 Equals 恒为 false,只要 CompareTo 一致就能命中。反过来也说明:CompareTo 与 Equals 不一致会让排序/查找与哈希结构的语义分裂。
关键点: BinarySearch 与 Equals 无关,契约一致性要自己保证。
0848 以下代码输出什么?
难度: 实战
bool ok = int.TryParse("0x1F", out int n);
Console.WriteLine(ok + " " + n);
- A. 输出
True 31 - B. 输出
False 0,默认 NumberStyles.Integer 不含 AllowHexSpecifier - C. 输出
False 31 - D. 抛
FormatException
查看答案与解析
正确答案B
解析: int.TryParse 默认使用 NumberStyles.Integer,它不允许 0x 前缀,解析失败返回 false,out 参数置为 0。要解析十六进制必须显式传入 NumberStyles.HexNumber 或 AllowHexSpecifier。
关键点: 十六进制不是默认行为,别指望 TryParse 自动识别 0x。
0849 以下代码输出什么?
难度: 进阶
var de = CultureInfo.GetCultureInfo("de-DE");
Console.WriteLine(decimal.Parse("1,5", NumberStyles.Float, de));
Console.WriteLine(decimal.Parse("1.5", NumberStyles.Float, de));
- A. 两行都输出
1.5 - B. 输出
1.5与15 - C. 输出
1.5,然后抛FormatException;de-DE 的小数分隔符是逗号,点号不是合法小数符号 - D. 两行都抛
FormatException
查看答案与解析
正确答案C
解析: 解析必须使用当前文化的符号表:de-DE 的小数分隔符是 ,,第一行成功得到 1.5;第二行的 . 在 de-DE 是千分位分隔符,而 NumberStyles.Float 不含 AllowThousands,故抛 FormatException。
关键点: 解析数字必须显式指定或确认文化,跨文化解析是最常见的隐晦 bug。
0850 以下代码输出什么?
难度: 实战
try
{
int.Parse("12x");
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().Name);
}
Console.WriteLine(int.TryParse("12x", out _));
- A. 输出
FormatException与False,同一输入 Parse 抛异常而 TryParse 返回 false - B. 输出
FormatException与True - C. 输出
OverflowException与False - D. 输出
ArgumentNullException与False
查看答案与解析
正确答案A
解析: Parse 失败抛异常,TryParse 失败只返回 false 不抛异常,这是两者最核心的契约差异;非法格式抛 FormatException,超出范围才抛 OverflowException。
关键点: Parse 用异常表达失败,TryParse 用返回值表达失败。
0851 对 int 值做字符串插值 $"{value}" 时,编译器生成的 DefaultInterpolatedStringHandler 通过哪个接口避免中间字符串分配?
难度: 进阶
- A. IFormattable.ToString
- B. object.ToString
- C. Convert.ToString
- D. ISpanFormattable.TryFormat
查看答案与解析
正确答案D
解析: 插值处理程序优先调用 ISpanFormattable.TryFormat 把值直接格式化进目标缓冲区(Span),避免先 ToString 产生中间字符串再复制。这正是值类型实现 ISpanFormattable 的收益。
关键点: 高性能格式化路径走 TryFormat,不走 ToString。
0852 以下代码输出什么?
难度: 实战
var buffer = new char[2];
bool ok = 12345.TryFormat(buffer, out int written);
Console.WriteLine(ok + " " + written);
- A. 输出
True 2 - B. 输出
False 0,缓冲区不足时 TryFormat 返回 false 且 charsWritten 置 0,而不是抛异常 - C. 输出
False 5 - D. 抛
ArgumentException
查看答案与解析
正确答案B
解析: ISpanFormattable.TryFormat 的契约是:目标缓冲区放不下时返回 false 并把 charsWritten 置 0,由调用方扩容后重试;12345 需要 5 个字符,2 个字符的缓冲区放不下。
关键点: TryFormat 用返回值表达“缓冲区不够”,不抛异常。
0853 以下代码输出什么?
难度: 进阶
var utf8 = new byte[16];
bool ok = 12345.TryFormat(utf8, out int bytesWritten);
Console.WriteLine(ok + " " + bytesWritten);
- A. 输出
True 10,每个数字占 2 字节 - B. 输出
False 0,byte 重载不存在 - C. 输出
True 5,.NET 8+ 提供 byte(UTF-8)TryFormat 重载,每个 ASCII 数字占 1 字节 - D. 编译失败
查看答案与解析
正确答案C
解析: 自 .NET 8 起数值类型提供 TryFormat(Span<byte>, out int, ...) 的 UTF-8 重载,直接把数字写成 UTF-8 字节;12345 是 5 个 ASCII 字符,占 5 字节,输出 True 5。
关键点: char 版本的计数单位是字符,byte 版本是字节。
0854 以下代码输出什么?
难度: 实战
var buffer = new char[8];
bool ok = 255.TryFormat(buffer, out int written, "X");
Console.WriteLine(ok + " " + buffer[..written].ToString());
- A. 输出
True FF,格式说明符 X 生成大写十六进制 - B. 输出
True 255 - C. 输出
False 0 - D. 编译失败,TryFormat 不支持格式说明符
查看答案与解析
正确答案A
解析: TryFormat 的第三个参数是格式说明符,与 ToString("X") 一致:255 用 X 格式化为大写十六进制 FF,8 个字符的缓冲区足够。
关键点: TryFormat 支持格式说明符,机制与 ToString 相同。
0855 以下代码输出什么?
难度: 实战
var style = NumberStyles.Integer | NumberStyles.AllowThousands;
bool ok = int.TryParse("1,000", style, CultureInfo.InvariantCulture, out int n);
Console.WriteLine(ok + " " + n);
- A. 输出
False 0 - B. 输出
True 1 - C. 抛
FormatException - D. 输出
True 1000,AllowThousands 允许千分位分隔符
查看答案与解析
正确答案D
解析: 默认的 NumberStyles.Integer 不含 AllowThousands,"1,000" 解析失败;加上该标志并在 InvariantCulture(逗号是千分位符)下解析,得到 1000。
关键点: 千分位是显式开启的样式,不是默认能力。
0856 关于 Dictionary 与 SortedDictionary 的键判定,哪项正确?
难度: 进阶
- A. 两者都用 Equals 判断键
- B. Dictionary 用 GetHashCode+Equals(默认走
IEquatable<T>),有序集合用Comparer<T>.Default(走IComparable<T>),两者的“相等”标准可以不同 - C. 两者都用 CompareTo
- D. 两者都要求键实现 IComparable
查看答案与解析
正确答案B
解析: Dictionary/HashSet 的键判定基于哈希与相等性(默认 EqualityComparer<T>.Default → IEquatable<T>);SortedDictionary/SortedSet 基于排序(Comparer<T>.Default → IComparable<T>)。两个“相等”标准在实现不一致时会得出不同结论。
关键点: 无序集合与有序集合对“同一个键”的判定标准不同。
0857 以下代码输出什么?
难度: 进阶
double nan = double.NaN;
Console.WriteLine(nan.CompareTo(1.0));
Console.WriteLine(nan < 1.0);
- A. 输出
1与True - B. 输出
0与False - C. 输出
-1与False,IComparable 把 NaN 视为小于任何值,而 IEEE 比较运算符对 NaN 恒为 false - D. 输出
-1与True
查看答案与解析
正确答案C
解析: Double.CompareTo 遵循 IComparable 契约:NaN 小于任何非 NaN 值(包括与自身比较为 0),所以返回 -1;而 < 等 IEEE 754 运算符对 NaN 的任何比较都为 false。同一数值在两条路径下语义相反。
关键点: CompareTo 与比较运算符对 NaN 的语义不同,排序 NaN 前要先定好规则。
0858 以下代码输出什么?
难度: 实战
string a = "a";
string b = "A";
Console.WriteLine(a == b);
Console.WriteLine(string.Compare(a, b, StringComparison.Ordinal));
- A. 输出
False与1,== 是大小写敏感的序数比较,Ordinal 下 ‘a’(97) 大于 ‘A’(65) - B. 输出
True与0 - C. 输出
False与-1 - D. 输出
True与1
查看答案与解析
正确答案A
解析: string == 做的是大小写敏感的序数相等比较,"a" != "A";Compare 按 Ordinal 比较码点,'a'(0x61) 大于 'A'(0x41),返回正数 1。很多人凭直觉以为小写“应该排在前面”而猜 -1。
关键点: Ordinal 比较按码点大小,‘a’ > ‘A’。
0859 以下代码输出什么?
难度: 进阶
static void Write<T>(T value) where T : ISpanFormattable
{
var buffer = new char[32];
bool ok = value.TryFormat(buffer, out int written, default, null);
Console.WriteLine(ok + " " + written);
}
Write(12345);
- A. 编译失败,ISpanFormattable 不能作为泛型约束
- B. 输出
True 10 - C. 输出
False 0 - D. 输出
True 5,泛型约束下值类型以受限调用调用 TryFormat,不装箱
查看答案与解析
正确答案D
解析: where T : ISpanFormattable 让编译器用受限调用(constrained call)直接调用值类型的 TryFormat,不装箱;12345 格式化 5 个字符,输出 True 5。若不加约束而把值转成接口引用,就会装箱并可能退化为 ToString 路径。
关键点: 泛型约束是避免值类型装箱调用接口方法的关键。
0860 自定义值类型实现 ISpanFormattable 而非只重写 ToString,主要收益是什么?
难度: 进阶
- A. 没有任何收益,只是多实现一个接口
- B. 插值、拼接与 UTF-8 格式化路径可以复用 TryFormat,避免中间字符串分配与复制
- C. 让类型支持 JSON 序列化
- D. 让类型可以放进 Dictionary
查看答案与解析
正确答案B
解析: 实现 ISpanFormattable.TryFormat 后,字符串插值、StringBuilder.Append 的 span 路径以及 UTF-8 格式化都能直接写入目标缓冲区,省去 ToString 的中间字符串分配;这在高频格式化场景收益明显。
关键点: TryFormat 是“零分配格式化”的入口。