Wu.Country@侠缘

勤学似春起之苗,不见其增,日有所长; 辍学如磨刀之石,不见其损,日所有亏!

深入剖析引用参数Ref和Out

[如果您正准备阅读本文,请一定决心读完所有的评论,谢谢。]

学过C/C++的人,对C#的关键字Ref和Out应该都很好理解。它们都提供了一种可以在被调用函数内修改传递的参数的值的方法。因为这一功能很类似C/C++的指针。对于没学过C/C++的,也应该可以明白这两个参数的作用。

虽然Ref和Out都提供了修改参数值的方法,但它们还是有一点点小的区别。

1、Ref在作为参数调用函数之前,变量一定要赋值,否则会得到一个常规编译错误:使用了未赋值的变量。
2、在被调用函数内,以Ref引入的参数在返回前不必为它赋值。
3、Out在作为参数调用函数之前,变量可以不被赋值。
4、在被调用函数内,以Out引入的参数在返回前一定要至少赋值一次。

其实本质上讲,Ref更适合理解为给被调用函数传递了一个与原参考同地址的变量。而Out则可以理解为在调用函数前,先给变量找个地方,让被调用函数在给定地点放一个值。

看上去很简单不是吗?确实如此,这里是一个例子:
namespace StudyAndTest
{
    
/// <summary>
    
/// Summary description for Class1.
    
/// </summary>

    class Class1
    
{
        
/// <summary>
        
/// The main entry point for the application.
        
/// </summary>

        [STAThread]
        
static void Main(string[] args) 
        
{
            
int m_temp        =0;    //Must be assigned to before call any mothed with the variable by reference.
            Console.WriteLine("Int data befor change:{0}",m_temp);
            ChangeData1(
ref m_temp);
            Console.WriteLine(
"Int data after change:{0}",m_temp);
            ChangeData2(
out m_temp);
            Console.WriteLine(
"Int data after change:{0}",m_temp);
        }


        
static void ChangeData1(ref int i_ref)
        
{
            Console.WriteLine(
"Int data in ChangeData1:{0}",i_ref);
            i_ref    
= 1;
        }


        
static void ChangeData2(out int i_ref)
        
{
            
//Console.WriteLine("Int data in ChangeData2:{0}",i_ref);    //Error in building, use of unassigned local variable i_ref
            i_ref    = 2;    //The out parament i_ref must be assigned to before control leaves the current mothod.
        }

    }

}

然而C#毕竟是与C/C++有着不同之处的。这就是在C#内,所有的变量被分为两类:值类型和引用类型。
那么我们就会有这样的问题:将Ref和Out分别应用于引用类型和值类型的变量上,会是什么样的结果呢?

对于应用于值类型数据的情况,上面的例子已经完全讨论过了,就是完全遵守上面的四句话。而对于引用类型数据,有一个很有趣的问题,就是默认情况下(不带Ref也不带Out)它是以Ref情况而调用函数,即上面的四句话仍然满足。

看这样的一个例子:

namespace StudyAndTest
{
    
/// <summary>
    
/// Summary description for Class1.
    
/// </summary>

    class Class1
    
{
        
/// <summary>
        
/// The main entry point for the application.
        
/// </summary>

        [STAThread]
        
static void Main(string[] args) 
        
{
            TempClass m_class1    
= new TempClass();
            m_class1.m_member    
= 0;
            Console.WriteLine(
"i_obj data before changeData3 :{0}",m_class1.m_member);
            ChangeData3(m_class1);
            Console.WriteLine(
"i_obj data after changeData3 :{0}",m_class1.m_member);

        }

        
static void ChangeData3(TempClass i_obj)
        
{
            i_obj.m_member    
= 3;
//            Console.WriteLine("i_obj data in ChangeData3:{0}",i_obj.m_member);
        }

    }


    
class TempClass
    
{
        
public int m_member;
    }

}


这让人感觉就是Ref,确实如此,默认就是在以Ref为引用类型在调用函数,所以还是要注意以下问题:
引用类型数据一定要初始化。而至于引用类型自己初始化的问题,就交给该类型自己了。如上面的问题,m_member在没有赋值前,一样可以编译的,但运行一定就不对了(但有默认值),这是因为TempClass里没有构造函数。在被调用函数内,一样的使用参数,而且所有对引用参数的改变都影响到函数外。这是默认的情况。

