C#中ref关键字的用法总结
ref表示引用的意思,C#中它有多种用法,这里简单总结一下:
1、按引用传递参数
2、引用局部变量
引用局部变量指的是在变量声明时使用ref关键字(或者使用ref readonly表示未只读),表示这个变量是另一个变量的引用,而不是值对象的赋值,或者引用类型的地址,这个引用可以理解为一个别名,操作这个别名对象与操作原始对象无异!
引用局部变量声明时必须初始化,而初始化引用局部变量需要使用ref赋值运算符(= ref):
var i =1;//定义一个整型变量
var list = new List<int>();//定义一个引用类型变量 //声明引用局部变量
ref int ref_i = ref i;
ref var ref_list = ref list; //使用引用局部变量等价于使用原变量,引用局部变量就是一个别名
ref_i = 2;
Console.WriteLine(i); //输出:2
ref_list.Add(1);
Console.WriteLine(list.Count);//输出:1
ref_list = new List<int>();
Console.WriteLine(list.Count);//输出:0
除此之外,我们还可以验证地址:
var i = 1;
unsafe
{
var j = i;
ref var m = ref i;
ref var n = ref i; //虽然将i赋值给j,但i、j是不同变量,地址不一样
Console.WriteLine((int)&i == (int)&j);//false fixed (int* p1 = &m, p2 = &n)
{
//使用引用局部变量,m、n是i的一个别名,地址一样
Console.WriteLine((int)&i);
Console.WriteLine((int)p1);
Console.WriteLine((int)p2);
}
}
3、引用返回值
引用返回值表示一个方法的返回值是一个引用,而不是值类型对象的副本或者引用类型的地址,而一个方法要实现引用返回值,需要满足两个条件:
1、返回值不能为void,且需要使用ref关键字(或者ref readonly表示只读)修饰返回类型
2、方法的每一个return语句需要是一个ref引用
例如:
public ref int Method(ref int i)
{
return ref i;
}
//只读引用返回值
public ref readonly int Invoke(ref int i)
{
return ref i;
}
因为引用返回值将返回值以引用的形式返回,调用方可以对返回值进行读写等操作,因此在return ref时规定:
1、返回值不能是null、常量等,必须是变量
2、返回值的生命周期必须比当前方法长,也就是说返回值不能是方法内部定义的局部变量,可用的返回值可以来自静态字段、ref修饰的引用方法参数、数组参数中的成员等
例如:
public ref int Invoke(int[] array)
{
return ref array[0];
}
在调用时,有两种方式:
int[] source = new int[] { 1, 2, 3 };
//不使用ref关键字,那么返回值采用值传递
var i = Invoke(source);
i = 0;
Console.WriteLine(string.Join(",", source));//输出:1,2,3
//使用ref关键,得到的是引用
ref var j = ref Invoke(source);
j = 0;
Console.WriteLine(string.Join(",", source));//输出:0,2,3
注:如果方法返回值使用ref readonly修饰,表示得到的引用是只读的
4、引用结构体
说到结构体,像int、bool等,与之形成对比的是类,两者都很多相似之处,比如都可以拥有属性、字段、方法等,但不同之处也有很多,比如在存储位置上:
一般的,结构体的实例存储在栈中,引用类型存储在托管堆中
栈:空间比较小,但是读取速度快
堆:空间比较大,但是读取速度慢
其实,上面说的不完全对,比如在一个类中创建了一个int类型的字段,那么它和这个类的对象的其他数据一个保存在堆上的,换句话说,就是结构体可以保存在栈中,也可以保存在托管堆里!
所谓引用结构体,就是结构体的一种特殊形式,规定这种结构体只能存在于栈中,不能保存在堆中,因此对引用结构体做了一下限制:
1、引用结构体不能作为数组成员,即假如T是一个引用结构体类型,你不能声明T[]这样的数组变量
2、引用结构体不能声明为其它类或者非引用结构体的字段属性
3、引用结构体不能实现接口
4、引用结构体不能装箱成System.ValueType(所有值类型隐式继承的父类)或者System.Object(所有类型隐式继承的父类),也就是说你无法直接使用Equals、ToString等隐式继承于父类的方法,若要使用,需要显示的重写,且重写方法中不能使用base关键字
5、引用结构体不能作为类型参数,也就是说List<>等中的类型参数不能是引用结构体
6、引用结构体的变量不能在Lambda表达和本地方法中使用
7、引用结构体的变量不能在使用async修饰的方法中使用,但是可以在那些没有使用async关键字且返回Task或者Task<T>类型的同步方法中使用
8、引用结构体不能在迭代器中使用,也就是说yield return的对象不能是引用结构体
其实,仔细想想,上面的8点不就是在限制引用结构体保存在托管堆中去么?
创建引用结构体只需要在struct关键字前使用ref关键字就可以了,而且结构体内部也可以拥有属性、字段、方法等等,例如:
public ref struct CustomRef
{
//构造函数
public CustomRef(int count)
{
(Count, IsValid) = (count, count > 0);
var span = new Span<int>(new int[0]);
Inputs = span;
Outputs = span;
} public bool IsValid;
public Span<int> Inputs;//其他引用结构体字段
public int Count { get; set; }
public Span<int> Outputs { get; set; }//其他引用结构体属性 public void Write()
{
//代码
}
}
注:如果使用ref的同时还使用了readonly关键字修饰结构体,那么readonly关键字需要写在ref前面,避免和ref readonly声明的引用局部变量冲突,如:
public readonly ref struct CustomRef
{
//其他成员
}
因为引用结构体的数据保存在栈中,因此它的读写速度非常快,另一方面,栈中的数据销毁很快,而不是像托管堆一样,交给GC去回收,因此目前.net 的基础库中很多地方都已改为使用引用结构体来实现,值得一提的是,.net 内部已经给我提供了两个泛型的引用结构体:System.Span<T> 和 System.ReadOnlySpan<T>,这就感觉跟委托一样,提供了Action<>和Func<>,多数时候不需要我们自己去定义,此外,与这两个引用结构体对应的结构体是:Memory<T> 和 ReadOnlyMemory<T>,用法相似,只是一个是引用结构体,一个是普通的结构体罢了。
结语
熟练使用ref,可以让我提高代码性能,而且,还让我们可以将C#玩出新高度,比如下面的代码:
static void Main(string[] args)
{
var str1 = "hello";
var str2 = "hello";
ref var c = ref MemoryMarshal.GetReference<char>(str1);
c = 'H';
Console.WriteLine(str1);//输出:Hello
Console.WriteLine(str2);//输出:Hello
}
虽然不知道看到这段代码的你能否理解其中的原理,但是从此,如果还有人跟你说string类型是不可变的,你不妨拿这段代码给他瞧瞧。
参考文档:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref
C#中ref关键字的用法总结的更多相关文章
- C++中typename关键字的用法
我在我的 薛途的博客 上发表了新的文章,欢迎各位批评指正. C++中typename关键字的用法
- c#多线程中Lock()关键字的用法小结
本篇文章主要是对c#多线程中Lock()关键字的用法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段 ...
- 解读typescript中 super关键字的用法
解读typescript中 super关键字的用法 传统的js,使用prototype实现父.子类继承.如果父.子类有同名的方法,子类去调用父类的同名方法需要用 “父类.prototype.metho ...
- java中synchronized关键字的用法
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...
- Java关键字-----------------java中synchronized关键字的用法
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. j ...
- ref关键字的用法
ref 关键字通过引用(而非值)传递参数. 通过引用传递的效果是,对所调用方法中的参数进行的任何更改都反映在调用方法中. 例如,如果调用方传递本地变量表达式或数组元素访问表达式,所调用方法会将对象替换 ...
- C#中 ref 关键字的认识和理解
之前接手老项目的时候有遇到一些的方法参数中使用了ref关键字加在传参的参数前面的情况.对于新手,这里介绍和讲解一下ref的用法和实际效果. CLR中默认所有方法的参数传递方式都是传值,也就是说不管你传 ...
- C/C++中const关键字的用法及其与宏定义的比较
1.const关键字的性质 简单来说:const关键字修饰的变量具有常属性. 即它所修饰的变量不能被修改. 2.修饰局部变量 ; ; 这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用c ...
- C/C++中static关键字的用法
1.什么是static? static 是C/C++中很常用的修饰符,它被用来控制变量的存储方式和可见性. 1.1static的引入 我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它 ...
随机推荐
- Linux基础命令---wget下载工具
wget wget是一个免费的文件下载工具,可以从指定的URL下载文件到本地主机.它支持HTTP和FTP协议,经常用来抓取大量的网页文件. 此命令的适用范围:RedHat.RHEL.Ubuntu.Ce ...
- FastDFS分布式文件系统及源码解析
记录一次本人学习FastDFS-分布式文件系统的学习过程,希望能帮助到有需要的人. 首选得对此技术有个大概的了解,可以参考 https://www.cnblogs.com/centos2017/p/7 ...
- javascript将平行的拥有上下级关系的数据转换成树形结构
转换函数 var Littlehow = {}; /** * littlehow 2019-05-15 * 平行数据树形转换器 * @type {{format: tree.format, sort: ...
- centos7 docker 修改Nginx文件
1.docker 安装 nginx : docker安装Nginx还是很简单的,可以参考百度文章 ,或者参照docker安装mysql :https://www.cnblogs.com/jonrain ...
- ActiveMQ(二)——ActiveMQ的安装和基本使用
一:安装 2.启动之后成功 二.创建实例测试ActiveMQ 配置Maven所需的依赖 <dependency> <groupId>org.apache.activemq< ...
- 智龙开发板搭建llsp环境
智龙开发板搭建llsp(linux+lighttpd+sqlite3+php)环境 1. 准备 1. 智龙开发板V3 2. 软件编译环境:VirtualBox6+CentOS6.10-i386.min ...
- 机器学习算法中的评价指标(准确率、召回率、F值、ROC、AUC等)
参考链接:https://www.cnblogs.com/Zhi-Z/p/8728168.html 具体更详细的可以查阅周志华的西瓜书第二章,写的非常详细~ 一.机器学习性能评估指标 1.准确率(Ac ...
- 深度解析Spring Cloud Ribbon的实现源码及原理
Ribbon的核心作用就是进行请求的负载均衡,它的基本原理如下图所示.就是客户端集成Ribbon这个组件,Ribbon中会针对已经配置的服务提供者地址列表进行负载均衡的计算,得到一个目标地址之后,再发 ...
- 数据恢复binlog2sql
目录 一.原理及其使用 用途 闪回原理简析 binlog 有三种可选的格式: 来实例演习下来实例演习下 二.准备工作 一.原理及其使用 生产上误删数据.误改数据的现象也是时常发生的现象,作为运维这时候 ...
- [BUUCTF]PWN——others_babystack
others_babystack 附件 步骤: 例行检查,64位程序,开了挺多保护 本地试运行一下程序 64位ida载入,看main函数 1是read函数,存在栈溢出:2是puts函数,3退出 利用思 ...