• 【转】编写高质量代码改善C#程序的157个建议——建议61:避免在finally内撰写无效代码


    建议61:避免在finally内撰写无效代码

    在阐述建议之前,需要先提出一个问题:是否存在一种打破try-finally执行顺序的情况,答案是:不存在(除非应用程序本身因为某些很少出现的特殊情况在try块中退出)。应该始终认为finally内的代码会在方法return之前执行,哪怕return在try块中。

    正是这点,可能会让你写出无效的代码,有时候,这样的无效代码会是一个隐藏很深的Bug。

    看下面代码:

            private static int TestIntReturnBelowFinally()
            {
                int i;
                try
                {
                    i = 1;
                }
                finally
                {
                    i = 2;
                    Console.WriteLine("	将int结果改为2,finally执行完毕");
                }
                return i;
            }

    返回值是2。

    但是:

            private static int TestIntReturnInTry()
            {
                int i;
                try
                {
                    return i = 1;
                }
                finally
                {
                    i = 2;
                    Console.WriteLine("	将int结果改为2,finally执行完毕");
                }
            }

    返回值是1。

    再看下面代码:

            static User TestUserReturnInTry()
            {
                User user = new User() { Name = "Mike", BirthDay = new DateTime(2010, 1, 1) };
                try
                {
                    return user;
                }
                finally
                {
                    user.Name = "Rose";
                    user.BirthDay = new DateTime(2010, 2, 2);
                    Console.WriteLine("	将user.Name改为Rose");
                }
            }

    user类:

        class User
        {
    
            public string Name { get; set; }
    
            public DateTime BirthDay { get; set; }
        }
    View Code

    TestUserReturnInTry方法返回的User中,Name的值已经改为Rose了。

    现在来解释为什么上面3个函数会有3种结果。查看TestIntReturnBelowFinally的finally部分的IL代码:

      finally
      {
        IL_0004:  ldc.i4.2
        IL_0005:  stloc.0
        IL_0006:  ldstr      bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65   // ...i.n.t..~.g9e
                                        3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00   // :N2...f.i.n.a.l.
                                        6C 00 79 00 67 62 4C 88 8C 5B D5 6B )             // l.y.gbL..[.k
        IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_0010:  endfinally
      }  // end handler
      IL_0011:  ldloc.0
      IL_0012:  ret
    }

    IL_0004: ldc.i4.2”首先将2压入栈顶

    IL_0005: stloc.0”将最顶层堆栈的值,也就是2赋值给本地变量,也就是 i (index 0)

    IL_0011: ldloc.0”将本地变量 i (index 0)的值再次压入栈

    IL_0012: ret”结束函数,同时把栈内的返回值压入调用者的栈中。就函数将2赋值给了返回值。

    看方法TestIntReturnInTry()的Debug版本的IL代码:

    .method private hidebysig static int32  TestIntReturnInTry() cil managed
    {
      // 代码大小       27 (0x1b)
      .maxstack  2
      .locals init ([0] int32 i,
               [1] int32 CS$1$0000)
      IL_0000:  nop
      .try
      {
        IL_0001:  nop
        IL_0002:  ldc.i4.1
        IL_0003:  dup
        IL_0004:  stloc.0
        IL_0005:  stloc.1
        IL_0006:  leave.s    IL_0018
      }  // end .try
      finally
      {
        IL_0008:  nop
        IL_0009:  ldc.i4.2
        IL_000a:  stloc.0
        IL_000b:  ldstr      bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65   // ...i.n.t..~.g9e
                                        3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00   // :N2...f.i.n.a.l.
                                        6C 00 79 00 67 62 4C 88 8C 5B D5 6B )             // l.y.gbL..[.k
        IL_0010:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_0015:  nop
        IL_0016:  nop
        IL_0017:  endfinally
      }  // end handler
      IL_0018:  nop
      IL_0019:  ldloc.1
      IL_001a:  ret
    } // end of method Program::TestIntReturnInTry

    TestIntReturnInTry在IL中创建了两个本地变量 i 和CS$1$0000 ,i 存储的是1,然后finally中 i 被赋值为2。调用者真正得到的是由IL创建的CS$1$0000所对应的值。用Reflector查看C#代码:

    private static int TestIntReturnInTry()
    {
        int i;
        int CS$1$0000;
        try
        {
            CS$1$0000 = i = 1;
        }
        finally
        {
            i = 2;
            Console.WriteLine("	将int结果改为2,finally执行完毕");
        }
        return CS$1$0000;
    }
    

    实际上,finally中i=2没有任何意义,所以在本函数的release版本中,IL中找不到对应的代码:

    .method private hidebysig static int32  TestIntReturnInTry() cil managed
    {
      // 代码大小       17 (0x11)
      .maxstack  1
      .locals init ([0] int32 CS$1$0000)
      .try
      {
        IL_0000:  ldc.i4.1
        IL_0001:  stloc.0
        IL_0002:  leave.s    IL_000f
      }  // end .try
      finally
      {
        IL_0004:  ldstr      bytearray (09 00 06 5C 69 00 6E 00 74 00 D3 7E 9C 67 39 65   // ...i.n.t..~.g9e
                                        3A 4E 32 00 0C FF 66 00 69 00 6E 00 61 00 6C 00   // :N2...f.i.n.a.l.
                                        6C 00 79 00 67 62 4C 88 8C 5B D5 6B )             // l.y.gbL..[.k
        IL_0009:  call       void [mscorlib]System.Console::WriteLine(string)
        IL_000e:  endfinally
      }  // end handler
      IL_000f:  ldloc.0
      IL_0010:  ret
    } // end of method Program::TestIntReturnInTry

    用Reflector查看release版本中C#代码:

    private static int TestIntReturnInTry()
    {
        int CS$1$0000;
        try
        {
            CS$1$0000 = 1;
        }
        finally
        {
            Console.WriteLine("	将int结果改为2,finally执行完毕");
        }
        return CS$1$0000;
    }
    

    再解释第三个方法TestUserReturnInTry为什么返回的是“Rose”。Reflector查看release版本中C#代码:

    private static User TestUserReturnInTry()
    {
        User CS$1$0000;
        User <>g__initLocal0 = new User {
            Name = "Mike",
            BirthDay = new DateTime(0x7da, 1, 1)
        };
        User user = <>g__initLocal0;
        try
        {
            CS$1$0000 = user;
        }
        finally
        {
            user.Name = "Rose";
            user.BirthDay = new DateTime(0x7da, 2, 2);
            Console.WriteLine("	将user.Name改为Rose");
        }
        return CS$1$0000;
    }
    

    User是引用类型, CS$1$0000 = user;说明CS$1$0000和user指向的是同一个对象,当在finally中 user.Name = "Rose"时CS$1$0000的Name也会变为“Rose”。所以返回的CS$1$0000的Name为“Rose”。

    再举一个例子:

            private static User TestUserReturnInTry2()
            {
                User user = new User() { Name = "Mike", BirthDay = new DateTime(2010, 1, 1) };
                try
                {
                    return user;
                }
                finally
                {
                    user.Name = "Rose";
                    user.BirthDay = new DateTime(2010, 2, 2);
                    user = null;
                    Console.WriteLine("	将user置为anull");
                }
            }

    返回的结果不是null,而一个Name=“Rose”,BirthDay = new DateTime(2010, 2, 2)的User对象。Reflector查看release版本中C#代码:

    private static User TestUserReturnInTry2()
    {
        User CS$1$0000;
        User <>g__initLocal1 = new User {
            Name = "Mike",
            BirthDay = new DateTime(0x7da, 1, 1)
        };
        User user = <>g__initLocal1;
        try
        {
            CS$1$0000 = user;
        }
        finally
        {
            user.Name = "Rose";
            user.BirthDay = new DateTime(0x7da, 2, 2);
            user = null;
            Console.WriteLine("	将user置为anull");
        }
        return CS$1$0000;
    }
    

    CS$1$0000和user指向的是同一个对象当在finally中 user=null 时,只是user指向为null了,CS$1$0000指向的对象并没有变。

    转自:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    Python入门篇-解析式、生成器
    使用Kerberos进行Hadoop认证
    Python标准库-datatime和time
    使用Cloudera Manager部署HUE
    使用Cloudera Manager部署oozie
    使用Cloudera Manager部署Spark服务
    HDFS重启集群导致数据损坏,使用fsck命令修复过程
    关系型数据的收集
    使用Cloudera Manager搭建Kudu环境
    分布式结构化存储系统-Kudu简介
  • 原文地址:https://www.cnblogs.com/farmer-y/p/7992697.html
Copyright © 2020-2023  润新知