但如果我们强行加上Ref或者Out关键字,会是什么结果呢???
1、如果是用Ref,那么结果是和什么都没用一样!即默认就是用的Ref。(让我们少打了几个字符)
2、如果是用Out,那么要遵守上面的3,4原则,即:在调用前,不必初始化引用对象,再简单一点:就是可以不用New一个对象。但在函数内,返回前一定要New一个,并且在New之前,参数对象是不能使用的。
也就是上面说到的,Out只是在调用前分配了一个地点,在调用函数中使用该地点。注意:这里“地点”一词决不是内存地址。

再思考一个问题:如果在使用Out参考时,在调用函数前,我们已经New了一个对象,再来调用函数结果会是什么呢?
你将“丢失”一部份内存(如果在C/C++里,一定是这样的)。也就是说,在调用了函数后,函数里New的一个对象会让函数外的对象丢失,而新的对象在函数内有效,在函数外也有效。幸运的是:原来的对象的内存并不会像C/C++那样完全的丢失,它将由垃圾回收器来管理了。所以我们并不担心内存的真正丢失问题(这真是一件值得庆幸的事)。

看这样的例子:
    class Class1
    
{
        
/// <summary>
        
/// The main entry point for the application.
        
/// </summary>

        [STAThread]
        
static void Main(string[] args) 
        
{
            TempClass m_class1    
= new TempClass();
            m_class1.m_member    
= 0;
            Console.WriteLine(
"i_obj data before changeData3 :{0}",m_class1.m_member);
            ChangeData3(
out m_class1);
            Console.WriteLine(
"i_obj data after changeData3 :{0}",m_class1.m_member);

        }

        
static void ChangeData3(out TempClass i_obj)
        
{
            i_obj    
= new TempClass();
            i_obj.m_member    
= 3;
//            Console.WriteLine("i_obj data in ChangeData3:{0}",i_obj.m_member);
        }

    }


    
class TempClass
    
{
        
public int m_member;
    }

到此为止,或许你已经觉得你已经对Ref和Out已经十分的了解了,然而你可能无法回答下面的一个问题:它的输出是什么?
    class Class1
    
{
        
/// <summary>
        
/// The main entry point for the application.
        
/// </summary>

        [STAThread]
        
static void Main(string[] args) 
        
{
            TempClass m_class1    
= new TempClass();
            m_class1.m_member    
= 0;
            TempClass m_class2    
= m_class1;
            ChangeData4(
ref m_class2,m_class2);
            Console.WriteLine(
"m_class1:{0},m_class2:{1}",m_class1.m_member,m_class2.m_member);
        }

        
static void ChangeData4(ref TempClass i_obj1,TempClass i_obj2)
        
{
            i_obj1    
= new TempClass();
            i_obj1.m_member    
= 41;
            i_obj2    
= new TempClass();
            i_obj2.m_member    
= 42;
        }

    }


    
class TempClass
    
{
        
public int m_member;
    }

请不要猜测它的结果,这样可能会让你犯一个错误。如果你利用前面的知识来理解,认为两个对象输出的结果是同一个对象的结果,那么你这回又是错误的!
这什么呢?前面不是说的很清楚吗?上面的函数参数一个用了ref,一个没有用,默认的都是Ref,也就是说:两个参数其实是一样的!!而且函数内用了两次New,因此,对象Class1的最后一个有效实例是最后一次New的结果,也就是说:class1和class2都将引用到(指向)函数内最后一次New的实例,所以最后输出应该都是42.

然而很不幸,这回结果是:m_class1:0,m_class2:41
而且它们的内存关系也是有点复杂的。让我们先来看看函数调用前的假想内存情况:

Class1->直接指向TempClass实例1

Class2->指向Class1,间接指向TempClass实例1

 

TempClass实例1,成员值:0

 

 

 


调用函数后的结果: 

Class1->直接指向TempClass实例1

Class2->直接指向TempClass实例2,因为第一个参数用的是Ref

 

TempClass实例1,成员值:0

TempClass实例2,第一次New的结果,成员值:41

TempClass实例3,第二次New的结果,但这只是在函数内的一个副本,该副本不能影响到函数外,即出了函数体就丢失,成员值:42

 


这就是为什么 会有两个结果了!!!也就是说:当我们用Ref来传递引用类型数据的一个引用时(这里的Class2就是这样的情况,它是一个指向TempClass实例1的一个引用),其实是使用的实例的一个副本。即:产生了即没有用Ref也没有用Out的效果。

[Post之后的修改]
这里请一定注意这里的调用:  ChangeData4(ref
 m_class2,m_class2);
