1. 面试官问这道基础题到底想考察什么is和as是 C# 里每天都会碰到的两个操作符也是面试中出现频率极高的 C# 基础题。我面试别人时经常拿这道题开场原因很简单它能一次性筛掉三种候选人——只会背概念的、只会用但不理解本质的、以及真正写过大量代码、踩过类型转换各种坑的人。所以这道题表面考语法实际考的是你对 C# 类型系统的理解深度。1.1 表面问题是语法深层问题是类型安全意识绝大多数候选人能答出第一层is返回布尔值判断对象是否兼容于某个类型as返回转换后的对象不兼容时返回null。这没错但只是及格线。面试官真正想听的是第二层甚至第三层为什么as不会抛异常而强转会抛出InvalidCastException为什么as不能用于值类型is和模式匹配之间是什么关系is T t和is加as连写哪个性能更好这些问题的背后是日常开发中非常现实的需求——我们几乎每天都在处理拿到的对象到底是不是我想要的类型这件事。类型判断做不对轻则逻辑分支走错重则直接NullReferenceException或InvalidCastException崩掉。所以说白了面试官考察的是你有没有把类型安全这件事刻进骨子里。能讲清楚is和as的区别说明你对 C# 的运行时类型系统有过真正思考。1.2 贴一层底层视角两个操作符对应两条 IL 指令C# 编译后的 IL 层面is操作符对应isinst指令强转类型转换对应castclass指令。而as在绝大多数情况下也是isinst指令。这里有个非常关键的低层差异isinst检查对象是否兼容目标类型兼容就返回对象本身不兼容就返回null不会抛异常。castclass不兼容时直接抛出InvalidCastException。这就是is不抛异常、as也不抛异常、而强转会抛异常的根本原因。理解了这一层你再看as的官方文档就会明白as的本质就是一句安全版的类型转换。面试时能说出这一层基本就把这道题从背诵题提升成原理题了。后面我会结合具体代码再展开因为光知道指令名还不够还得知道它们在实际代码里怎么影响你的选择。2. is 操作符不只是判断更是 C# 模式匹配的入口很多初学者对is的理解停留在判断某个对象是不是某个类型这个理解太狭隘了。C# 7.0 之后is被大幅强化成了模式匹配语法的基石。现在的is能干的事情远多于早年版本面试时如果能展示出这部分知识是明显的加分项。2.1 三种常见写法从类型检查到变量声明先看最基础的用法object obj GetSomeObject(); // 写法一只判断类型C# 1.0 就有 if (obj is string) { Console.WriteLine(是字符串); } // 写法二判断类型并声明变量C# 7.0 引入 if (obj is string str) { Console.WriteLine($是字符串内容是 {str}); } // 写法三判断类型并同时验证条件C# 7.0 模式组合 if (obj is string str2 str2.Length 5) { Console.WriteLine($超过5个字符内容是 {str2}); }写法二非常实用它把类型检查和安全取值合并到了一步。在if (obj is string str)这个条件成立的分支里str已经是一个非空的string变量可以直接安全使用。这意味着你不再需要这样写老代码// 老写法先判断再强转啰嗦且有隐患 if (obj is string) { var str (string)obj; // 这里理论上是安全的但要写两遍 obj }我自己的经验是写完if (obj is string)再在里面强转这种代码一旦中间的obj被替换成复杂表达式就很容易出问题。而is string str把变量声明和类型检查绑定在一起更安全也更简洁。日常开发中我几乎不用老写法。2.2 is 与模式匹配家族的联动property pattern、relational pattern、switchC# 7.0 之后is支持的远不止类型模式。可以配合属性模式、关系模式做更精细的判断if (obj is string { Length 0 }) { Console.WriteLine(非空字符串); } if (obj is int i and 0 i 100) { Console.WriteLine(0到100之间的整数); } // 结合 switch 表达式C# 8.0 var description obj switch { int n when n 0 正整数, int n when n 0 负整数, string s $字符串:{s}, _ 其他 };这里有个重要的概念升级is已经从类型判断操作符变成了模式匹配操作符。面试时能提到is int i and 0、obj switch { ... }这层说明你对现代 C# 特性跟得紧。实际开发中的常见场景是接收基类对象需要根据子类类型和属性走不同分支。用老式if-else 强转能写但用is加属性模式会清晰很多代码评审也容易过。2.3 一个容易忽略的细节模式变量的作用域用is T t声明了模式变量后这个变量的作用域需要特别注意。if (obj is string str)中str的作用域是if语句及其所有分支块但在 else 分支里不可用更准确地说编译器会确认str在 else 分支中是未赋值状态definitely assigned 规则。看下面这段if (obj is string str) { Console.WriteLine(str); // OK } else { // 这里不能用 str编译器会报使用了未赋值的变量 } // 出了 if 块str 也不可用了还有一个常见误区就是模式变量可能会造成变量遮蔽string str 外部; object obj 内部; if (obj is string str) // 编译错误不能在此作用域声明同名变量 { }这个和普通局部变量的作用域规则是相通的但初学者经常踩。我的建议是模式变量的命名要么有意义且固定比如userDto、configValue要么前面加个前缀避免和外层变量冲突。代码评审里看到过有人为此纠结半天其实提前约定好命名习惯就能避免。3. as 操作符安全转换的正确打开方式但有明确的适用范围as的核心语义是尝试转换失败就给我null别给我抛异常。它的好处极其直观不会让程序因为类型不匹配直接崩掉让开发者可以把类型不匹配作为正常分支来处理。但它有三条硬边界只适用于引用类型和可空值类型不能用于不可空值类型也不能用于无继承关系的类型之间。3.1 什么时候可以用 as什么时候编译器都不让你用看代码object obj hello; // 正确引用类型转换 string s obj as string; if (s ! null) { Console.WriteLine(s); } // 正确可空值类型转换 object numObj 42; int? num numObj as int?; if (num.HasValue) { Console.WriteLine(num.Value); } // 编译错误不能对不可空值类型使用 as // int i obj as int; // CS0039 错误 // 运行时返回 null没有继承关系的引用类型 object other new MemoryStream(); string notString other as string; // 编译通过但运行结果是 null最后一行是关键。MemoryStream和string都是引用类型object到string的as在编译器看来是合法的都继承自 object运行时发现MemoryStream不是string就返回null。不会抛异常但你必须做好判空否则下一步调用notString.ToUpper()就直接NullReferenceException了。为什么as不能用于不可空值类型因为值类型如int、bool天然不能存null而as的设计就是失败返回 null。一个int类型变量不可能持有 null所以用as去处理int在语义上就说不通。你想对值类型做安全转换要用可空版本int?或者干脆用is。3.2 as 和强转cast怎么选一条操作性很强的判断标准面试时我经常追问一个问题as不抛异常那我是不是应该把所有强转都改成as答案是不能。as会掩盖真正的逻辑错误强转会暴露问题。判断标准就一条类型不匹配在你的业务逻辑里是常态分支还是异常情况如果是常态分支比如多态分发时对象可能是A也可能是B用as加判空干净利落。如果是异常情况比如某个接口契约本该返回string却返回了别的用强转(string)result让它立刻抛InvalidCastException快速暴露上游 bug。举个例子我在做 C# 上位机软件开发时经常从第三方控件或通讯库里拿到object类型的数据包。这个包的预期类型是明确的通讯协议定义了这时候我倾向于强转因为一旦类型不对说明协议解析或数据封装出了问题应该立刻崩溃定位而不是默默拿到null然后诡异地在后面某行才炸。反过来如果是从一个异构集合里提取元素元素可能是多种类型之一我就用as加分支处理。3.3 底层视角再看 as失败成本低但别滥用前面说as对应isinst指令在这里需要补充一个重要细节as操作符的运行时开销其实就一次类型检查加一次引用复制。它不产生装箱、不调用用户自定义转换运算符、不进入异常处理流程。所以在性能敏感的代码里as通常比 try-catch 包裹的强转要快得多。但也有一种被我反复强调的反模式用as做调用链里的多次转换即频繁出现obj as A然后obj as B然后obj as C。每次as都是一次独立的类型检查如果这个obj在逻辑上必须同时满足多个类型约束说明设计有问题应该考虑用接口或抽象类统一而不是靠多次as去推导。代码评审阶段看到这种写法我第一反应不是改操作符而是建议重新审视对象模型。4. is 和 as 实战对比什么时候用哪个本质是需求决定选择这一节是本文的核心干货。在开发中is和as经常是二选一的问题但选择不是凭喜好。我需要给出一个完整的对比框架帮大家在做技术决策时有据可循。4.1 一句话感觉太浅直接上一张对比表维度isas返回值bool转换后的对象失败为 null是否会抛异常不会类型不匹配返回 false不会类型不匹配返回 null适用类型所有类型含值类型、可空类型仅引用类型和可空值类型底层指令isinst类型兼容性检查isinst类型兼容性检查并返回引用是否支持模式匹配支持类型模式、属性模式、关系模式等不支持只做类型转换是否声明变量支持is T t声明模式变量是通过返回结果变量对 null 的处理null is T返回 falsenull is null返回 truenull as T返回 null典型使用场景分支判断、switch 表达式、模式匹配转换后需要判空再安全使用的场景这张表是我给团队新人讲的版本。它能直观说明两者差异但表格毕竟偏抽象下面用真实代码场景来强化理解。4.2 经典场景一判断 使用用 is 最省事这是最常见的场景你有一个object先判断类型再在分支里使用。C# 7.0 之前代码通常是is判断 强转。C# 7.0 之后直接用is T t一步完成安全性和可读性都更好// 推荐写法is 模式声明变量 if (data is Order order) { ProcessOrder(order); } // 反面写法is 判断 as 转换多此一举 if (data is Order) { var order data as Order; // as 理论上不会失败但开发者被迫写两次类型名 if (order ! null) // 这个判空又是多余检查 { ProcessOrder(order); } }第二种写法不能说错但明显冗余。is判断成功之后再做as必然成功多写的判空分支永远走不到。这种代码看多了会让人怀疑作者对is的模式变量功能不熟。4.3 经典场景二只转换不判断用 as 更直接反过来如果代码流程已经笃定或者允许转换失败并接受 null那么直接用as最简洁// 推荐写法as 加判空 var text obj as string; if (text ! null) { SaveToLog(text); } else { HandleNonString(obj); } // 不那么推荐is 再取值写两遍 if (obj is string s) { SaveToLog(s); } else { HandleNonString(obj); }这里两种写法功能上等价。区别在于你是站在检查的角度还是转换的角度。我通常这么说如果语义重心是有没有可能是 string是就处理不是就走另一条路用is如果语义重心是我要尝试拿到 string拿不到就用 null 占位后续统一判断用as。4.4 经典场景三对值类型做安全判断值类型场景只有is能用除非用可空值类型配as。object value 12345; // 正确is 判断值类型没问题 if (value is int intValue) { Console.WriteLine($整数: {intValue}); } // 想用 as必须用可空值类型 int? nullableInt value as int?; if (nullableInt.HasValue) { Console.WriteLine($整数: {nullableInt.Value}); }as int?这个用法在面试题里出现频率很高。它利用了可空值类型本身是引用类型装箱后的特性将int?当作容器来检查。说实话我从 11 年 C# 写到现在项目代码里as int?用得不算多更多是面试题喜欢考。但考到的时候能说出因为as失败需要返回 null而 int 不能存 null所以要把 int 提升为可空类型 int? 才能用来接收失败结果这道题的加分效果就达到了。4.5 性能视角is 和 as 哪个更快真相是别纠结在很多面试题解析里会看到is比as快或者as比强转快这类结论很容易误导人。我实测过多次结论是单次类型匹配的开销差异在纳秒级别对于绝大多数业务代码来说不值得为了性能去选择某一个操作符。真正值得关注的是那些会放大开销的写法obj is string然后又(string)obj做了两次类型检查浪费但可忽略在循环里对同一个object反复做is/as而结果不变属于可以提到循环外的一次性判断用try { (string)obj } catch替代as异常分支会带来极高的开销——异常处理的成本远高于类型检查这一点才是性能上真正该避开的。所以我的建议是性能优化上优先关注算法和异常路径而is和as的选择首先从语义和可读性出发。面试官如果追问性能你就把上面三点摆出来比背数字靠谱。5. 这些坑我代码评审里见过不下十次具备多年经验以后我意识到真正体现 is 和 as 区别 价值的不是背定义而是在实际代码里避开那些隐藏在细节中的雷。下面我把自己踩过或帮别人兜住的坑整理一下。5.1 as 之后不判空下一个方法调用直接崩这个坑出现频率极高。很多人只记得as不会抛异常但忘了as失败时会返回null。拿到null之后紧接着调用成员就是典型的NullReferenceExceptionobject obj new MemoryStream(); var text obj as string; Console.WriteLine(text.Length); // 一旦 text 为 null这里直接崩这类崩溃最烦人的一点是崩溃地点不在as这行而在后面某个调用处。排查时要沿着调用链往回找浪费时间。我自己的习惯是使用as的下一行一定是判空中间不插任何其他代码。这属于写得多了、吃过亏之后形成的肌肉记忆。5.2 用 is 判断 null居然有性能和语义双坑is的一个常见写法是if (obj is null)。这在 C# 7.0 之后完全合法语义上等同于obj null。可很多老代码里会写if (obj is string false)或if (!(obj is string))这在语义上没问题但有另一个隐藏问题——当obj是值类型时is触发的装箱可能带来额外开销。来看这段int x 100; if (x is int) // 对值类型执行 is 会装箱白白浪费 { Console.WriteLine(始终为 true); }虽然这里的结果始终为 true但装箱发生在堆分配层面即使后续没有使用也造成了无谓的开销。正确做法是直接用x is int可省略或者用typeof(int)判断。真实业务里这种代码多半是从object接收参数时写出来的不是完全没意义但我建议团队规范里明确约定能用泛型或直接类型匹配的就不要写x is int这种永远为真的判断——它只会让人觉得你是从别处复制过来的。5.3 as 遇到不可空值类型编译器直接报错但运行时的坑更隐蔽as用于不可空值类型在编译期就会被拦住这一点反而保护了开发者。真正隐蔽的坑发生在泛型场景public void ProcessT(object obj) { // 这里 T 可能是值类型也可能是引用类型 // 不能直接 obj as T编译器会报错 // 常见替代方案先判断 typeof(T) }泛型里用as T是不合法的因为编译器在编译泛型类时不知道T是引用类型还是值类型。正确的写法是if (obj is T typedValue) { // T 是值类型时is 也能正确判断obj 是值类型装箱后的盒子也能匹配 }这个is T写法在泛型场景下非常实用它同时兼容引用类型和值类型避免了as T的编译限制。我在写通用的数据解析器、事件参数转换器时经常用这个技巧。面试时能提到这一点说明你真的处理过泛型和反射相关的场景。5.4 is 和 as 都解决不了的问题自定义隐式转换运营商这一点一定要提醒is和as作用于运行时类型而不是编译时的隐式类型转换。举个例子public class MyNumber { public int Value { get; set; } public static implicit operator int(MyNumber n) n.Value; } object obj new MyNumber { Value 100 }; if (obj is int intValue) // falseobj 运行时类型是 MyNumber不是 int { } int direct (int)obj; // 这也会抛 InvalidCastException因为运行时类型不是 int这里类定义了到int的隐式转换运算符但is和as不会触发这个转换只有编译期强转会用到。因为is和as是运行时类型检查不是编译期类型转换。如果obj是MyNumber想拿到里面的int应该先转MyNumber再取Valueif (obj is MyNumber n) { int realValue n.Value; }这类问题在反射、序列化、动态加载场景里很容易出现。理解is/as只看运行时类型这一点能从根上避免踩进去。6. 我这个老 C# 选手的真实心得写is和as相关的代码已经十几年我的习惯可以总结成三句话第一在需要判断类型的场景里优先用is T t模式声明它让你的代码同时完成类型检查和安全取值可读性最好也最不容易在判断和取值之间被其他逻辑打断。第二在需要容忍失败的转换场景里as加立即判空是王道但判空要养成肌肉记忆否则迟早被NullReferenceException教育。第三遇到值类型和泛型别硬套引用类型的脑回路is是你的唯一选择as int?只是特殊技巧不是常规手段。最后再分享一个面试小技巧如果面试官问这道题你可以反问一句您是指传统用法还是考虑模式匹配的现代用法——这一句就能让对方知道你不只会背操作符而是真的理解 C# 类型系统这些年来的演进。当然前提是你真读懂了这篇的内容否则反问只会暴露得更快。
阅读完成 · 觉得有帮助?