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等隐式继承于⽗类的⽅法,若要使⽤,需要显⽰的重写,且重写⽅法中不
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
{
//其他成员
}
writeline输出数值变量
  因为引⽤结构体的数据保存在栈中,因此它的读写速度⾮常快,另⼀⽅⾯,栈中的数据销毁很快,⽽不是像托管堆⼀样,交给GC去回收,因此⽬前 的基础库中很多地⽅都已改为使⽤引⽤结构体来实现,值得⼀提的是, 内部已经给我提供了两个泛型的引⽤结构体: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类型是不可变的,你不妨拿这段代码给他瞧瞧。
  参考⽂档:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。