读者自己试着这样试试:
  ChangeData4(ref
 m_class1,m_class2);
  ChangeData4(ref
 m_class2,m_class1);
  ChangeData4(ref
 m_class2,m_class1);
然后再分析一下内存,可能会有很大的收获。

最后一个问题:就是上面这种情况应用于值类型数据的时候会是什么情况呢?这就交给读者自己去解决了。

好了,最后一个引用的例子值得思考一下。希望对读者有帮助。

================================
  /\_/\                        
 (=^o^=)  Wu.Country@侠缘      
 (~)@(~)  一辈子,用心做一件事!
--------------------------------
  Happy Jimmy, keep dreaming!  
================================
Wu.Country@侠缘
关注 - 0
粉丝 - 1
0
0
(请您对文章做出评价)
« 上一篇:如何解决DataGrid中删除记录后分页错误
» 下一篇:好大的一场雪。。。。。

posted on 2006-02-27 10:05 Wu.Country@侠缘 阅读(2230) 评论(47) 编辑 收藏

评论

#1楼 2006-02-27 11:17 A.Z      

结果很正常。  回复 引用 查看   

#2楼 2006-02-27 11:19 装配脑袋      

你把这问题复杂化了,其实ref/out对值类型和引用类型的作用是一样的,没什么区别。
此外我觉得C++程序员才更不容易理解哈,因为C++没有“到引用的引用”
 回复 引用 查看   

#3楼[楼主] 2006-02-27 11:41 Wu.Country@侠缘      

不是吧,你们没有注意到,最后一个问题,如果使用不当,会产生多余的内存!
这里是用的一个例子。如果默认,引用类型是Ref参数类型,那么在使用引用类型的时候,有人可能会这样用:
class1 m_class1 = new calss1;
class1 m_class2 = m_class1;
SomeMethod(m_class2);
(认为默认用Ref,所以在函数内的修改会影响m_class1,或者你错误的认为,m_class2会有一个新的实例,而这都是错误的,实际上m_class2一直与m_class1的实例相关,参与函数调用的完全是一个与m_class2也及m_class1完全无关的对象实例。。。。。。。。)

但:SomeMethod(ref m_class2);却又是与上面截然不同的。

而这样的结果会又是什么呢??????
SomeMethod(m_class1);
SomeMethod(ref m_class1);
这里才是真正的引用类型参数传递,在函数内的修改都会对m_class1产生效果,而且如果还有m_class2引用到同一个实例的话,也会对m_class2产生影响。。。。。

我不知道你们是怎样理解的,但这里如果对这样的参数使用不当,只会白白的添加内存消耗,甚至出现想不到的错误。例如上面的第一种情况。
 回复 引用 查看   

#4楼 2006-02-27 12:56 robin-hbifts[未注册用户]

class Test1
{
public int x;

public Test1()
{
x = 0;
}
};

static public void ChanageTest(Test1 obj){
obj.x = 12345;
}

Test1 t1 = new Test1();
Test1 t2 = t1;
Console.WriteLine(t2.x);
t2.x = 12;
Console.WriteLine(t1.x);
Program.ChanageTest(t2);
Console.WriteLine(t1.x);
Console.WriteLine(t2.x);

你觉得这样输出的结果是什么样的呢?

通过ChangeTest后,t1.x的值已变成了12345
在.NET中,所有的Classh默认就是引用类型的.不用显示的使用Ref...
 回复 引用   

#5楼 2006-02-27 13:01 robin-hbifts[未注册用户]

再说了, .NET和C++的一个很大的区别就是GC.所有的对象的申请/释放,都是由GC来自动完成的,对于程序员而言,是不可能直接操作像C++中的指针样的东西的!

同上,C#中函数在传递参数时,对于对象(Class),是默认使用引用的方式传递的.
值类型就得强制使用Ref...

Out一般是用来把函数内的一个结果传给函数调用者的,因为return有只能返回一个值的限制.

Out和ref的最大区别在于,使用ref的参数一定要是一个实际的对象,也就是不能指向一个null,而out的话,可以直接使用一个指向null的变量(相当于COM中的 void**).
 回复 引用   

#6楼[楼主] 2006-02-27 13:14 Wu.Country@侠缘      

唉,,,,,,,,,,,,,,,,,,,,,
看来你们都没有仔细的看一下我最后留下的一个问题。

你们说的都是对的,而且都是大家都已经很清楚的东西!

但我的问题我已经说的很清楚了,这样的两句话:
SomeMethod(m_class2);
SomeMethod(ref m_class2);
在不同的条件下,会有完全不一样的结果!并不是默认的Ref!!!!!

