C#中ref关键字的用法总结
<p> ref表示引用的意思,C#中它有多种用法,这里简单总结一下:</p><p> <strong><span style="font-size: 18px">1、按引用传递参数</span></strong></p>
<p> 具体可见:C#中的值传递与引用传递(in、out、ref)</p>
<p> <strong><span style="font-size: 18px">2、引用局部变量</span></strong></p>
<p> 引用局部变量指的是在变量声明时使用ref关键字(或者使用<strong>ref readonly</strong>表示未只读),表示这个变量是另一个变量的引用,而不是值对象的赋值,或者引用类型的地址,这个引用可以理解为一个别名,操作这个别名对象与操作原始对象无异! </p>
<p> 引用局部变量声明时必须初始化,而初始化引用局部变量需要使用ref赋值运算符(= ref): </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">var</span> i =<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, 255, 1)">var</span> list = <span style="color: rgba(0, 0, 255, 1)">new</span> List<<span style="color: rgba(0, 0, 255, 1)">int</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)">声明引用局部变量</span>
<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> ref_i = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> i;
</span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">var</span> ref_list = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> list;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用引用局部变量等价于使用原变量,引用局部变量就是一个别名</span>
ref_i = <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine(i); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:2</span>
ref_list.Add(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine(list.Count);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:1</span>
ref_list = <span style="color: rgba(0, 0, 255, 1)">new</span> List<<span style="color: rgba(0, 0, 255, 1)">int</span>><span style="color: rgba(0, 0, 0, 1)">();
Console.WriteLine(list.Count);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:0</span></pre>
</div>
<p> 除此之外,我们还可以验证地址: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">var</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)">unsafe</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> j =<span style="color: rgba(0, 0, 0, 1)"> i;
</span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">var</span> m = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> i;
</span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">var</span> n = <span style="color: rgba(0, 0, 255, 1)">ref</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)">虽然将i赋值给j,但i、j是不同变量,地址不一样</span>
Console.WriteLine((<span style="color: rgba(0, 0, 255, 1)">int</span>)&i == (<span style="color: rgba(0, 0, 255, 1)">int</span>)&j);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span>
<span style="color: rgba(0, 0, 255, 1)">fixed</span> (<span style="color: rgba(0, 0, 255, 1)">int</span>* p1 = &m, p2 = &<span style="color: rgba(0, 0, 0, 1)">n)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用引用局部变量,m、n是i的一个别名,地址一样</span>
Console.WriteLine((<span style="color: rgba(0, 0, 255, 1)">int</span>)&<span style="color: rgba(0, 0, 0, 1)">i);
Console.WriteLine((</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)p1);
Console.WriteLine((</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">)p2);
}
}</span></pre>
</div>
<p> <strong><span style="font-size: 18px">3、引用返回值</span></strong></p>
<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)">、返回值不能为void,且需要使用ref关键字(或者ref readonly表示只读)修饰返回类型
</span><span style="color: rgba(128, 0, 128, 1)">2</span>、方法的每一个return语句需要是一个ref引用</pre>
</div>
<p> 例如: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Method(<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)"> i)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</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, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Invoke(<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)"> i)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> i;
}</span></pre>
</div>
<p> 因为引用返回值将返回值以引用的形式返回,调用方可以对返回值进行读写等操作,因此在return ref时规定: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">、返回值不能是null、常量等,必须是变量
</span><span style="color: rgba(128, 0, 128, 1)">2</span>、返回值的生命周期必须比当前方法长,也就是说返回值不能是方法内部定义的局部变量,可用的返回值可以来自静态字段、ref修饰的引用方法参数、数组参数中的成员等</pre>
</div>
<p> 例如: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Invoke(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">[] array)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> array[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">];
}</span></pre>
</div>
<p> 在调用时,有两种方式: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">int</span>[] source = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[] { <span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(128, 0, 128, 1)">2</span>, <span style="color: rgba(128, 0, 128, 1)">3</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)">不使用ref关键字,那么返回值采用值传递</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> i =<span style="color: rgba(0, 0, 0, 1)"> Invoke(source);
i </span>= <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine(</span><span style="color: rgba(0, 0, 255, 1)">string</span>.Join(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">,</span><span style="color: rgba(128, 0, 0, 1)">"</span>, source));<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:1,2,3
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用ref关键,得到的是引用</span>
<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">var</span> j = <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> Invoke(source);
j </span>= <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine(</span><span style="color: rgba(0, 0, 255, 1)">string</span>.Join(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">,</span><span style="color: rgba(128, 0, 0, 1)">"</span>, source));<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:0,2,3</span></pre>
</div>
<p> <strong>注:如果方法返回值使用ref readonly修饰,表示得到的引用是只读的</strong></p>
<p> <strong><span style="font-size: 18px">4、引用结构体</span></strong></p>
<p> 说到结构体,像int、bool等,与之形成对比的是类,两者都很多相似之处,比如都可以拥有属性、字段、方法等,但不同之处也有很多,比如在存储位置上: </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> 一般的,结构体的实例存储在栈中,引用类型存储在托管堆中
栈:空间比较小,但是读取速度快
堆:空间比较大,但是读取速度慢</span></pre>
</div>
<p> 其实,上面说的不完全对,比如在一个类中创建了一个int类型的字段,那么它和这个类的对象的其他数据一个保存在堆上的,换句话说,就是结构体可以保存在栈中,也可以保存在托管堆里!</p>
<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)">、引用结构体不能作为数组成员,即假如T是一个引用结构体类型,你不能声明T[]这样的数组变量
</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><span style="color: rgba(0, 0, 0, 1)">、引用结构体不能实现接口
</span><span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">、引用结构体不能装箱成System.ValueType(所有值类型隐式继承的父类)或者System.Object(所有类型隐式继承的父类),也就是说你无法直接使用Equals、ToString等隐式继承于父类的方法,若要使用,需要显示的重写,且重写方法中不能使用base关键字
</span><span style="color: rgba(128, 0, 128, 1)">5</span>、引用结构体不能作为类型参数,也就是说List<><span style="color: rgba(0, 0, 0, 1)">等中的类型参数不能是引用结构体
</span><span style="color: rgba(128, 0, 128, 1)">6</span><span style="color: rgba(0, 0, 0, 1)">、引用结构体的变量不能在Lambda表达和本地方法中使用
</span><span style="color: rgba(128, 0, 128, 1)">7</span>、引用结构体的变量不能在使用async修饰的方法中使用,但是可以在那些没有使用async关键字且返回Task或者Task<T><span style="color: rgba(0, 0, 0, 1)">类型的同步方法中使用
</span><span style="color: rgba(128, 0, 128, 1)">8</span>、引用结构体不能在迭代器中使用,也就是说yield return的对象不能是引用结构体</pre>
</div>
<p> 其实,仔细想想,上面的8点不就是在限制引用结构体保存在托管堆中去么?</p>
<p> 创建引用结构体只需要在struct关键字前使用ref关键字就可以了,而且结构体内部也可以拥有属性、字段、方法等等,例如: </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> CustomRef
{
</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)">public</span> CustomRef(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> count)
{
(Count, IsValid) </span>= (count, count > <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)">var</span> span = <span style="color: rgba(0, 0, 255, 1)">new</span> Span<<span style="color: rgba(0, 0, 255, 1)">int</span>>(<span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">]);
Inputs </span>=<span style="color: rgba(0, 0, 0, 1)"> span;
Outputs </span>=<span style="color: rgba(0, 0, 0, 1)"> span;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> IsValid;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Span<<span style="color: rgba(0, 0, 255, 1)">int</span>> Inputs;<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)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Count { <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<<span style="color: rgba(0, 0, 255, 1)">int</span>> Outputs { <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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">其他引用结构体属性</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Write()
{
</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> 注:如果使用ref的同时还使用了readonly关键字修饰结构体,那么readonly关键字需要写在ref前面,避免和ref readonly声明的引用局部变量冲突,如:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> <span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> CustomRef
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">其他成员</span>
}</pre>
</div>
<p> 因为引用结构体的数据保存在栈中,因此它的读写速度非常快,另一方面,栈中的数据销毁很快,而不是像托管堆一样,交给GC去回收,因此目前.net 的基础库中很多地方都已改为使用引用结构体来实现,值得一提的是,.net 内部已经给我提供了两个泛型的引用结构体:System.Span<T> 和 System.ReadOnlySpan<T>,这就感觉跟委托一样,提供了Action<>和Func<>,多数时候不需要我们自己去定义,此外,与这两个引用结构体对应的结构体是:Memory<T> 和 ReadOnlyMemory<T>,用法相似,只是一个是引用结构体,一个是普通的结构体罢了。</p>
<p> </p>
<p> <strong><span style="font-size: 14pt">结语 </span></strong></p>
<p> 熟练使用ref,可以让我提高代码性能,而且,还让我们可以将C#玩出新高度,比如下面的代码: </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)">var</span> str1 = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello</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)">var</span> str2 = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello</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)">ref</span> <span style="color: rgba(0, 0, 255, 1)">var</span> c = <span style="color: rgba(0, 0, 255, 1)">ref</span> MemoryMarshal.GetReference<<span style="color: rgba(0, 0, 255, 1)">char</span>><span style="color: rgba(0, 0, 0, 1)">(str1);
c </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">H</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
Console.WriteLine(str1);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:Hello</span>
Console.WriteLine(str2);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">输出:Hello</span>
}</pre>
</div>
<p> 虽然不知道看到这段代码的你能否理解其中的原理,但是从此,如果还有人跟你说string类型是不可变的,你不妨拿这段代码给他瞧瞧。</p>
<p> </p>
<p> 参考文档:</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/15043798.html
頁:
[1]