ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

理解.Net中带out、ref的方法签名和普通方法签名的区别

2019-06-25 18:54:20  阅读:314  来源: 互联网

标签:Int32 中带 TestRef para void 签名 ref out


今天有位新同事问我.Net中带out、ref的方法签名和普通方法签名的有什么区别?我觉得可以从下面的例子说明一些关键的地方。

一、ref/out修饰符说明

对于用ref/out修饰符的说明在MSDN上有详细的说明,地址如下:

http://msdn.microsoft.com/en-us/library/t3c3bfhx(VS.80).aspx

 

二、透过IL代码观察ref/out修饰的方法签名(以值类型为例)

1、示例代码:

using System;

namespace ConsoleMain
{
    class Program
    {
        static void Main()
        {
            Int32 p ;

            TestRef(out p);                             //①

            //TestRef(ref p)                        //②
            
            TestRef(p);                         //③

            Console.ReadKey();
        }

        static void TestRef(Int32 para)                 //④
        {
            para = 1;
        }

        static void TestRef(out Int32 para)                //⑤
        {
            para = 2;
        }

        /*static void TestRef(ref Int32 para)                //⑥
        {
            Para3 =  3;
        } */
    }
}

2、使用Reflector查看相应的IL代码如下:

(1) Main()

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] int32 p)
    L_0000: ldloca.s p
    L_0002: call void ConsoleMain.Program::TestRef(int32&)
    L_0007: ldloc.0 
    L_0008: call void ConsoleMain.Program::TestRef(int32)
    L_000d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
    L_0012: pop 
    L_0013: ret 
}

(2) TestRef(ref Int32 para)

.method private hidebysig static void TestRef(int32& para) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldc.i4.2 
    L_0002: stind.i4 
    L_0003: ret 
}

(3) TestRef(Int32 para)

.method private hidebysig static void TestRef(int32 para) cil managed
{
    .maxstack 8
    L_0000: ldc.i4.1 
    L_0001: starg.s para
    L_0003: ret 
}

(4) TestRef(out Int32 para)

.method private hidebysig static void TestRef([out] int32& para) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldc.i4.2 
    L_0002: stind.i4 
    L_0003: ret 
}

3、IL代码分析

某个方法被调用时会创建Evaluation Stack、局部变量区、方法参数区等存储区被创建,具体内容可参见MSIL 心得一文。


 

1) Main()

.entrypoint,

当前方法为入口方法;

.maxstack 1,

将创建的Evaluation Stack元素容量最大值设置为1;

.locals init ([0] int32 p)

     pint32

L_0000: ldloca.s p

pEvaluation Stack


L_0002: call void ConsoleMain.Program::TestRef(int32&)

     callcall
    Call””(this)
public class TestClass
{
    private void InvokeTest()
    {
        Test(1);
    }

    private void Test(Int32 i)
    {           
    }       
}   

InvokeTest方法的IL代码如下:
.method private hidebysig instance void InvokeTest() cil managed
{
   .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldc.i4.1 
    L_0002: call instance void ConsoleMain.TestClass::Test(int32)
    L_0007: ret 
}
L_0000: ldarg.0TestTestEvaluation Stackarg0TestClassthisStaticarg0

     CallVrtual tableCallCallvirt

     MainEvaluation Stack&pTestRef(int32&)””TestRef(int32&)MainEvaluation Stackref&

L_0007: ldloc.0

     Mainp

L_0008: call void ConsoleMain.Program::TestRef(int32)

     MainEvaluation StackpTestRef(int32)””TestRef(int32)

L_000d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()

     mscorlib.dllReadKey(valuetype)System.ConsoleKeyInfoMainEvaluation Stack

L_0012: pop  L_0013: ret,

     MainEvaluation StackMain

 

2)TestRef(ref Int32 para)

 L_0000: ldarg.0

    0Evaluation Stack

L_0001: ldc.i4.2

    int322Evaluation Stack

L_0002: stind.i4

    Evaluation Stack(valueaddress)valueaddress

L_0003: ret

 

3)、TestRef(Int32 para)

L_0000:ldc.i4.1,

执行完成后的堆栈变化情况:



L_0001: starg.s para,

执行完成后的堆栈变化情况:



L_0003: ret,

方法返回,相应的Evaluation Stack、局部变量区、方法参数区等存储区被释放。

 

4)、TestRef(out Int32 para)

     从方法签名上看它只比TestRef(ref Int32 para)多一个[out],其它内容完全一样。

     在代码中将②放开,会发现编译不通过,说明方法签名的区别如果仅仅是ref和out则无法实现方法的overload,也就是TestRef(ref Int32 para)和TestRef(out Int32 para)这两个方法不能同时存在于同一个类型中。

在代码中将①注释而将②放开,会发现编译不通过,因为不能将一个未初始化的变量传给ref修饰参数的方法,但是传给out修饰参数的方法是可以的,但是在方法返回前一定要给out修饰的参数赋值。借用MS的一句话:

the ref and out keywords are treated differently at run-time, but they are treated the same at compile time.

 

4、结论

     (1)、有ref和out修饰参数的方法和普通方法在调用前的数据准备是不一样的,由L_0000: ldloca.s p和L_0007: ldloc.0可以看到,前者是获取目标变量的内存地址,后者是获取目标变量的值,这就是所谓的传引用和传值。

     (2)、两个方法的区别仅仅是相同参数,一个使用的修饰符是ref,另一个是out,那么无法重载这两个方法,且分别编译它们得到的IL代码完全一样,只是方法签名中由out修饰的那个参数前会有个token[out]。

(3)、使用out参数的作用:我并不关心变量的初值是什么或者我不知道初值应该赋什么或者我只是想知道我的方法执行完成后的状态(例如:成功or错误并给出错误原因),因为凡是用out修饰了的参数在方法中一定要为该参数重新赋值,正如MS所说:允许方法有选择地返回值。

转载于:https://www.cnblogs.com/vivounicorn/archive/2009/09/17/1568242.html

标签:Int32,中带,TestRef,para,void,签名,ref,out
来源: https://blog.csdn.net/weixin_34270606/article/details/93642172

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有