希望读者能仔细阅读一下最后的一个例子,并这样用一下:
//////////////////////////////////////////////////////////
这里请一定注意这里的调用: ChangeData4(ref m_class2,m_class2);
读者自己试着这样试试:
ChangeData4(ref m_class1,m_class2);
ChangeData4(ref m_class2,m_class1);
ChangeData4(ref m_class1,m_class1);
然后再分析一下内存,可能会有很大的收获。
//////////////////////////////////////////////////////////
 回复 引用 查看   

#7楼 2006-02-27 13:14 A.Z      

.net对象只有构造没有析构的,不用那么紧张吧。在方法体里new一个参数,只有在OUt的场合下才会使用,起码得规则嘛。  回复 引用 查看   

#8楼[楼主] 2006-02-27 13:17 Wu.Country@侠缘      

还有,这里与GC对象也没有太大的关系,因为我前面已经说的很清楚了,正是因为
SomeMethod(m_class2);
SomeMethod(ref m_class2);
在不同的情况下,结果对内存产生一些负面的影响(GC会帮助我们处理),所以我们应该注意一下,并不是所有的引用对象都是默认用Ref的。。。。。
 回复 引用 查看   

#9楼 2006-02-27 13:19 robin-hbifts[未注册用户]

...晕.好好看了你的最后一个例子...
不用ref,对于引用类型,修改的是传进来参数的实际的对象中的值.
如果你这个时候再new一个对象,赋给参数,这个时候是不可能影响函数外面的对象的...

你如果想得到类似out的效果,那是一定要ref的...

....你那样的做法本来就是有问题的.........
 回复 引用   

#10楼[楼主] 2006-02-27 13:20 Wu.Country@侠缘      

To.A.z
那可不一定,上面的例子中,第二个引用,就算你不用New,它一样的会是一个临时的对象在内存里(但要注意,它是参数的一个副本)。。。。。。不管里用什么!
我这里没有对临时类TempClass进行初始化,如果初始化m_member为-1,结果会更加的明显。。。。。。
 回复 引用 查看   

#11楼[楼主] 2006-02-27 13:23 Wu.Country@侠缘      

To:robin-hbifts
不用ref,对于引用类型,修改的是传进来参数的实际的对象中的值.
如果你这个时候再new一个对象,赋给参数,这个时候是不可能影响函数外面的对象的...
==========
你再试试,你用ref class1
再new一个看看,一样的修改了函数外的m_class1,
而对于m_class2,默认是什么都不用,它会是临时的一个对象,不会影响外面的m_class1,更不会影响m_class2......

这就是我说的引用的引用,当我们用引用的引用来做为参数的时候,就不是默认的Ref了。。。。。。
 回复 引用 查看   

#12楼 2006-02-27 13:29 A.Z      

不会有人写这种代码的。  回复 引用 查看   

#13楼 2006-02-27 13:31 FangMing[未注册用户]

晕,这么简单的问题被你说的这么复杂?!
你一开始不说了吗:
1、Ref在作为参数调用函数之前,变量一定要赋值,否则会得到一个常规编译错误:使用了未赋值的变量。
2、在被调用函数内,以Ref引入的参数在返回前不必为它赋值。
3、Out在作为参数调用函数之前,变量可以不被赋值。
4、在被调用函数内,以Out引入的参数在返回前一定要至少赋值一次。

至于你说的问题,那就是应用类型和值类型的区别。
 回复 引用   

#14楼[楼主] 2006-02-27 13:31 Wu.Country@侠缘      

但愿是吧,,,,,,  回复 引用 查看   

#15楼[楼主] 2006-02-27 13:33 Wu.Country@侠缘      

To:FangMing
你看一下这个函数的运行:
ChangeData4(ref m_class2,m_class2);
这真的不仅仅是值类型与引用类型的问题。因为同样一个引用对象,用不同的方法调用,一个用ref,一个不用,得到的结果是截然不同的。
 回复 引用 查看   

#16楼 2006-02-27 13:37 装配脑袋      

我说了,ref/out对值类型和引用类型的作用都是一样的,不写按值传,写ref按引用传。你说的那些不同是值类型和引用类型自身不同带来的。

对值类型:
默认按值传,因此参数把值拷贝了一遍。
ref按引用传,因此参数是原变量的一个引用。

对引用类型:
默认按值传,因此参数把引用拷贝了一遍。
ref按引用传,因此参数是原引用的引用。

