返回新类型和返回对象之间有区别吗

本文关键字:返回 对象 之间 有区别 新类型 类型 | 更新日期: 2023-09-27 18:20:25

在C#中,为两种不同的编写方式生成的字节码有什么不同吗?我希望是一样的:

返回创建对象:

public MemoryStream GetStream() {
  MemoryStream s = new MemoryStream(this.GetBytes());
  return s;
}

新退货:

public MemoryStream GetStream() {
  return new MemoryStream(this.GetBytes());
}

会优化掉任何差异吗?还是第一个比第二个更容易受到垃圾收集的影响?还是这只是个人喜好?

返回新类型和返回对象之间有区别吗

查看IL代码,第二个版本中的步骤似乎比第一个版本少。

.method public hidebysig 
    instance class [mscorlib]System.IO.MemoryStream GetStream1 () cil managed 
{
    // Method begins at RVA 0x22c0
    // Code size 13 (0xd)
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream s,
        [1] class [mscorlib]System.IO.MemoryStream CS$1$0000
    )
    IL_0000: nop
    IL_0001: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: stloc.1
    IL_0009: br.s IL_000b
    IL_000b: ldloc.1
    IL_000c: ret
} // end of method Form1::GetStream1
.method public hidebysig 
    instance class [mscorlib]System.IO.MemoryStream GetStream2 () cil managed 
{
    // Method begins at RVA 0x22dc
    // Code size 11 (0xb)
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream CS$1$0000
    )
    IL_0000: nop
    IL_0001: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    IL_0006: stloc.0
    IL_0007: br.s IL_0009
    IL_0009: ldloc.0
    IL_000a: ret
} // end of method Form1::GetStream2

它似乎没有做太多,但还是多走了几步。

@Alexei Levenkov,这是代码的发布版本

.method public hidebysig 
    instance class [mscorlib]System.IO.MemoryStream GetStream1 () cil managed 
{
    // Method begins at RVA 0x2264
    // Code size 8 (0x8)
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream s
    )
    IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: ret
} // end of method Form1::GetStream1
.method public hidebysig 
    instance class [mscorlib]System.IO.MemoryStream GetStream2 () cil managed 
{
    // Method begins at RVA 0x2278
    // Code size 6 (0x6)
    .maxstack 8
    IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor()
    IL_0005: ret
} // end of method Form1::GetStream2

看起来还是稍微多一点。

如果您使用Reflector来检查为此生成的代码:

public MemoryStream GetStream(byte[] bytes)
{
    MemoryStream s = new MemoryStream(bytes);
    return s;
}

对于发布版本,您可以获得以下信息:

.method public hidebysig instance class [mscorlib]System.IO.MemoryStream GetStream(uint8[] bytes) cil managed
{
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream s)
    L_0000: ldarg.1 
    L_0001: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor(uint8[])
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ret 
}

正如您所看到的,C#编译器已经优化掉了额外的变量。

然而,对于调试构建,您可以得到以下内容:

.method public hidebysig instance class [mscorlib]System.IO.MemoryStream GetStream(uint8[] bytes) cil managed
{
    .maxstack 1
    .locals init (
        [0] class [mscorlib]System.IO.MemoryStream s,
        [1] class [mscorlib]System.IO.MemoryStream CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.1 
    L_0002: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor(uint8[])
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: stloc.1 
    L_000a: br L_000f
    L_000f: ldloc.1 
    L_0010: ret 
}

显然,编译器无法优化掉用于调试构建的额外变量,以防您在调试时检查它。

因此,如果您想保留额外的变量用于调试目的,那么这很好——它对发布版本没有影响。

我相信最终优化的JITed代码将是相同的。

这肯定不会对GC行为产生影响,因为对象的生存期将由使用返回值的人决定(您可能会想到在函数结束前不再使用值的情况,但这里显然不是这样——s在方法执行结束时返回)。

在非优化(调试)构建中,您将能够看到s变量的值。

这两个代码片段大部分相同,性能差异很小,可以忽略不计,从它们中进行选择取决于代码样式和方法功能。如果您的方法除了返回MemoryStream对象之外不应该做任何其他事情,那么第二个代码片段就足够了,但如果您需要在返回MemoryStream对象之前对其执行一些操作,则必须使用第一个。在垃圾回收方面没有区别。