首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

关于引用类型作为参数加上ref与不加ref的区别,该怎么解决

2012-03-19 
关于引用类型作为参数加上ref与不加ref的区别众所周知,引用有class,object,string,delegate,,interface。按

关于引用类型作为参数加上ref与不加ref的区别
众所周知,引用有class,object,string,delegate,,interface。

按道理说引用类型作为参数的时候是引用传递值的。最近写程序的时候无意中发现 引用类型 作为参数的时候,加上ref 与不加ref是有区别的。

以下以class类型 作为测试:
测试代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
  public class Test
  {
  private string _message;
  public string Message
  {
  get
  {
  return _message;
  }
  set
  {
  _message = value;
  }
  }
  }
  class Program
  {
  static void Main(string[] args)
  {
  Test a = new Test();
  a.Message = "初始值";
  Test001(a);
  Console.WriteLine("没有添加ref关键字,也没有对引用参数引用了新的一个Test类型的对象,结果为:{0}",a.Message);

  Test b = new Test();
  b.Message = "初始值";
  TestRef(b);
  Console.WriteLine("没有添加ref关键字,并重新对引用参数引用了新的一个Test类型的对象,结果为:{0}", b.Message);

  Test c = new Test();
  c.Message = "初始值";
  TestRef(ref c);
  Console.WriteLine("关键字ref关键字,并重新对引用参数引用了新的一个Test类型的对象,结果为:{0}", c.Message);

  Console.Read();
  }

  static void TestRef(Test t)
  {
  t = new Test();
  t.Message = "对象已赋值";
  }

  static void TestRef(ref Test t)
  {
  t = new Test();
  t.Message = "对象已赋值";
  }

  static void Test001(Test t)
  {
  t.Message = "对象已赋值";
  }
  }
}

给出每个方法的IL

TestRef(Test t)
.method private hidebysig static void TestRef(class ConsoleApplication1.Test t) cil managed
{
  // 代码大小 21 (0x15)
  .maxstack 8
  IL_0000: nop
  IL_0001: newobj instance void ConsoleApplication1.Test::.ctor()
  IL_0006: starg.s t
  IL_0008: ldarg.0
  IL_0009: ldstr bytearray (F9 5B 61 8C F2 5D 4B 8D 3C 50 ) // .[a..]K.<P
  IL_000e: callvirt instance void ConsoleApplication1.Test::set_Message(string)
  IL_0013: nop
  IL_0014: ret
} // end of method Program::TestRef

TestRef(ref Test t)
.method private hidebysig static void TestRef(class ConsoleApplication1.Test& t) cil managed
{
  // 代码大小 22 (0x16)
  .maxstack 8
  IL_0000: nop
  IL_0001: ldarg.0
  IL_0002: newobj instance void ConsoleApplication1.Test::.ctor()
  IL_0007: stind.ref
  IL_0008: ldarg.0
  IL_0009: ldind.ref
  IL_000a: ldstr bytearray (F9 5B 61 8C F2 5D 4B 8D 3C 50 ) // .[a..]K.<P
  IL_000f: callvirt instance void ConsoleApplication1.Test::set_Message(string)
  IL_0014: nop
  IL_0015: ret
} // end of method Program::TestRef

Test001(Test t)
.method private hidebysig static void Test001(class ConsoleApplication1.Test t) cil managed
{
  // 代码大小 14 (0xe)
  .maxstack 8
  IL_0000: nop
  IL_0001: ldarg.0
  IL_0002: ldstr bytearray (F9 5B 61 8C F2 5D 4B 8D 3C 50 ) // .[a..]K.<P
  IL_0007: callvirt instance void ConsoleApplication1.Test::set_Message(string)


  IL_000c: nop
  IL_000d: ret
} // end of method Program::Test001


[解决办法]
当然是有区别的,加ref就是按引用传递,不加就是值传递。“引用类型作为参数的时候是引用传递值的”这个不正确,只有加了ref的才是按引用传递。
[解决办法]

探讨
按道理说引用类型作为参数的时候是引用传递值的。最近写程序的时候无意中发现 引用类型 作为参数的时候,加上ref 与不加ref是有区别的。

[解决办法]
探讨
引用:
按道理说引用类型作为参数的时候是引用传递值的。最近写程序的时候无意中发现 引用类型 作为参数的时候,加上ref 与不加ref是有区别的。


何止“加上ref与不加ref”的区别?你连方法内部的代码都不同。你明明一个是对new的新对象的属性赋值,另一个则是对参数传来的对象的属性赋值。

如果你都是对传来的对象的属性赋值,也就是都是写一句 t……