这就是问题的关键了,“引用的引用”与“拷贝引用”当然是不同的概念,你举那么多例子其实就是来自这一点。
 回复 引用 查看   

#17楼 2006-02-27 13:40 FangMing[未注册用户]

是一样的啊!不带ref的参数,你改变不了它的值,所以调用方法前是什么值,调用完它还是什么值(out也差不多,只是和ref有些区别,你已经说了)。
所以“因为同样一个引用对象,用不同的方法调用,一个用ref,一个不用,得到的结果是截然不同的”,这很正常啊!
注意,引用类型,它本身的值和它所引用的值是不同的。这个怎么说呢?那c++做列子好说些:一个指针的值和它指向的地址中的值是不同的。
 回复 引用   

#18楼 2006-02-27 13:47 装配脑袋      

“我们用Ref来传递引用类型数据的一个引用时,其实是使用的实例的一个副本。即:产生了即没有用Ref也没有用Out的效果。”

这是完全错误的,我看你是自己被绕进去了。
 回复 引用 查看   

#19楼[楼主] 2006-02-27 13:48 Wu.Country@侠缘      

值类型:
默认是值方式传递,
加ref时用引传递。
所以:SomeMethod(ref m_int);会对函数外的m_int有影响。
SomeMethod(m_int);不会对函数外的m_int有影响。。。这个大家都知道。

引用类型:
默认是引用方法,
用ref是什么呢?还是引用!!!
所以:SomefMethod(ref m_class);与SomeMethod(m_class)的效果应该是一样的吗???
默认是的。。。。

但上面的例子已经说明:当m_class2是另一个class的引用时:
SomeMethod(ref m_class2)与SomeMethod(m_class2)就不再一样了。。。。。

To:FangMing
你可能还没有十分的明白这里面的关系。

To:装配脑袋
这里可能就是你所说的,“引用的引用”与“拷贝引用”当然是不同的概念。
我只是希望读者能明白,引用类型并不是所有的情况都是默认用引用的,在我讨论的情况下,应该注意这样的问题,以免程序出现问题还不知道。而且以免白白的浪费内存。
 回复 引用 查看   

#20楼[楼主] 2006-02-27 13:50 Wu.Country@侠缘      

To:装配脑袋
我的:
“我们用Ref来传递引用类型数据的一个引用时,其实是使用的实例的一个副本。即:产生了即没有用Ref也没有用Out的效果。”

这里我的说的是SomeMethod(ref m_class2,m_class2)里的第二个参数,它产生的效果确实是产生了即没有使用Ref也没有Out的效果,也就是我们熟悉的:有点像值类型的值方式。。。。。
但又决对不是值方式。。。。
 回复 引用 查看   

#21楼 2006-02-27 13:52 装配脑袋      

默认是引用方法,
用ref是什么呢?还是引用!!!

这是不准确的,应该是:

默认是按值传递引用
用ref是按引用传递引用,就是引用的引用

我看倒是你好像还没明白?
 回复 引用 查看   

#22楼 2006-02-27 13:55 FangMing[未注册用户]

SomeMethod(ref m_class2)与SomeMethod(m_class2)
这两个用法当然不一样了。。。。。。
当然了,像你一开始那种用法,即使用了ref,但你本身在方法里面没有去改变m_classs2的值,那既然你都没有去变它,那它就没有变了。。。。。。
晕,都说了,引用类型,它本身的值和它所引用的值是不同的。
 回复 引用   

#23楼 2006-02-27 13:58 装配脑袋      

详细解释一遍:

int i = 100;

这里i是值类型,所以i就是100

引用类型

string s = "abc";

这里s 是到 实例"abc" 的一个 引用。 并不是s就等于"abc",s只 是 一 个 引 用

因此,如果有somemethod(s),那么这里按值传递s,注意s的值就是一个到"abc"的引用,所以你以为“默认是按引用”传的,但不是,这里是按值传的。s的值本身是个引用。

somemethod(ref s)这里传递的s是一个到s的引用,s本身就是到"abc"的引用,那么这里传递的是到s的引用,两层引用!而不是什么“相当于没有ref的效果”
 回复 引用 查看   

#24楼[楼主] 2006-02-27 14:00 Wu.Country@侠缘      

To:装配脑袋
默认是按值传递引用
用ref是按引用传递引用,就是引用的引用

那就是说:两种方法传递的效果是完全一样的了。。。。。
按值引用和按引用的引用!?
如果是这样,那么这个Ref对引用类型的数据还有什么意义呢???

