C#中的值传递与引用传递(in、out、ref)
<p> 在C#中,方法、构造函数可以拥有参数,当调用方法或者构造函数时,需要提供参数,而参数的传递方式有两种(以方法为例):</p><p> <span style="font-size: 14pt"><strong>值传递</strong></span></p>
<p> <strong>值类型对象</strong>传递给方法时,传递的是值类型对象的副本而不是值类型对象本身。常用的一个例子: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> MyStruct
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Invoke(MyStruct myStruct, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> i)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">MyStruct和int都是值类型</span>
myStruct.Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
i </span>= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Modify myStruct.Value = {myStruct.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Modify i = {i}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> myStruct = <span style="color: rgba(0, 0, 255, 1)">new</span> MyStruct();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
Invoke(myStruct, i);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Main myStruct.Value = {myStruct.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Main i = {i}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Modify myStruct.Value = 1
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Modify i = 2
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Main myStruct.Value = 0
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Main i = 0</span>
}</pre>
</div>
<p> 对于<strong>引用类型对象</strong>,很多人认为它是引用传递,其实不对,它也是按值传递的,但是不像值类型传递的是一个副本,引用类型传递的是一个地址(可以认为是一个整型数据),在方法中使用这个地址去修改对象的成员,自然就会影响到原来的对象,这也是很多人认为它是引用传递的原因,一个简单的例子: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyClass
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Invoke(MyClass myClass)
{
myClass.Value </span>= <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Modify myClass.Value = {myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> myClass = <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0</span>
<span style="color: rgba(0, 0, 0, 1)"> Invoke(myClass);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Main myClass.Value = {myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Modify myClass.Value = 1
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Main myClass.Value = 1</span>
}</pre>
</div>
<p> 需要注意的是,<strong>如果值类型对象中含有引用类型的成员,那么当值类型对象在传递给方法时,副本中克隆的是引用类型成员的地址,而不是引用类型对象的副本,所以在方法中修改此引用类型对象成员中的成员等也会影响到原来的引用类型对象。</strong></p>
<p> </p>
<p> <span style="font-size: 14pt"><strong>引用传递</strong></span></p>
<p> 引用传递可以理解为就是对象本身传递,而非一个<strong>副本或者地址</strong>,一般使用 in、out、ref 关键字声明参数是引用传递。 </p>
<p> 在说 in、out、ref 之前,先看看引用传递与值传递的区别,以更好的理解引用传递。</p>
<p> <span style="color: rgba(255, 0, 0, 1)"><strong>对于值类型对象</strong></span>,看一个最简单的变量值交换的例子: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Swap(<span style="color: rgba(0, 0, 255, 1)">int</span> i,<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> j)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> temp =<span style="color: rgba(0, 0, 0, 1)"> i;
i </span>=<span style="color: rgba(0, 0, 0, 1)"> j;
j </span>=<span style="color: rgba(0, 0, 0, 1)"> temp;
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> j = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
Swap(i, j);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">交换i,j</span>
Console.WriteLine($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">i={i}, j={j}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:</span><span style="color: rgba(0, 128, 0, 1)">i=1, j=2</span>
}</pre>
</div>
<p> 可以看到,i,j的值没有交换,因为值类型值传递传的是一个副本,这就好比,值对象的数据保存在一个房间中,比如桌子凳子椅子,作为方法参数传递时,会将这个房间包括里面的桌子凳子椅子全部克隆一份得到一个新房间,然后将这个新房间搬走使用,对新房间的装修挥霍自然对原房间没有影响。</p>
<p> 上面的代码可以翻译为: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> j = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这是Swap方法执行过程
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">先创建两个临时变量,赋值为i,j
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">在方法中使用的是这两个临时变量</span>
<span style="color: rgba(0, 0, 255, 1)">int</span> m = i, n =<span style="color: rgba(0, 0, 0, 1)"> j;
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> temp =<span style="color: rgba(0, 0, 0, 1)"> m;
m </span>=<span style="color: rgba(0, 0, 0, 1)"> n;
n </span>=<span style="color: rgba(0, 0, 0, 1)"> temp;
}
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">i={i}, j={j}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:i=1, j=2</span>
}</pre>
</div>
<p> 再看看引用传递的例子: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Swap(<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i,<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> j)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> temp =<span style="color: rgba(0, 0, 0, 1)"> i;
i </span>=<span style="color: rgba(0, 0, 0, 1)"> j;
j </span>=<span style="color: rgba(0, 0, 0, 1)"> temp;
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> j = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
Swap(</span><span style="color: rgba(0, 0, 255, 1)">ref</span> i, <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> j);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">i={i}, j={j}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:i=2, j=1</span>
}</pre>
</div>
<p> 可以看到,i,j的值交换成功,因为这里搬走使用的不再是克隆出来的新房间,而是原房间!</p>
<p> 这里的代码可以翻译为: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> j = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这是Swap方法执行过程
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">没有创建临时变量,在方法中直接使用i,j
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">注:这里是有创建临时变量,只是变量是引用,等价于原对象的一个别名</span>
<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> temp =<span style="color: rgba(0, 0, 0, 1)"> i;
i </span>=<span style="color: rgba(0, 0, 0, 1)"> j;
j </span>=<span style="color: rgba(0, 0, 0, 1)"> temp;
}
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">i={i}, j={j}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:i=2, j=1</span>
}</pre>
</div>
<p> <strong><span style="color: rgba(255, 0, 0, 1)">再看看引用类型对象</span></strong>,在值传递中,引用类型传递的是地址,在方法中可以通过这个地址去修改对象成员而影响到原对象的成员,但是无法影响到整个对象,看下面的例子: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyClass
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Invoke(MyClass myClass)
{
myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass() { Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> };
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
MyClass myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0</span>
<span style="color: rgba(0, 0, 0, 1)"> Invoke(myClass);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">myClass.Value={myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:myClass.Value=0</span>
}</pre>
</div>
<p> 可以看到,Main方法中将<span style="color: rgba(0, 0, 0, 1)">myClass对象传入Invoke方法,在Invoke方法中给Invoke方法赋值,但是这并没有影响到Main方法中的<span style="color: rgba(0, 0, 0, 1)">myClass</span>对象,这就好比,引用类型对象的数据保存在房间A中,作为方法参数传递时,会新建一个房间B,房间B保存的是房间A的地址,对房间B的任何修改会转向这个地址去修改,也就是房间A的修改,现在将房间B保存的地址换成房间C的地址,对房间B的操作自然跟房间A没有关系了。</span></p>
<p><span style="color: rgba(0, 0, 0, 1)"> 可以将上面的Main方法大致翻译成这样子:</span> </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
MyClass myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这是Invke方法执行过程
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">创建临时变量,在方法中使用临时变量</span>
MyClass temp =<span style="color: rgba(0, 0, 0, 1)"> myClass;
{
temp </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass() { Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> };
}
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">myClass.Value={myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:myClass.Value=0</span>
}</pre>
</div>
<p> 但如果是引用传递,结果就不一样了: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Invoke(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> MyClass myClass)
{
myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass() { Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> };
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
MyClass myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0</span>
Invoke(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> myClass);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">myClass.Value={myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:myClass.Value=1</span>
}</pre>
</div>
<p> 这是因为引用传递传的是对象本身,而不是地址,这就是说,在传递时,没有创建一个房间B,而是直接使用的房间A!(准确说,是给房间A取了一个别名)</p>
<p> 上面的Main方法可以翻译为: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
MyClass myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Value=0
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这是Invke方法执行过程
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">没有创建临时变量,在方法中直接使用myClass
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">注:这里是有创建临时变量,只是变量是引用,等价于原对象的一个别名</span>
<span style="color: rgba(0, 0, 0, 1)"> {
myClass </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass() { Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> };
}
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">myClass.Value={myClass.Value}</span><span style="color: rgba(128, 0, 0, 1)">"</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:myClass.Value=1</span>
}</pre>
</div>
<p> 可以理解为,引用类型对象的引用传递,其实就是给对象取了一个别名,其它与原对象一模一样。</p>
<p> 到这里,应该能对值传递和引用传递区分开了,接下来看看引用传递的 in、out、ref 的用法。</p>
<p> <strong><span style="font-size: 18px">in</span></strong></p>
<p> 在C#中,可以在下面这些地方使用in关键字:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span>、在泛型接口和委托的泛型参数中使用in关键字作为逆变参数,如:Action<<span style="color: rgba(0, 0, 255, 1)">in</span> T>
<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">、作为参数修饰符,这是接下来要说的
</span><span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">、在foreach中使用in迭代
</span><span style="color: rgba(128, 0, 128, 1)">4</span>、在Linq表达式中的join、from子句中使用in关键字</pre>
</div>
<p> 作为参数修饰符,in修饰的参数表示参数通过引用传递,但是参数是只读的,所以in修饰的参数在调用方法时必须先初始化! </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> MyStruct
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyClass
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Invoke(<span style="color: rgba(0, 0, 255, 1)">in</span> MyClass myClass, <span style="color: rgba(0, 0, 255, 1)">in</span> MyStruct myStruct, <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> i)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">in参数是只读的,下面的赋值将会报错
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">myClass = new MyClass();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">myStruct = new MyClass();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">i = 1;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">类成员可以直接读写</span>
myClass.Value = myClass.Value + <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">结构体成员只能读,直接写会报错</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> value = myStruct.Value + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">结构体成员在不安全代码中可以使用指针实现写操作</span>
<span style="color: rgba(0, 0, 255, 1)">unsafe</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">fixed</span> (MyStruct* p = &<span style="color: rgba(0, 0, 0, 1)">myStruct)
{
(</span>*p).Value = myStruct.Value + <span style="color: rgba(128, 0, 128, 1)">1</span>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">可以写</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}
}</span></pre>
</div>
<p> 在调用时,我们需要满足下面的条件: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">、传递之前变量必须进行初始化
</span><span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">、多数情况下调用in关键字可以省略,当使用in关键字时,变量类型应与参数类型一致
</span><span style="color: rgba(128, 0, 128, 1)">3</span>、可以使用常量作为参数,但是要求常量可以隐式转换成参数类型,编译器会生成一个临时变量来接收这个常量,然后使用这个临时变量调用方法</pre>
</div>
<p> 如: </p>
<div class="cnblogs_code">
<pre> MyClass myClass = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyClass();
MyStruct myStruct </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyStruct();
</span><span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
Invoke(</span><span style="color: rgba(0, 0, 255, 1)">in</span> myClass, <span style="color: rgba(0, 0, 255, 1)">in</span> myStruct, <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> i);
Invoke(myClass, myStruct, i);
Invoke(</span><span style="color: rgba(0, 0, 255, 1)">in</span> myClass, <span style="color: rgba(0, 0, 255, 1)">in</span> myStruct, <span style="color: rgba(128, 0, 128, 1)">2</span>);</pre>
</div>
<p> <strong><span style="font-size: 18px">out</span></strong></p>
<p> 在C#中,out参数可以用作:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span>、在泛型接口和委托的泛型参数中使用out关键字作为协变参数,如:Func<<span style="color: rgba(0, 0, 255, 1)">out</span> T>
<span style="color: rgba(128, 0, 128, 1)">2</span>、作为参数修饰符,这是接下来要说的</pre>
</div>
<p> 作为参数修饰符,out修饰的参数表示参数通过引用传递,但是参数是必须是一个变量,且在方法中必须给这个变量赋值,但是在调用方法时无需初始化: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> MyStruct
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyClass
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Value { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Invoke(<span style="color: rgba(0, 0, 255, 1)">out</span> MyClass myClass, <span style="color: rgba(0, 0, 255, 1)">out</span> MyStruct myStruct, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> i)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">out参数必须在返回之前赋一个值</span>
myClass = <span style="color: rgba(0, 0, 255, 1)">new</span> MyClass() { Value = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> };
myStruct </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> MyStruct() { Value = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)"> };
i </span>= <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">赋值之后,类成员、结构体成员都可以直接读写</span>
}</pre>
</div>
<p> 在调用时: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">、必须声明out关键字,且变量类型应与参数类型一致
</span><span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">、变量无需初始化,只需声明即可
</span><span style="color: rgba(128, 0, 128, 1)">3</span>、如果不关注out参数的返回值,我们常使用弃元</pre>
</div>
<p> 例如:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">参数需要初始化</span>
<span style="color: rgba(0, 0, 0, 1)"> MyClass myClass;
MyStruct myStruct;
</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> i;
Invoke(</span><span style="color: rgba(0, 0, 255, 1)">out</span> myClass, <span style="color: rgba(0, 0, 255, 1)">out</span> myStruct, <span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)"> i);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">等价写法</span><span style="color: rgba(0, 0, 0, 1)">
Invoke(</span><span style="color: rgba(0, 0, 255, 1)">out</span> MyClass myClass, <span style="color: rgba(0, 0, 255, 1)">out</span> MyStruct myStruct, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> i);
</span><span style="color: rgba(0, 0, 255, 1)">bool</span> isInt = <span style="color: rgba(0, 0, 255, 1)">long</span>.TryParse(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">out</span> _);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断字符串是否是整型而不需要结果</span>
<span style="color: rgba(0, 0, 255, 1)">bool</span> isBool = <span style="color: rgba(0, 0, 255, 1)">bool</span>.TryParse(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">true</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">out</span> _);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">判断字符串是否是布尔型而不关注结果</span></pre>
</div>
<p> <strong><span style="font-size: 18px">ref</span></strong></p>
<p> ref关键字的用法有很多,具体可见:C#中ref关键字的用法</p>
<p> 作为参数修饰符,ref修饰的参数表示参数通过引用传递,但是参数是必须是一个变量。</p>
<p> ref 可以看做是 in 和 out 的结合体,但是与 in 和 out 又有些区别: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span>、<span style="color: rgba(0, 0, 255, 1)">ref</span>和<span style="color: rgba(0, 0, 255, 1)">in</span>都是引用传递,而且要求调用方法前需要提前初始化,但是与<span style="color: rgba(0, 0, 255, 1)">in</span>不同的是,调用时<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)">关键字不能省略,且参数必须是变量,不能是常量
</span><span style="color: rgba(128, 0, 128, 1)">2</span>、<span style="color: rgba(0, 0, 255, 1)">ref</span>和<span style="color: rgba(0, 0, 255, 1)">out</span>都是引用传递,且在调用是,<span style="color: rgba(0, 0, 255, 1)">ref</span>和<span style="color: rgba(0, 0, 255, 1)">out</span>关键字不能省略,且参数必须是变量,不能是常量,但是<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)">要求调用方法前需要提前初始化,且无需在调用方法结束前赋值
</span><span style="color: rgba(128, 0, 128, 1)">3</span>、与<span style="color: rgba(0, 0, 255, 1)">in</span>和<span style="color: rgba(0, 0, 255, 1)">out</span>不同的是,在调用方法中时,可以读写整个<span style="color: rgba(0, 0, 255, 1)">ref</span>参数对象及它的成员</pre>
</div>
<p> 看看上面变量值交换的例子应该就清晰了。</p>
<p> <strong><span style="font-size: 18px">in、out、ref的限制</span></strong></p>
<p> C#中规定,引用传递(即in、out、ref)使用时有下面的限制: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span>、异步方法,即使用<span style="color: rgba(0, 0, 255, 1)">async</span>修饰的方法中,参数不能使用<span style="color: rgba(0, 0, 255, 1)">in</span>、<span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">、<span style="color: rgba(0, 0, 255, 1)">ref</span>关键字,但是可以在那些没有使用async关键字且返回Task或者Task<T>类型的同步方法中使用
</span><span style="color: rgba(128, 0, 128, 1)">2</span>、迭代器方法,即使用<span style="color: rgba(0, 0, 255, 1)">yield return</span>和<span style="color: rgba(0, 0, 255, 1)">yield break</span>返回迭代对象的方法中,,参数不能使用<span style="color: rgba(0, 0, 255, 1)">in</span>、<span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)">、<span style="color: rgba(0, 0, 255, 1)">ref</span>关键字
</span><span style="color: rgba(128, 0, 128, 1)">3</span>、如果拓展方法的第一个参数(<span style="color: rgba(0, 0, 255, 1)">this</span>)是结构体,且非泛型参数,则可使用<span style="color: rgba(0, 0, 255, 1)">in</span>关键字,否则不能使用in关键字
<span style="color: rgba(128, 0, 128, 1)">4</span>、拓展方法的第一个参数(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)不能使用<span style="color: rgba(0, 0, 255, 1)">out</span>关键字
</span><span style="color: rgba(128, 0, 128, 1)">5</span>、如果拓展方法的第一个参数(<span style="color: rgba(0, 0, 255, 1)">this</span>)非结构体,也非约束为结构体的泛型参数,则不能使用<span style="color: rgba(0, 0, 255, 1)">ref</span>关键字</pre>
</div>
<p> 此外,in、out、ref不能作为重载的标识,也就是说,如果两个方法,除了这三个关键字修饰的不同,其他如方法名,参数个数、类型等都相同,但是不能算重载: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下面的三个方法,除了in、out、ref,其他都一样,但是不能算重载,编译不通过</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method1(<span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> str) { }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method1(<span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str) { str = <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method1(<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> str) { }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下面的三个方法,除了in、out、ref,其他都一样,但是不能算重载,编译不通过</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i) { i = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i) { str = <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i) { }</pre>
</div>
<p> 但是,一个不使用in、out、ref使用的方法,和一个使用in、out、ref参数的方法可以构成重载: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下面的两个方法算重载,调用这样的重载,需要在调用是指定in、out、ref来区分调用</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method1(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> str) { }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method1(<span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str) { }<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">可以使用in、out、ref
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">下面的三个方法算重载,调用这样的重载,需要在调用是指定in、out、ref来区分调用</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">int</span> i) { i = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i) { i = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Method2(<span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">string</span> str, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">int</span> i) { i = <span style="color: rgba(128, 0, 128, 1)">0</span>; }</pre>
</div>
<p> </p>
<p> 参考文档:</p>
<p> https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-parameter-modifier</p>
<p> https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier</p>
<p> https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref</p>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
<div class="lightSignature">
<span>一个专注于.NetCore的技术小白</span>
</div><br><br>
来源:https://www.cnblogs.com/shanfeng1000/p/15043795.html
頁:
[1]