void C<T>(object o) where T : struct { if (o is T t) Console.WriteLine($"Argument is {typeof(T)}: {t}"); }
编译成以下CIL
IL_0000: ldarg.0 IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!!T> IL_0006: unBox.any valuetype [mscorlib]System.Nullable`1<!!T> IL_000b: stloc.1 IL_000c: ldloca.s 1 IL_000e: call instance !0 valuetype [mscorlib]System.Nullable`1<!!T>::GetValueOrDefault() IL_0013: stloc.0 IL_0014: ldloca.s 1 IL_0016: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue() IL_001b: brfalse.s IL_003c IL_001d: ldstr "Argument is {0}: {1}" IL_0022: ldtoken !!T IL_0027: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_002c: ldloc.0 IL_002d: Box !!T IL_0032: call string [mscorlib]System.String::Format(string,object,object) IL_0037: call void [mscorlib]System.Console::WriteLine(string) IL_003c: ret
但是下面的C#
void D<T>(object o) where T : struct { if (o is T) Console.WriteLine($"Argument is {typeof(T)}: {(T) o}"); }
编译成以下CIL
IL_0000: ldarg.0 IL_0001: isinst !!T IL_0006: brfalse.s IL_002c IL_0008: ldstr "Argument is {0}: {1}" IL_000d: ldtoken !!T IL_0012: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0017: ldarg.0 IL_0018: unBox.any !!T IL_001d: Box !!T IL_0022: call string [mscorlib]System.String::Format(string,object) IL_0027: call void [mscorlib]System.Console::WriteLine(string) IL_002c: ret
我认为发生了什么:查看第一种方法的CIL,似乎(1)检查参数是否为[盒装?] Nullable< T>,如果是,则将其推入堆栈,否则为null,( 2)取消它(如果它为空?),(3)尝试获取其值,否则默认(T),(4)然后检查它是否有值,如果没有则分支出来.第二种方法的CIL足够简单,它只是试图取消打包参数.
如果两个代码的语义是等价的,为什么前一种情况涉及拆箱到Nullable< T>.而前一种情况“只是拆箱”?其次,在第一个CIL中,如果对象参数是一个盒装的int,我现在认为它正是它在tin上所说的(即盒装的int而不是盒装的Nullable< int>),不会isinst指令总是失败? Nullable< T>即使在CIL级别也能得到特殊待遇?
更新:在手写一些MSIL之后,看起来该对象(如果它确实是一个盒装的int)可以解包到int或Nullable< int>中.
.method private static void Foo(object o) cil managed { .maxstack 1 ldarg.0 isinst int32 brfalse.s L_00 ldarg.0 unBox.any int32 call void [mscorlib]System.Console::WriteLine(int32) L_00: ldarg.0 isinst valuetype [mscorlib]System.Nullable`1<int32> brfalse.s L_01 ldarg.0 unBox valuetype [mscorlib]System.Nullable`1<int32> call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() call void [mscorlib]System.Console::WriteLine(int32) L_01: ldarg.0 unBox valuetype [mscorlib]System.Nullable`1<int32> call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() brtrue.s L_02 ldstr "No value!" call void [mscorlib]System.Console::WriteLine(string) L_02: ret }
解决方法
if(o is T) //use (T)o
T t = o as T; if(t != null) //use t
对于引用类型,第一个具有冗余转换,因为is被编译为isinst和条件分支,正如您可以从使用的CIL指令中看到的那样.第二个代码与CIL方面的第一个代码相同,减去额外的(T)o cast(编译为castclass).
对于值类型,第二个选项只能以可空类型完成,我也认为它实际上比第一个慢(必须创建一个结构).
我已经将以下方法编译为CIL:
static void C<T>(object o) where T : struct { T? t = o as T?; if(t != null) Console.WriteLine("Argument is {0}: {1}",typeof(T),t); }
.method private hidebysig static void C<valuetype .ctor ([mscorlib]System.ValueType) T>(object o) cil managed { // Code size 48 (0x30) .maxstack 3 .locals init (valuetype [mscorlib]System.Nullable`1<!!T> V_0) IL_0000: ldarg.0 IL_0001: isinst valuetype [mscorlib]System.Nullable`1<!!T> IL_0006: unBox.any valuetype [mscorlib]System.Nullable`1<!!T> IL_000b: stloc.0 IL_000c: ldloca.s V_0 IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<!!T>::get_HasValue() IL_0013: brfalse.s IL_002f IL_0015: ldstr "Argument is {0}: {1}" IL_001a: ldtoken !!T IL_001f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0024: ldloc.0 IL_0025: Box valuetype [mscorlib]System.Nullable`1<!!T> IL_002a: call void [mscorlib]System.Console::WriteLine(string,object) IL_002f: ret }
除了调用GetValueOrDefault之外,这正是问题中的代码,因为我没有获得可空实例的实际值.
可以为空的类型不能直接装箱或取消装箱,只能通过它们的底层值,或者作为普通的空值.第一个isinst确保其他类型不会产生异常(我想isinst !! T也可以使用),而只是一个空引用.然后,unBox.any操作码从引用中形成一个可为空的实例,然后像往常一样使用它.该指令也可以写为空检查并自行构成可空实例,但这种方式更短.
C#7使用第二种方式为T t,因此如果T是值类型,则除了使用可空类型之外别无选择.为什么不选择以前的选项呢?我只能猜测它在语义或实现,变量分配等方面可能会有一些实质性的差异.因此,他们选择与新构造的实现保持一致.
为了比较,这是我在上面的方法中改变T:struct到T:class时生成的内容(以及T?到T):
.method private hidebysig static void C<class T>(object o) cil managed { // Code size 47 (0x2f) .maxstack 3 .locals init (!!T V_0) IL_0000: ldarg.0 IL_0001: isinst !!T IL_0006: unBox.any !!T IL_000b: stloc.0 IL_000c: ldloc.0 IL_000d: Box !!T IL_0012: brfalse.s IL_002e IL_0014: ldstr "Argument is {0}: {1}" IL_0019: ldtoken !!T IL_001e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0023: ldloc.0 IL_0024: Box !!T IL_0029: call void [mscorlib]System.Console::WriteLine(string,object) IL_002e: ret }
再次与原始方法相当一致.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。