• C# 7.0


    C# 7.0

    本文参考Roslyn项目中的Issue:#118

      1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法

      2. C# 7.0 新特性2: 本地方法

      3. C# 7.0 新特性3: 模式匹配

      4. C# 7.0 新特性4: 返回引用

    C#早在最初的发行版C# 1.0中(2002年1月),就借鉴并延续了C/C++中指针参数,原生允许将值类型数据的引用(指针)通过标记ref参数的形式,传递到方法体中。

    但对于方法内的值类型引用,该如何以引用的方式返回,却一直以来没有一个非常完美的解决方案,尽管这种用例非常少见。

    提一个简单的问题,我们需要获取三个int中的最大值的引用

    我们照惯例,回顾下C#7.0之前的做法:

    C/C++指针

    我们回归到C/C++中,这个问题没有什么好争议的,实现起来会很理所应当的是这样的:

    复制代码
    1 int* Max(int* first, int* second, int* third) {
    2   int* max = *first > *second ? first : second;
    3   return *max > *third ? max : third;
    4 }
    5 ....
    6 int a = 1, b = 2, c = 3;
    7 int* max = Max(&a, &b, &c);
    8 *max = 4; // c == 4;
    复制代码

    下面我们思考一下C#中怎么合理的翻译这段代码。

    /unsafe 指令

    可能有的童鞋看到C/C++指针,已经想到了.NET编译指令中,开启/unsafe指令,它允许C#直接访问内存。的确,只要在项目中勾选“Allow unsafe code”。

    就可以通过下面这种几乎和C/C++中一致方式来做到:

    复制代码
     1 unsafe static int* Max(int* first, int* second, int* third)
     2 {
     3     int* max = *first > *second ? first : second;
     4     return *max > *third ? max : third;
     5 }
     6 ....
     7 int a = 1, b = 2, c = 3;
     8 unsafe
     9 {
    10     int* max = Max(&a, &b, &c);
    11     *max = 4; // c == 4
    12 }
    复制代码

    但unsafe并不是C# 推荐使用的,它绕过了CLR的内存安全机制,指针的不安全滥用会被允许,容易使你的指针指到各种非预期的目标,比如允许访问已经返回(被释放)的调用栈(call stack),我们来做一个实验。

    复制代码
     1 unsafe static int* GetRef()
     2 {
     3     //Some codes
     4     int i = 4;
     5     return &i;
     6 }
     7 unsafe static void Main(string[] args)
     8 {
     9     int* num = GetRef();
    10     Console.WriteLine(*num); // 4
    11     //Some codes
    12     Console.WriteLine(*num); // 不可预期
    13 }
    复制代码

    这是非常典型的一种错误,当GetRef()的调用返回后,它的调用堆栈被释放,我们尝试获取它本地的引用(num)时,如果GetRef遗留在内存的栈结构侥幸没有被重新分配,我们依然可以获取到。

    但正常情况下,我们的逻辑一旦需要做一些其它处理(包括第一次Console.WriteLine()的调用本身),num所在的这块不安全内存自然会被覆盖。

    虽然这是一段本身错误的代码,但站在语言层面,并没有做任何完全可以做的规避。(C/C++中同样存在这个问题)

    返回模型对象

    当然,其实C#6.0及以前,我们还有一种比较常见的方案:将有必要返回引用的值类型封装在一个寄宿模型类中。

    由于对象以引用heap的地址传递,引用目标不在调用栈(call stack)上,不会由于函数返回而被释放。

    1 static HostModel Max(HostModel first, HostModel second, HostModel third)
    2 {
    3     HostModel max = first.Value > second.Value ? first : second;
    4     return max.Value > third.Value ? max : third;
    5 }

    这种类似做法被广泛应用在Model传递,DTO等场景中,无可厚非。。

    但是如果在性能要求敏感,且数据和逻辑结构简单的场景下,为一个简单数据凭空多了一组装箱和拆箱动作,以对象形式在heap中申请本没有必要的内存,是一种非常浪费和奢侈的做法。

    引用返回

    C#7.0 中引入了引用返回(ref return)的概念,允许C#方法中返回一个值类型的引用。

    Issue:#118。中给出了下面的例子:

    复制代码
     1 static ref int Max(ref int first, ref int second, ref int third)
     2 {
     3     ref int max = first > second ? ref first : ref second;
     4     return max > third ? ref max : ref third;
     5 }
     6 …
     7 int a = 1, b = 2, c = 3;
     8 Max(ref a, ref b, ref c) = 4;
     9 Debug.Assert(a == 1); // true
    10 Debug.Assert(b == 2); // true
    11 Debug.Assert(c == 4); // true
    复制代码

    这样,我们通过C#7.0,能直接将调用栈(call stack)上的引用返回。

    并且,对于体积较大的结构体(struct),返回引用比传递结构值要快很多,因为结构体的赋值会对整个结构进行拷贝。

    另外需要注意的是,ref return的引用,在语言层面附加规则,不允许返回方法内的局部变量的引用,换句话说,被返回的堆栈地址,必须低于当前方法的入口地址。

    总结

    我们从另一个侧面看这个feature,其实是对性能要求极致情况下出现的考虑,对于目前大多数的.NET应用中,其实用例非常局限,也并非以往.NET侧重的方面。。

    但是Roslyn项目在C#7.0设计初期就加入这个feature,是否隐含了更长远的考量?

    我们再看看微软最近的新闻就不难理解了,本月初(6月1日)微软在北京举办的开发者峰会上,Satya Nadella宣布建立物联网实验室,峰会上还发布了微软的IoT套件。

    近期微软还发布了Windows的IoT版本(Windows IoT),刚刚发布的.NET Core也允许跑在装有Windows IoT 的 Raspberry PI(树莓派)等设备上。

    在这些对惜内存如金的端设备上,C#想要有一席用武之地,不可避免的需要一改以往对内存的任性的一些设计,也就可以理解了。这或许是C#7.0加入ref return的一个重要的原因。

    本文链接:http://www.cnblogs.com/ylvict/p/5633480.html (转载请注明)

  • 相关阅读:
    DOM2DOM3续
    总结 @ 在 C# 中的用法 (装载)
    ORACLE10G卸载过程
    .net中访问oracle数据库的几种方式(转载)
    试图运行项目时出错,无法启动调试。没有正确安装调试器。请运行安装程序安装或修复调试器
    设计模式 构造器
    设计模式 抽象工厂
    linq中日期格式转换或者比较,程序报错说不支持方法的解决办法
    bootstrap图标字体不出来问题的解决办法
    JavaScript对象属性访问的两种方式
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5634916.html
Copyright © 2020-2023  润新知