关于引用类型作为参数加上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的才是按引用传递。
[解决办法]
[解决办法]
[解决办法]
- 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,你可以把传进来的引用指向另一个对象,最终作用在实参上。
有什么好困惑的。
[解决办法]
其实很简单,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 = "对象已赋值";
}
}
[解决办法]
[解决办法]
- 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 }
[解决办法]
[解决办法]
并不需要什么CLR via C#,其实无论你看什么书,无论你怎么学的C#,这都是一个基本的不能再基本的问题。
[解决办法]
[解决办法]
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.将获取的引用推送到堆栈上。