还是看这个函数:
SomeMethod(ref m_class2,m_class2)
第一个参数,如果是用m_class1来调用,与用m_class2来调用都是一样的,但如果把第二个参数改用m_class1,那么结果就会不一样了。。。
而第二个参数:引用的引用。。。。它将做为函数内新的临时类的一个新实例。而且与传递的参数有同样的成员值,也就是一个副本。。。
 回复 引用 查看   

#25楼[楼主] 2006-02-27 14:03 Wu.Country@侠缘      

To: 装配脑袋
==========================
引用类型

string s = "abc";

这里s 是到 实例"abc" 的一个 引用。 并不是s就等于"abc",s只 是 一 个 引 用

因此,如果有somemethod(s),那么这里按值传递s,注意s的值就是一个到"abc"的引用,所以你以为“默认是按引用”传的,但不是,这里是按值传的。s的值本身是个引用。

somemethod(ref s)这里传递的s是一个到s的引用,s本身就是到"abc"的引用,那么这里传递的是到s的引用,两层引用!而不是什么“相当于没有ref的效果”
=========================
我看明白你的意思了。但你样的说明无法解释:为什么加Ref与不加Ref结果对引用类型数据都一样??!!!!
 回复 引用 查看   

#26楼 2006-02-27 14:04 装配脑袋      

好好看我的话吧,你会弄明白的……  回复 引用 查看   

#27楼 2006-02-27 14:20 A.Z      

C#语法没有必要再讨论了吧,可以聊聊怎样写出最少的代码。  回复 引用 查看   

#28楼[楼主] 2006-02-27 14:21 Wu.Country@侠缘      

TO:装配脑袋
我仔细的看了一下内存的情况:
用不用Ref对引用类型来说,真的是一样。
跟踪一下函数就知道了:SomeMethod(ref m_class2,m_class2)
在修改第一个参数的值的时候,第二个也一样的变了。
如果你有兴趣,可以用非安全代码,用&来对m_class1或者m_class2来运算的看一下,你就知道什么叫真正的引用的引用了。(呵呵,看来这样的讨论真的没有太大的必要了)

对于我所讨论的问题:
i_obj1 = new TempClass();
i_obj1.m_member = 41;
i_obj2 = new TempClass();
i_obj2.m_member = 42;
对于用Ref和不用Ref确实还是存在区别的。。。。。(输出结果已经说明问题了)
还是那句话:但愿不会有人写这样的代码。
 回复 引用 查看   

#29楼 2006-02-27 14:25 ???[未注册用户]

这是什么?!  回复 引用   

#30楼 2006-02-27 14:26 装配脑袋      

默认是按值传递引用,带ref是按引用传地引用,这两者当然存在区别了,难道我说半天白说了……
我的天,我还以为只有VB.NET的用户才会对这个问题困惑。还是那句话,ref无论对值类型还是对引用类型,原理都一样。所谓不同是值类型和引用类型的变量本身存在本质不同,要理解这个问题,先去把值类型和引用类型这两个概念弄得100%清楚再说
 回复 引用 查看   

#31楼[楼主] 2006-02-27 14:34 Wu.Country@侠缘      

TO:装配脑袋
用你的理论可以解释一下下面代码的输出内容吗?
=====================
static void Main(string[] args)
{
string m_string1 = "abc";
string m_string2 = m_string1;
ChangeData5(ref m_string2,m_string2);
Console.WriteLine("m_string1:{0},m_string2:{1}",m_string1,m_string2);
}
static void ChangeData5(ref string m_str1,string m_str2)
{
m_str1 = "change string1";
m_str2 = "change string2";
}
===============================================
它的输出内容与我所讨论的是一致的,我前面已经分析过原因了。
我想知道你是怎样理解这一段代码的。
虽然希望不会有人这样写代码。。。。。。。
 回复 引用 查看   

#32楼 2006-02-27 14:45 装配脑袋      

好,分析一下
string m_string1 = "abc"; 创建了一个实例"abc",m_string1是指向它的引用

string m_string2 = m_string1; 等号在此表示复制引用,因此现在m_string2也是指向"abc"的引用。

ChangeData5(ref m_string2, m_string2) 传了两个参数,第一个是按引用传递参数,所以传了一个指向m_string2的引用,第二个参数是按值传递参数,因此传递了一个m_string2的值,m_string2的值是什么呢?是一个指向"abc"的引用,好,因此第二个参数传递的是一个到"abc"的引用。

