用于空检查的 "is" 类型模式表达式
The "is" type pattern expression for null check
我可以重构这段代码(流行的 as/null check
模式)
var a = b as MyType;
if(a != null) { ... }
..变成一个不错的 "is" type pattern expression:
if(b is MyType a) { ... }
..这很酷...我觉得...是吗?
不过现在也在考虑重构
var a = SomeMethod();
if(a != null) { ... }
..进入:
if(SomMethod() is MyType a) { ... }
注意:没有 as
和 SomeMethod() 已经 returns MyType。它看起来像(伪代码)if(A is A)
并且很容易混淆,不是吗?
第一个重构是合法的,那么后一个呢?我不是 IL 专家来检查自己,C# 7.0 功能对我来说仍然是新的。或许还有我没有发现的问题?
显然这两个实现非常相似,在内存、分配和周期中差异可以忽略不计.
编译器基本上按如下方式处理它们(对于引用类型)
第一
MyType myType = SomeMethod();
if (myType != null)
{
Console.WriteLine(myType.ToString());
}
第二
MyType myType2;
if ((object)(myType2 = SomeMethod()) != null)
{
Console.WriteLine(myType2.ToString());
}
使用 IL
可能效果更好
第一
IL_0000: ldarg.0
IL_0001: call instance class C/MyType C::SomeMethod()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse.s IL_0015
IL_000a: ldloc.0
IL_000b: callvirt instance string[mscorlib] System.Object::ToString()
IL_0010: call void[mscorlib] System.Console::WriteLine(string)
第二
IL_0015: ldarg.0
IL_0016: call instance class C/MyType C::SomeMethod()
IL_001b: dup
IL_001c: stloc.1
IL_001d: brfalse.s IL_002a
IL_001f: ldloc.1
IL_0020: callvirt instance string[mscorlib] System.Object::ToString()
IL_0025: call void[mscorlib] System.Console::WriteLine(string)
注意 : 可以查看反汇编,IL和jit-asm here
IL 区别基本上是 2 个操作码:
dup
: 复制计算堆栈上当前最顶层的值,然后将副本压入计算堆栈。
Ldloc
: 将特定索引处的局部变量加载到计算堆栈上。
当Jitted时,它很可能优化到相同的指令
总结
- 没有明显的技术差异。
- 是的,我想
is
版本更整洁、更简洁。
- 可能更多的是可打印字符,所以如果你有可打印字符强迫症或者遭受残酷的代码审查,这可能不是一件好事
- 如果你喜欢它并且你的团队也喜欢它,那就去吧。
- 这不是我的菜
我发现编译器非常智能。
is
表达式有多种翻译变体:
if(SomeMethod() is MyType a) {...}
SomeMethod
returns MyType
MyType
没有覆盖运算符 ==,变量 a
未使用
if (SomeMethod() != null) {...}
MyType
有覆盖运算符 ==,但变量 a
未使用
if ((object)(SomeMethod()) != null) {...}
MyType
没有覆盖运算符 ==,使用变量 a
MyType a;
if ((a = SomeMethod()) != null) {...}
MyType
有覆盖运算符 ==,并且使用变量 a
MyType a;
if ((object)(a = SomeMethod()) != null) {...}
SomeMethod
returns 其他类型 object
未使用变量a
if (SomeMethod() is MyType) {...}
MyType
没有覆盖运算符 ==,使用变量 a
MyType a;
if ((a = (SomeMethod() as MyType)) != null) {...}
MyType
有覆盖运算符 ==,并且使用变量 a
MyType a;
if ((object)(a = (SomeMethod() as MyType)) != null) {...}
顺便说一句,您可以通过 ILSpy 或类似工具检查所有这些变体。
我不会用它来对引用类型执行身份转换,因为 null 检查对未来来说更直观 reader。
对于可空类型,情况完全不同。给定struct S
,然后
void foo(S? p)
{
if (p is S s) {
bar(s);
}
}
相当于
void foo(S? p)
{
if (p.HasValue) {
bar(p.GetValueOrDefault());
}
}
并避免 GetValueOrDefault()
调用(或者更糟的是,读取 Value
属性 执行 另一个 空检查)是 IMO非常有用并且显着有助于提高可读性。
在c#8中,使用模式匹配表达式测试非空的sugar表达式:
if (name is {}) // name !=null
Console.WriteLine("name is not null")
我可以重构这段代码(流行的 as/null check
模式)
var a = b as MyType;
if(a != null) { ... }
..变成一个不错的 "is" type pattern expression:
if(b is MyType a) { ... }
..这很酷...我觉得...是吗?
不过现在也在考虑重构
var a = SomeMethod();
if(a != null) { ... }
..进入:
if(SomMethod() is MyType a) { ... }
注意:没有 as
和 SomeMethod() 已经 returns MyType。它看起来像(伪代码)if(A is A)
并且很容易混淆,不是吗?
第一个重构是合法的,那么后一个呢?我不是 IL 专家来检查自己,C# 7.0 功能对我来说仍然是新的。或许还有我没有发现的问题?
显然这两个实现非常相似,在内存、分配和周期中差异可以忽略不计.
编译器基本上按如下方式处理它们(对于引用类型)
第一
MyType myType = SomeMethod();
if (myType != null)
{
Console.WriteLine(myType.ToString());
}
第二
MyType myType2;
if ((object)(myType2 = SomeMethod()) != null)
{
Console.WriteLine(myType2.ToString());
}
使用 IL
可能效果更好第一
IL_0000: ldarg.0
IL_0001: call instance class C/MyType C::SomeMethod()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse.s IL_0015
IL_000a: ldloc.0
IL_000b: callvirt instance string[mscorlib] System.Object::ToString()
IL_0010: call void[mscorlib] System.Console::WriteLine(string)
第二
IL_0015: ldarg.0
IL_0016: call instance class C/MyType C::SomeMethod()
IL_001b: dup
IL_001c: stloc.1
IL_001d: brfalse.s IL_002a
IL_001f: ldloc.1
IL_0020: callvirt instance string[mscorlib] System.Object::ToString()
IL_0025: call void[mscorlib] System.Console::WriteLine(string)
注意 : 可以查看反汇编,IL和jit-asm here
IL 区别基本上是 2 个操作码:
dup
: 复制计算堆栈上当前最顶层的值,然后将副本压入计算堆栈。Ldloc
: 将特定索引处的局部变量加载到计算堆栈上。
当Jitted时,它很可能优化到相同的指令
总结
- 没有明显的技术差异。
- 是的,我想
is
版本更整洁、更简洁。 - 可能更多的是可打印字符,所以如果你有可打印字符强迫症或者遭受残酷的代码审查,这可能不是一件好事
- 如果你喜欢它并且你的团队也喜欢它,那就去吧。
- 这不是我的菜
我发现编译器非常智能。
is
表达式有多种翻译变体:
if(SomeMethod() is MyType a) {...}
SomeMethod
returnsMyType
MyType
没有覆盖运算符 ==,变量a
未使用if (SomeMethod() != null) {...}
MyType
有覆盖运算符 ==,但变量a
未使用if ((object)(SomeMethod()) != null) {...}
MyType
没有覆盖运算符 ==,使用变量a
MyType a; if ((a = SomeMethod()) != null) {...}
MyType
有覆盖运算符 ==,并且使用变量a
MyType a; if ((object)(a = SomeMethod()) != null) {...}
SomeMethod
returns 其他类型object
未使用变量
a
if (SomeMethod() is MyType) {...}
MyType
没有覆盖运算符 ==,使用变量a
MyType a; if ((a = (SomeMethod() as MyType)) != null) {...}
MyType
有覆盖运算符 ==,并且使用变量a
MyType a; if ((object)(a = (SomeMethod() as MyType)) != null) {...}
顺便说一句,您可以通过 ILSpy 或类似工具检查所有这些变体。
我不会用它来对引用类型执行身份转换,因为 null 检查对未来来说更直观 reader。
对于可空类型,情况完全不同。给定struct S
,然后
void foo(S? p)
{
if (p is S s) {
bar(s);
}
}
相当于
void foo(S? p)
{
if (p.HasValue) {
bar(p.GetValueOrDefault());
}
}
并避免 GetValueOrDefault()
调用(或者更糟的是,读取 Value
属性 执行 另一个 空检查)是 IMO非常有用并且显着有助于提高可读性。
在c#8中,使用模式匹配表达式测试非空的sugar表达式:
if (name is {}) // name !=null
Console.WriteLine("name is not null")