C# 7.0 新特性4: 返回引用
本文参考Roslyn项目中的Issue:#118。
1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法
C#早在最初的发行版C# 1.0中(2002年1月),就借鉴并延续了C/C++中指针参数,原生允许将值类型数据的引用(指针)通过标记ref参数的形式,传递到方法体中。
但对于方法内的值类型引用,该如何以引用的方式返回,却一直以来没有一个非常完美的解决方案,尽管这种用例非常少见。
提一个简单的问题,我们需要获取三个int中的最大值的引用。
我们照惯例,回顾下C#7.0之前的做法:
C/C++指针
我们回归到C/C++中,这个问题没有什么好争议的,实现起来会很理所应当的是这样的:
int* Max(int* first, int* second, int* third) {
int* max = *first > *second ? first : second;
return *max > *third ? max : third;
}
....
int a = , b = , c = ;
int* max = Max(&a, &b, &c);
*max = ; // c == 4;
下面我们思考一下C#中怎么合理的翻译这段代码。
/unsafe 指令
可能有的童鞋看到C/C++指针,已经想到了.NET编译指令中,开启/unsafe指令,它允许C#直接访问内存。的确,只要在项目中勾选“Allow unsafe code”。
就可以通过下面这种几乎和C/C++中一致方式来做到:
unsafe static int* Max(int* first, int* second, int* third)
{
int* max = *first > *second ? first : second;
return *max > *third ? max : third;
}
....
int a = , b = , c = ;
unsafe
{
int* max = Max(&a, &b, &c);
*max = ; // c == 4
}
但unsafe并不是C# 推荐使用的,它绕过了CLR的内存安全机制,指针的不安全滥用会被允许,容易使你的指针指到各种非预期的目标,比如允许访问已经返回(被释放)的调用栈(call stack),我们来做一个实验。
unsafe static int* GetRef()
{
//Some codes
int i = ;
return &i;
}
unsafe static void Main(string[] args)
{
int* num = GetRef();
Console.WriteLine(*num); // 4
//Some codes
Console.WriteLine(*num); // 不可预期
}
这是非常典型的一种错误,当GetRef()的调用返回后,它的调用堆栈被释放,我们尝试获取它本地的引用(num)时,如果GetRef遗留在内存的栈结构侥幸没有被重新分配,我们依然可以获取到。
但正常情况下,我们的逻辑一旦需要做一些其它处理(包括第一次Console.WriteLine()的调用本身),num所在的这块不安全内存自然会被覆盖。
虽然这是一段本身错误的代码,但站在语言层面,并没有做任何完全可以做的规避。(C/C++中同样存在这个问题)
返回模型对象
当然,其实C#6.0及以前,我们还有一种比较常见的方案:将有必要返回引用的值类型封装在一个寄宿模型类中。
由于对象以引用heap的地址传递,引用目标不在调用栈(call stack)上,不会由于函数返回而被释放。
static HostModel Max(HostModel first, HostModel second, HostModel third)
{
HostModel max = first.Value > second.Value ? first : second;
return max.Value > third.Value ? max : third;
}
这种类似做法被广泛应用在Model传递,DTO等场景中,无可厚非。。
但是如果在性能要求敏感,且数据和逻辑结构简单的场景下,为一个简单数据凭空多了一组装箱和拆箱动作,以对象形式在heap中申请本没有必要的内存,是一种非常浪费和奢侈的做法。
引用返回
C#7.0 中引入了引用返回(ref return)的概念,允许C#方法中返回一个值类型的引用。
Issue:#118。中给出了下面的例子:
static ref int Max(ref int first, ref int second, ref int third)
{
ref int max = first > second ? ref first : ref second;
return max > third ? ref max : ref third;
}
…
int a = , b = , c = ;
Max(ref a, ref b, ref c) = ;
Debug.Assert(a == ); // true
Debug.Assert(b == ); // true
Debug.Assert(c == ); // 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 (转载请注明)
目前(2016年7月)C#7.0还未正式发布,大家如果想体验部分特性,可以去下载VS15预览版,最终发布的语法可能和本文中提及的有所不同,最新动态请大家关注Roslyn项目。
C# 7.0 新特性4: 返回引用的更多相关文章
- C# 7.0 新特性1: 基于Tuple的“多”返回值方法
本文基于Roslyn项目中的Issue:#347 展开讨论. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# 7.0 新特性3: ...
- C# 7.0 新特性2: 本地方法
本文参考Roslyn项目中的Issue:#259. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# 7.0 新特性3: 模式匹配 ...
- C# 7.0 新特性3: 模式匹配
本文参考Roslyn项目Issue:#206,及Docs:#patterns. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# ...
- [C#]6.0新特性浅谈
原文:[C#]6.0新特性浅谈 C#6.0出来也有很长一段时间了,虽然新的特性和语法趋于稳定,但是对于大多数程序猿来说,想在工作中用上C#6.0估计还得等上不短的一段时间.所以现在再来聊一聊新版本带来 ...
- 返璞归真 asp.net mvc (9) - asp.net mvc 3.0 新特性之 View(Razor)
原文:返璞归真 asp.net mvc (9) - asp.net mvc 3.0 新特性之 View(Razor) [索引页][源码下载] 返璞归真 asp.net mvc (9) - asp.ne ...
- 返璞归真 asp.net mvc (6) - asp.net mvc 2.0 新特性
原文:返璞归真 asp.net mvc (6) - asp.net mvc 2.0 新特性 [索引页][源码下载] 返璞归真 asp.net mvc (6) - asp.net mvc 2.0 新特性 ...
- C# 7.0 新特性:本地方法
C# 7.0:本地方法 VS 2017 的 C# 7.0 中引入了本地方法,本地方法是一种语法糖,允许我们在方法内定义本地方法.更加类似于函数式语言,但是,本质上还是基于面向对象实现的. 1. 本地方 ...
- C#6.0,C#7.0新特性
C#6.0新特性 Auto-Property enhancements(自动属性增强) Read-only auto-properties (真正的只读属性) Auto-Property Initia ...
- [翻译] C# 8.0 新特性
原文: Building C# 8.0 [译注:原文主标题如此,但内容大部分为新特性介绍,所以意译标题为 "C# 8.0 新特性"] C# 的下一个主要版本是 8.0.我们已经为它 ...
随机推荐
- 大型网站的 HTTPS 实践(1):HTTPS 协议和原理
转自:http://op.baidu.com/2015/04/https-s01a01/ 1 前言 百度已经于近日上线了全站 HTTPS 的安全搜索,默认会将 HTTP 请求跳转成 HTTPS.本文重 ...
- html5 如何进行自定义属性的定义和查询
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>T ...
- Hello, AnnsShadow!
Hello! 发现这个神奇的园子快一年了,自己的学习历程磕磕碰碰也过了一年了,想想,这么久了,是时候做些记录做个分享者了. 从一开始的只敢看Blog,到现在自己发表一下自己的所感所想,算是一种成长了吧 ...
- js和jquery页面初始化加载函数的方法及先后顺序
运行下面代码.弹出A.B.C.D.E的顺序:A=B=C>D=E. jquery:等待页面加载完数据,以及页面部分元素(不包括图片.视频), js:是页面全部加载完成才执行初始化加载. <! ...
- iOS打包Framework真机和模拟器兼容合并版本 - 详细攻略步骤
打包Framework,测试时: 1.用模拟器打包,测试时只能跑在模拟器 2.用真机打包,测试时只能跑在真机 那么怎么做到一个版本兼容以上两种场景呢? 解决如下: 1.打开终端 2.输入 lipo ...
- Jmeter之Bean shell使用(二)
上一篇Jmeter之Bean shell使用(一)简单介绍了下Jmeter中的Bean shell,本文是对上文的一个补充,主要总结下常用的几种场景和方法,相信这些基本可以涵盖大部分的需求.本节内容如 ...
- 009.CentOS 6.7安装运行netmap
一.netmap简介: 1.netmap是一个高性能收发原始数据包的框架,由Luigi Rizzo等人开发完成,其包含了内核模块以及用户态库函数.其目标是,不修改现有操作系统软件以及不需要特殊硬件支持 ...
- log4j 日志信息的引入(通用版)——解决项目运行过程中的日志信息
定义 log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件,甚至是套接口服务器.NT的事件记录器.UNIX Syslog守护进程 ...
- XSS Payload知识备忘
参考资料:<白帽子讲Web安全>吴翰清 著 参见: 百度百科 http://baike.baidu.com/view/50325.htm 维基百科 http://zh.wikipedia. ...
- oracle操作之传输表空间
一.传输表空间概述 什么是传输表空间,传输表空间技术始于oracle9i,不论是数据字典管理的表空间还是本地管理的表空间,都可以使用传输表空间技术:传输表空间不需要在源数据库和目标数据库之间具有同样的 ...