下面进入ChangeData5

m_str1 = "change string1";
m_str1 刚才解释了,就是指向m_string2的引用。因此改变它就等于改变m_string2,现在m_str1和m_string2已经全部指向新实例“change string1"了。

m_str2 = "change string2";
m_str2原来是什么呢?是一个指向"abc"的引用,现在让他指向"change string2"了。由于按值传递,这个m_str2根本不会影响到调用方传递的参数。

好了,结果很明显,m_string1还是刚才那个指向"abc"的引用,而m_string2已经被改成了指向"change string1"的一个引用,因此结果就正如你运行的一样,明白了吗?
 回复 引用 查看   

#33楼 2006-02-27 14:51 装配脑袋      

引用类型的变量,到底是修改引用,还是修改被引用的实例,这是有区别的

假设a是引用类型的变量

a = New SomeType()
这个修改了a,但是并 没 有修改a引用的实例

a.SomeChanges("abc")
这个修改了a引用的实例,但是 并 没 有修改a
 回复 引用 查看   

#34楼 2006-02-27 15:04 装配脑袋      

我知道你为什么理解错了!我看到你解释

SomeType obj1 = new SomeType();
SomeType obj2 = obj1;

的时候,你说obj2引用obj1,间接引用内存中那个SomeType实例。但这是错的!
=只会复制引用关系,不会增加一层引用,因此第二个执行完了以后,obj2是直接引用内存中那个SomeType实例,与obj1没关系。

你跟我一个兄弟犯了同样错误。
 回复 引用 查看   

#35楼[楼主] 2006-02-27 15:19 Wu.Country@侠缘      

我又花时间仔细分析了一下你的解释,而且做了好几个的测试。
最后你的解释都是对的。我也十再是找不出反例了。
虽然我们分析的过程不一样,但最后的结果和我一开始所讨论的是一样的。
可能理解方式不一样。
 回复 引用 查看   

#36楼[楼主] 2006-02-27 15:31 Wu.Country@侠缘      

To:装配脑袋
呵呵。。。。你的解释可能更容易理解一些。
你直接理解引用为一个可修改的值对象,所以对ref参数而言,有ref就是修改引用的值。没有就是修改引用实例的值。
 回复 引用 查看   

#37楼[楼主] 2006-02-27 15:41 Wu.Country@侠缘      

=============================
我知道你为什么理解错了!我看到你解释

SomeType obj1 = new SomeType();
SomeType obj2 = obj1;

的时候,你说obj2引用obj1,间接引用内存中那个SomeType实例。但这是错的!
=只会复制引用关系,不会增加一层引用,因此第二个执行完了以后,obj2是直接引用内存中那个SomeType实例,与obj1没关系。
=========================================
这个地方的理解,我确实有点偏差。
 回复 引用 查看   

#38楼 2006-02-27 15:49 A.Z      

装配脑袋 拯救了一个误入歧途的少年........  回复 引用 查看   

#39楼[楼主] 2006-02-27 15:54 Wu.Country@侠缘      

这回明白 装配脑袋 说的引用类型与值类型是一样的道理了。
呵呵呵,,,谢谢 装配脑袋 了。
希望读者能原谅我的过失。。。。。。。。
 回复 引用 查看   

#40楼 2006-02-27 16:00 robin-hbifts[未注册用户]

...说白了就是C++中的 void*和void **使用的区别...
 回复 引用   

#41楼 2006-02-27 20:13 sanni:mylove      

其实像这样的问题在c++中已经被林锐说得很清楚了,在c++中就是“野指针”的问题,在方法内部对指针赋值是带不出方法范围的~  回复 引用 查看   

#42楼 2006-08-17 17:49 菜鸟胖子[未注册用户]

这个问题弄得有点糊涂,看来装配脑袋的解释还是比较好理解的.
TempClass class1=new TempClass();
TempClass class2=class1;
class1本身是引用类型的,class12是复制引用,我原来一直把它看成是指向引用的引用,看来原来想错了.
传递给方法一个ref Obj,一个Obj,Obj本身是引用类型的,对于ref Obj来说,就有点像把普通内部肉类比较int a=1按引用传递给方法一个,如果在方法内部作了对a的修改,那么会反映到方法外,而对于Obj来说,是复制引用,所以会它作的修改是不会反映到方法外的,就有点像传值调用,也跟指针传递差不多,指针本身是按值传递的,所以对指针本身作的修改是不会反映到方法外的,而对指针所指向的内容作的修改却可以反映到方法外.

