zoukankan      html  css  js  c++  java
  • C#中ref关键字的用法总结

      ref表示引用的意思,C#中它有多种用法,这里简单总结一下:

      1、按引用传递参数

      具体可见:C#中的值传递与引用传递(in、out、ref)

      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

       

    一个专注于.NetCore的技术小白
  • 相关阅读:
    js预编译
    JS防抖和节流模式的实际应用
    常见的几种数组去重的方法,总有一种适合你~
    调用微信扫一扫功能,踩坑'invalid signature'
    如何快速的vue init 属于自己的vue模板?
    如何做到在webpack打包vue项目后,在外部动态修改配置文件
    vue拖拽组件 vuedraggable API options实现盒子之间相互拖拽排序克隆clone
    js解析URL参数
    vue复选框 模拟checkbox多选全选,vue页面加载屏蔽花括号
    js毫秒转换天时分秒/动态倒计时
  • 原文地址:https://www.cnblogs.com/shanfeng1000/p/15043798.html
Copyright © 2011-2022 走看看