[解决办法]
C# code
class Program    {        static void Main(string[] args)        {            MyClass classA = new MyClass();            Console.WriteLine(classA.Message);            MyClass classA1 = methodA(classA);            Console.WriteLine(classA.Message);            Console.WriteLine(classA1.Message);            Console.WriteLine(classA.Equals(classA1));            Console.WriteLine("--------------------------------");            MyClass classB = new MyClass();            Console.WriteLine(classB.Message);            MyClass classB1 = methodB(ref classB);            Console.WriteLine(classB.Message);            Console.WriteLine(classB1.Message);            Console.WriteLine(classB.Equals(classB1));            Console.ReadLine();        }        private static MyClass methodA(MyClass myClass)        {            myClass = new MyClass();            myClass.Message = "MethodA";            return myClass;        }        private static MyClass methodB(ref MyClass myClass)        {            myClass = new MyClass();            myClass.Message = "MethodB";            return myClass;        }    }    class MyClass    {        internal MyClass()        {            this.Message = "MyClass";        }        internal string Message { get; set; }    }
[解决办法]
探讨
引用:
当然是有区别的,加ref就是按引用传递,不加就是值传递。“引用类型作为参数的时候是引用传递值的”这个不正确,只有加了ref的才是按引用传递。

这位兄弟,如果非要加ref才是引用传递的话,你可以测一下我写的第三个方法static void Test001(Test t),看看这个运行结果是什么,这个方法是没有加关键字ref的。
另外就我个人的知识,……

[解决办法]
这么简单的问题还用反编译么?ref 是传引用,否则是传值。

注意,这里的引用不是对象引用的那个引用,你可以叫做引用的引用。有了 ref,你可以把传进来的引用指向另一个对象,最终作用在实参上。

有什么好困惑的。
[解决办法]
其实很简单,C#中方法中的参数也是一个变量,这个变量也需要有一个地址。

对于引用类型的方法参数,传入的对象如果不加Ref,方法参数也就是这个变量也将被创建,不过因为是引用类型,所以地址直接指向所传入对象的地址。所以实际上是有两个变量,但都指向了同一处地址。
如果加了Ref,那么方法参数这个变量,将不被创建,也就是只有一个变量,指向了一处地址。

