1. is 操作符:类型兼容性检查
在实际项目里,经常会遇到你拿到一个基类对象的引用,但你知道或者怀疑它其实是某个更具体的子类。这种情况下,你可能需要访问子类独有的成员。为了安全又方便地处理这种情况,C#有几个关键工具:is、as操作符,还有现代C#里很强大的pattern matching(模式匹配)功能。
is可以检查一个对象是不是指定类型的实例、它的子类型,或者实现了某个接口。如果对象和这个类型兼容,它会返回true,否则就是false。
主要用途:判断能不能安全地把对象转换成某个类型。
例子:检查基类和继承关系
class Animal { public string Species { get; set; } = "未知"; }
class Dog : Animal { public void Bark() => Console.WriteLine("汪!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("喵!"); }
Animal myAnimal = new Dog { Species = "金毛寻回犬" };
Console.WriteLine($"myAnimal is Animal: {myAnimal is Animal}"); // True
Console.WriteLine($"myAnimal is Dog: {myAnimal is Dog}"); // True
Console.WriteLine($"myAnimal is Cat: {myAnimal is Cat}"); // False
在这个例子里,myAnimal实际上是Dog。所以myAnimal is Animal和myAnimal is Dog都会返回true。
例子:检查null
is操作符和null一起用的时候表现也很直观。
Animal nullAnimal = null;
Dog specificDog = new Dog();
Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (null不是任何类型的实例)
Console.WriteLine($"specificDog is null: {specificDog is null}"); // False (对象不是null)
注意,null不是任何类型的实例,所以null is MyType永远是false。不过someObject is null是个很方便又安全的方式,判断引用是不是null。
例子:is和接口一起用
is也可以用来检查对象有没有实现某个接口。
interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("扑棱扑棱!"); }
class Fish : Animal { }
Animal creature = new Bird();
Console.WriteLine($"creature is Bird: {creature is Bird}"); // True
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // True
creature = new Fish();
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // False
2. as 操作符:安全类型转换
as操作符用来安全地把对象转换成指定类型。和直接强制转换(Type)obj不一样,后者失败会抛InvalidCastException,as如果转换不了就返回null。所以当你不确定对象真实类型时,这个操作符特别好用。
主要用途:尝试转换类型,如果失败就得到null。
例子:as的基本用法
class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Square : Shape { public double Side { get; set; } }
Shape myShape = new Circle { Radius = 5.0 };
// 尝试转换成Circle
Circle circle = myShape as Circle;
if (circle != null) // 一定要检查null!
{
Console.WriteLine($"这是一个半径为: {circle.Radius} 的圆"); // 输出: 这是一个半径为: 5 的圆
}
// 尝试转换成Square
Square square = myShape as Square;
if (square == null) // 转换失败,square == null
{
Console.WriteLine("这不是正方形。"); // 输出: 这不是正方形。
}
可以看到,as能帮你避免运行时错误,转换失败时只会返回null,不会抛异常。
例子:as的限制
要注意,as只能用在引用类型和nullable值类型上。普通值类型不能用,因为它们不能赋值为null。
object someValue = 100;
int num = someValue as int; // 编译错误:'as'不能用于非null类型
int? nullableNum = someValue as int?; // 可以,nullable int
Console.WriteLine($"Nullable int: {nullableNum}"); // 输出: Nullable int: 100
string str = someValue as string; // 返回null,因为100不是字符串
Console.WriteLine($"String from int: {str ?? "null"}"); // 输出: String from int: null
对于非nullable值类型,如果你确定类型(并且能接受异常),可以用强制转换((int)someValue),不过更推荐用pattern matching。
3. Pattern Matching(模式匹配)
Pattern matching是个很强大、还在不断进化的功能,能让你更优雅、更安全地检查类型和提取对象里的数据。它能大大减少模板代码,让代码更好读。
Type Pattern(类型模式)和is
这是pattern matching最常用的形式,可以检查对象类型,如果成功,还能直接把它赋值给一个新变量。
语法: 表达式 is 类型 变量
例子:替代is+强制转换
// 继续用Shape, Circle, Square类
Shape currentShape = new Circle { Radius = 7.5 };
// 以前的啰嗦写法:
if (currentShape is Circle)
{
Circle c = (Circle)currentShape;
Console.WriteLine($"旧写法: 圆的半径是 {c.Radius}");
}
// 新的优雅写法,type pattern
if (currentShape is Circle c) // 检查类型并创建变量'c'
{
Console.WriteLine($"新写法: 圆的半径是 {c.Radius}"); // 'c'已经是Circle类型
}
Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
Console.WriteLine($"这是一个边长为 {s.Side} 的正方形");
}
c(或s)变量只在if块里有效,这样就不会在类型不对的时候误用它。
Property Pattern(属性模式)
从C# 8.0开始,你不仅能检查类型,还能同时检查对象的一个或多个属性值,还能把它们提取到新变量里。
语法: 表达式 is 类型 { 属性1: 值模式, 属性2: 提取变量 }
例子:检查属性
Shape testShape = new Circle { Color = "绿色", Radius = 12.0 };
// 检查对象是不是圆,颜色是不是绿色
if (testShape is Circle { Color: "绿色" })
{
Console.WriteLine("找到绿色的圆。");
}
// 检查对象是不是圆,同时提取半径
if (testShape is Circle { Radius: var r })
{
Console.WriteLine($"半径提取出来了: {r}");
}
// 组合检查和提取:
if (testShape is Circle { Color: "绿色", Radius: var radiusVal } circleObj)
{
Console.WriteLine($"提取到绿色的圆。半径: {radiusVal}, 对象: {circleObj.Radius}");
}
属性模式能让复杂的条件判断变得很简单。
例子:属性模式里的范围和逻辑操作符
现在你可以检查对象属性是不是满足某个条件:
Circle bigCircle = new Circle { Radius = 25.0, Color = "蓝色" };
// 检查半径大于20且颜色是蓝色
if (bigCircle is Circle { Radius: > 20, Color: "蓝色" })
{
Console.WriteLine("发现一个很大的蓝色圆。");
}
// 检查半径在(5..15)之间
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
Console.WriteLine("中等大小的圆。");
}
注意! 这里用的是关键字:and、or和not,不是bool类型。
4. Switch表达式和Switch语句里的Pattern Matching
pattern matching在switch语句里最灵活、最易读。你可以根据匹配到的模式执行不同的逻辑。
例子:Switch语句
可以为每个匹配的模式定义代码块。
// Animal, Dog, Cat类和前面一样
Animal currentCreature = new Dog { Species = "贵宾犬" };
switch (currentCreature)
{
case Dog d: // 类型模式:如果是Dog,赋值给'd'
d.Bark();
Console.WriteLine($"这是一只{d.Species}。");
break;
case Cat c when c.Species == "暹罗猫": // 类型模式+条件
c.Meow();
Console.WriteLine($"这是一只暹罗猫。");
break;
case Animal a: // 只是Animal(不是Dog/Cat)
Console.WriteLine($"这只是一个{a.Species}。");
break;
case null: // null模式,处理null值
Console.WriteLine("对象是null。");
break;
default: // 以上都不匹配
Console.WriteLine("未知生物。");
break;
}
switch里的case顺序很重要:越具体的模式要写在越前面。
例子:Switch表达式
这是switch的更简洁写法,会返回一个值。很适合根据对象类型或属性,把它转换成别的值。
语法: 表达式 switch { 模式1 => 结果1, 模式2 => 结果2, ... _ => 默认结果 }
Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "红色" };
string shapeInfo = processShape switch
{
Circle { Radius: var r } when r > 10 => $"大圆 (R={r})", // 属性模式+条件
Circle { Color: "蓝色" } c => $"蓝色圆 (R={c.Radius})", // 属性模式+提取
Circle c => $"普通圆 (R={c.Radius})", // 类型模式
Rectangle { Width: var w, Height: var h } when w == h => $"正方形 ({w}x{h})", // 正方形
Rectangle r => $"矩形 ({r.Width}x{r.Height})", // 其他矩形
null => "图形不存在 (null)", // null模式
_ => "未知图形" // _代替'default'
};
Console.WriteLine(shapeInfo); // 输出: 正方形 (5x5)
switch expression特别适合写简短又直观的转换逻辑。
Var Pattern(var模式)
从C# 7.0开始,你可以在pattern matching里用var。var会匹配任何对象(除了null),并把它赋值给对应类型的变量。
例子:用var Pattern
object obj = "Hello World";
if (obj is var result) // 总是true,result会是string
{
Console.WriteLine($"类型: {result.GetType().Name}, 值: {result}");
}
obj = 123;
string typeName = obj switch
{
var x when x is int => "这是整数",
var y when y is string => "这是字符串",
_ => "别的东西"
};
Console.WriteLine(typeName); // 输出: 这是整数
var Pattern一般不会单独用来检查类型,但在别的模式或switch里提取值很方便。
5. is vs as vs Pattern Matching:啥时候用哪个?
选哪个工具要看你具体需求和C#版本。
is操作符(简单用法):
用法:当你只需要判断对象是不是某个类型,而且后续不需要马上转换或访问特定成员时。
if (myObject is SomeType)
as操作符:
用法:当你想转换引用类型(或nullable值类型),并且想失败时不用抛异常,而是用null判断。
MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }
Pattern Matching(is Type variable):
现代推荐写法:把类型检查和安全转换合成一行,更好读。适用于所有类型。
用法:当你需要检查类型并马上用它的特有成员时。
if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }
Pattern Matching(switch表达式/语句):
现代推荐写法:当你需要根据对象类型、属性或其他特征执行不同操作或得到不同值时。比长长的if-else if链清爽多了。
用法:实现多态行为,或者根据对象特征做复杂选择逻辑。
string GetShapeInfo(Shape s) => s switch
{
Circle c => $"圆 R={c.Radius}",
Rectangle r => $"矩形 W={r.Width} H={r.Height}",
_ => "未知"
};
GO TO FULL VERSION