不知道我的理解对不对.
 回复 引用   

#43楼[楼主] 2006-08-18 08:40 Wu.Country@侠缘      

是的。你的理解是对的。
引用数据数据也有一个“地址”存放了指向的对象。
如果修改指向的对象,当然其它指向该对象的引用得到的值会相应的修改。
但如果修改“地址”,那么引用所指的对象就不是原来的那个了,再来修改,对于原来的对象就不会有什么改变了。
 回复 引用 查看   

#44楼 2006-08-23 00:47 ILoveCG[未注册用户]

参数传递机制区别为传递方向和传递方式,ref(in/out)(传递指针);out(out)(传递指针);ByValue(in)(传值)
using System;
class Class1
{
static void Main(string[] args)
{
TempClass m_class1 = new TempClass();
m_class1.m_member = 0;
TempClass m_class2 = m_class1;
ChangeData1( m_class2,ref m_class2);
Console.WriteLine("m_class1:{0},m_class2:{1}", m_class1.m_member, m_class2.m_member);
}
static void ChangeData1(TempClass i_obj1,ref TempClass i_obj2 )
{
//修改字段,引用指向未变
i_obj1.m_member = 41;
//修改引用指向但不会被传出
i_obj1 = new TempClass();
//修改字段,引用指向未变
i_obj2.m_member = 42;
//修改引用指向并传出,传入的引用被修改,指向一个新对象
i_obj2 = new TempClass();
}
}

class TempClass
{
public int m_member;
}
 回复 引用   

#45楼 2006-11-29 15:45 DrkBreeze

c# 的类型分两种:值类型 和 引用类型

值类型主要由两类组成:
Struct(结构)类型
枚举类型
结构类型包括用户定义的 struct 类型以及下列内置的简单类型:
Numeric(数值)类型
整型
浮点型
decimal
bool

引用类型的变量又称为对象,可存储对实际数据的引用。本节介绍以下用于声明引用类型的关键字:
class
interface
delegate
本节也介绍以下内置引用类型:
object
string

string 既是内置的值类型又是内置的引用类型; string aaa = "www"; 这样声明并初始化时是内置的值类型,当 string aaa = new string(new char[] {'a','b','c'});这样声明并初始化是内置的引用类型。

当传递的参数是引用类型时
change(ref 引用类型 型参)
change(引用类型 型参) 这两种调用一样的 ,都会改变所有的引用对象(引用三四次我也试过了)。c#默认就是引用传递。

例:
AAA a1 = new AAA();
a1.ID = 1;
AAA a2 = a1;
AAA a3 = a2;
// change(ref a3); 改变a3会改变 a1, a2
change(a3); // 改变 a3 仍然会改变 a1,a2
Console.WriteLine(a1.ID ); Console.WriteLine(a2.ID); nsole.WriteLine(a3.ID);

void change(AAA ac)
{
ac.ID = 12;
}

当传递的参数是值类型时
string str1 = "abc";
string str2 = m_string1;
string str3 = m_string2;
Change(ref str3); //仅改变 str3 自己本身 不改变 str1,str2
//Change(str3); //str1,str2,str3 都不会被改变
Console.WriteLine("m_string1 = {0}, m_string2 = {1}, m_string3 = {2}",m_string1,m_string2,m_string3);

void Change(string as)
{
as = "changed";
}

所以讨论来讨论去,如果没有分清楚 值类型和引用类型 ,感觉没有办法解释上面讨论的问题
 回复 引用   

#46楼[楼主] 2006-11-29 16:14 Wu.Country@侠缘      

@DrkBreeze
你的理解和我写这篇文章时是一样的。

当传递的参数是引用类型时
change(ref 引用类型 型参)
change(引用类型 型参) 这两种调用一样的 ,都会改变所有的引用对象(引用三四次我也试过了)。c#默认就是引用传递。

其实它们是不一样的,它们的区别与:
change(ref 值类型 型参)
change(值类型 型参)
是一样的!
你再把 @装配脑袋 的评论好好的读一下,你会明白的。
 回复 引用 查看   

#47楼 2007-09-04 00:51 壁虎[未注册用户]

@Wu.Country@侠缘
拜读,学习了。
对dotnet如何实现引用理解更进一步了。


另外对一些人(忘了谁)说一句:技术归技术,规则归规则。不要在讨论技术的时候,说规则里不会允许这样写的(也就是说这个不需要了解也没关系)。只有明白了技术上的实现,才知道规则为什么这样定。
 回复 引用