引用类型加不加Ref没有本质区别,只是多个了变量而已。
[解决办法]
class a{
 pubic int b;
 public a(int m){b=m}
}
void test(ref a a1)
{
a1=new a(10);
}
void test1(a a1)
{
a1=new a(10);
}
void main(...)
{
a a2=new a(1);
test1(a2);
console.writeline(a2.b);
test(ref a2);
consolse.writeline(a2.b);
}
[解决办法]
在.net中,具有ref的要比没有它的运行慢。这是跟c++完全不同的。ref传送的是(如#8楼所说)“引用的引用”,而程序又会特意额外多生成一条语句去修改调用方的引用。所以假设你写
C# code
static void TestRef(ref Test t){    t.Message = "对象已赋值";}
------解决方案--------------------


这种问题在CSDN求解释,很可能是:水越搅越混
你直接去看MSDN的解释就一清二楚:
http://msdn.microsoft.com/zh-cn/library/s6938f28.aspx
[解决办法]
Test c = new Test();
c.Message = "初始值";
TestRef(c);


static void TestRef(Test t)//调用时复制c引用给t,此时t和c指向相同的地址
{
t = new Test();//t指向新的地址,与c一点关系都没有了
t.Message = "对象已赋值";//所以在这里,c.Message依然是"初始值";
}

而ref则不会有这个问题,当t=new Test()时,c也指向新的地址,参考6楼的例子,看看它们是不是同一个对象就知道了。
[解决办法]
class Program
{
static void Main(string[] args)
{
Test a = new Test();
a.Message = "初始值";
Test001(a);
Console.WriteLine("没有添加ref关键字,也没有对引用参数引用了新的一个Test类型的对象,结果为:{0}", a.Message);

Test b = new Test();
b.Message = "初始值";
TestRef(b);
Console.WriteLine(b.GetHashCode());
Console.WriteLine("没有添加ref关键字,并重新对引用参数引用了新的一个Test类型的对象,结果为:{0}", b.Message);

Test c = new Test();
c.Message = "初始值";
TestRef(ref c);
Console.WriteLine(c.GetHashCode());
Console.WriteLine("关键字ref关键字,并重新对引用参数引用了新的一个Test类型的对象,结果为:{0}", c.Message);

Console.Read();
}

static void TestRef(Test t)
{
t = new Test();
Console.WriteLine(t.GetHashCode());
t.Message = "对象已赋值";
}

static void TestRef(ref Test t)
{
t = new Test();
Console.WriteLine(t.GetHashCode());
t.Message = "对象已赋值";
}

static void Test001(Test t)
{
t.Message = "对象已赋值";
}
}
[解决办法]

探讨
引用:
在.net中,具有ref的要比没有它的运行慢。这是跟c++完全不同的。ref传送的是(如#8楼所说)“引用的引用”,而程序又会特意额外多生成一条语句去修改调用方的引用。所以假设你写

C# code

static void TestRef(ref Test t)
{
t.Message = "对象已赋值";
}
它与Test001相比就是……

[解决办法]
C# code
namespace ConsoleApplication7{    class A {        public int a;        public A(int temp)        {            a = temp;        }    }    class Program    {        static void test(A aa)        {            aa=new A(1000);        }        static void testref(ref A aa)        {            aa = new A(1000);        }        static void Main(string[] args)        {            A a = new A(10);            test(a);            testref(ref a);        }    }}
[解决办法]
Assembly code
    static void Main(string[] args)        {00000000  push        ebp  00000001  mov         ebp,esp 00000003  push        edi  00000004  push        esi  00000005  push        ebx  00000006  sub         esp,38h 00000009  mov         esi,ecx 0000000b  lea         edi,[ebp-40h] 0000000e  mov         ecx,0Dh 00000013  xor         eax,eax 00000015  rep stos    dword ptr es:[edi] 00000017  mov         ecx,esi 00000019  xor         eax,eax 0000001b  mov         dword ptr [ebp-1Ch],eax 0000001e  mov         dword ptr [ebp-3Ch],ecx 00000021  cmp         dword ptr ds:[009D86F0h],0 00000028  je          0000002F 0000002a  call        793CE1B9 0000002f  nop                          A a = new A(10);00000030  mov         ecx,9D9328h 00000035  call        FFCA0E84 0000003a  mov         dword ptr [ebp-44h],eax 0000003d  mov         ecx,dword ptr [ebp-44h] 00000040  mov         edx,0Ah 00000045  call        FFCBB350 0000004a  mov         eax,dword ptr [ebp-44h] 0000004d  mov         dword ptr [ebp-40h],eax             test(a);00000050  mov         ecx,dword ptr [ebp-40h] 00000053  call        FFCBB2E8 00000058  nop                          testref(ref a);00000059  lea         ecx,[ebp-40h] 0000005c  call        FFCBB2F0 00000061  nop                      }再看看最终的汇编吧!! 


[解决办法]
static void test(A aa)
{
00000000 push ebp
00000001 mov ebp,esp 
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 sub esp,34h 
00000009 xor eax,eax 
0000000b mov dword ptr [ebp-10h],eax 
0000000e xor eax,eax 
00000010 mov dword ptr [ebp-1Ch],eax 
00000013 mov dword ptr [ebp-3Ch],ecx 
00000016 cmp dword ptr ds:[009D86F0h],0 
0000001d je 00000024 
0000001f call 793CE0E1 
00000024 nop
aa=new A(1000);
00000025 mov ecx,9D9328h 
0000002a call FFCA0DAC 
0000002f mov dword ptr [ebp-40h],eax 
00000032 mov ecx,dword ptr [ebp-40h] 
00000035 mov edx,3E8h 
0000003a call FFCBB278 
0000003f mov eax,dword ptr [ebp-40h] 
00000042 mov dword ptr [ebp-3Ch],eax 
}
00000045
[解决办法]

Assembly code
    static void testref(ref A aa)        {00000000  push        ebp  00000001  mov         ebp,esp 00000003  push        edi  00000004  push        esi  00000005  push        ebx  00000006  sub         esp,34h 00000009  xor         eax,eax 0000000b  mov         dword ptr [ebp-10h],eax 0000000e  xor         eax,eax 00000010  mov         dword ptr [ebp-1Ch],eax 00000013  mov         dword ptr [ebp-3Ch],ecx 00000016  cmp         dword ptr ds:[009D86F0h],0 0000001d  je          00000024 0000001f  call        793CE081 00000024  nop                          aa = new A(1000);00000025  mov         ecx,9D9328h 0000002a  call        FFCA0D4C 0000002f  mov         dword ptr [ebp-40h],eax 00000032  mov         ecx,dword ptr [ebp-40h] 00000035  mov         edx,3E8h 0000003a  call        FFCBB218 0000003f  mov         edx,dword ptr [ebp-3Ch] 00000042  mov         eax,dword ptr [ebp-40h] 00000045  call        79151428         }
[解决办法]
探讨
引用:
Assembly code

static void testref(ref A aa)
{
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ……

很清晰,谢谢。能不能介绍下这是用的什么工具,能不能调试汇编……

[解决办法]
并不需要什么CLR via C#,其实无论你看什么书,无论你怎么学的C#,这都是一个基本的不能再基本的问题。
[解决办法]
探讨

引用:
引用:
Assembly code

static void testref(ref A aa)
{
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ……

很……

[解决办法]
TestRef(Test t)和TestRef(ref Test t)的区别,你自己不也贴出IL代码了吗

关键点:

TestRef(Test t) 
IL_0000: newobj instance void ConsoleApplication3.Test::.ctor()
IL_0005: starg.s t
IL_0007: ldarg.0

TestRef(ref Test t) 
IL_0000: ldarg.0
IL_0001: newobj instance void ConsoleApplication3.Test::.ctor()
IL_0006: stind.ref
IL_0007: ldarg.0
IL_0008: ldind.ref


stind.ref
存储所提供地址处的对象引用值

堆栈转换行为依次为: 

1.将地址推送到堆栈上。 

2.将值推送到堆栈上。 

3.从堆栈中弹出值和地址;将值存储在该地址。 



ldind.ref
将对象引用作为 O(对象引用)类型间接加载到计算堆栈上

堆栈转换行为依次为: 



1.将地址推送到堆栈上。 

2.从堆栈中弹出地址;获取位于此地址的对象引用。 

3.将获取的引用推送到堆栈上。

热点排行