本文要点

  • 应遵循《.NET设计规范:.NET约定惯用法与模式》一书。和十年前第一版出版时一样,书中给出的原则在当前依然有指导意义。
  • API设计是最重要的。设计不好的API会在极大地增加软件缺陷,同时降低可重用性。
  • 时刻牢记“良性循环”(Pit of Success)这一哲理:让正确的事情更易于做,让犯错误更加困难。
  • 移除“线路噪音”(Line Noise)和“样板”(Boilerplate)代码,聚焦于对业务逻辑的关注。
  • 出于性能考虑而牺牲代码清晰度前,请认真考虑一下。

C# 7是一个重大更新,其中提供了很多有意思的新功能。虽然已有大量的文章介绍这些功能可以做什么,但是鲜有文章介绍应如何使用这些功能。本文将过一遍《.NET设计规范:.NET约定惯用法与模式》 (译者注:英文书名为“Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries”)一书中给出的指导原则,力图更好地使用C# 7的新特性。

元组返回(Tuple Returns)

通常在C#编程中,一个函数返回多个值实现起来十分繁琐。一种做法是使用输出参数,这只适用于暴露异步方法的情况。另一种做法是使用 Tuple<T>。创建Tuple<T>过于啰嗦,需要做内存分配,并且Tuple的字段没有描述性名字。也可以使用自定义的结 构体。虽然结构体在性能上要优于元组,但是大量使用一次性类型会将代码弄得一团糟。而使用具有动态特性的匿名类型,存在性能不好的问题,还缺少静态类型检 查。

在C# 7中新提供了元组返回语法,它解决了全部上述问题。下面给出一个基本语法的例子:

public (string, string) LookupName(long id) // tuple return type
{
return ("John", "Doe"); //元组常值。
}
var names = LookupName();
var firstName = names.Item1;
var lastName = names.Item2;

该函数的实际返回类型是ValueTuple<string, string>。正如名称所示,ValueTuple<string, string>类似于Tuple<T>类,是一个轻量级的结构体。它解决了类型膨胀(Type Bloat)问题,但是依然没有解决描述性名称这一困扰Tuple<T>的问题。我们看一下如下的例子:

public (string First, string Last) LookupName(long id)
var names = LookupName(0);
var firstName = names.First;
var lastName = names.Last;

其中的返回类型依然是ValueTuple<string, string>,但是现在编译器在函数中添加了一个TupleElementNames属性。这样调用该函数的代码就可以使用描述性名称,而不再是Item1或Item2这样的名称了。

警告: TupleElementNames属性只能由编译器赋予。如果返回类型上使用了反射,你将只能看到裸的ValueTuple<T>结构体。因为在获得结果时,属性是位于函数本身上,而这个信息丢失了。

编译器会尽可能维护额外类型的幻象。例如,给出如下这些声明:

var a = LookupName(0);
(string First, string Last) b = LookupName(0);
ValueTuple<string, string> c = LookupName(0);
(string make, string model) d = LookupName(0);

在编译器看来,a和b同是(string First, string Last)。鉴于c被显式声明为ValueTuple<string, string>,因此不存在c.First属性。

该例中d的赋值语句展示了这一设计的失灵之处,即会在一定程度上导致缺失类型安全。字段意外地重命名是一个非常容易发生的问题,一个元组可以错误地 指定给另一个恰好具有同样形状的元组。这同样是由于编译器没有真正地将(string First, string Last)和(string make, string model)区分为不同的类型。

ValueTuple是可变的

有意思的是, ValueTuple是可变的。Mads Torgersen给出了这样的解释:

为什么通常可变结构体是不好的,不要应用于元组?下面给出原因。

如果你按正常的封装方式编写了一个可变结构体,并且其中具有私有的状态,还有公开的修改器(Mutator)属性和方法,那么你可能就会陷入一些严重的错误中。因为只要结构体是保持在只读变量中,那么修改器就会默默地工作于结构体的一个拷贝上!

但是元组的确有公开的可变字段。它在设计上并未考虑修改器,因此不存在出现上述现象的风险。

此外,ValueTuple是结构体,而结构体在传递时需要进行拷贝。结构体并不直接在线程间共享,也不承担“共享可变状态”的风险。这不同于System.Tuple家族的类型,这些类型也是类。为确保线程安全,需要这些类型是不可变的。

注意,这里Torgersen所指的是“字段”,而不是“属性”。对于使用元组返回函数结果的反射库,这会导致问题。

元组返回的指导原则

  • 当字段列表规模较小并不会发生更改时,考虑使用元组返回,而不是out参数。
  • 对元组返回中的描述性名字使用帕斯卡拼写法(PascalCase),这会使得元组字段看上去就像是正常的类和结构中的属性。
  • 在不进行解析就读取元组返回时,使用var,以避免意外地误标字段。
  • X 如果想要对返回值使用反射,应避免返回值元组。
  • X 如果在未来的版本中可能会返回额外的字段,那么就不要在公开API上使用元组返回。在元组返回中添加字段是一种破坏性变更。

----------------------------------------------------节选自infoQ:C# 7编程模式与实践-----------------------------------------------------------------

C# 7中函数多值返回_转自InfoQ的更多相关文章

  1. Go中函数作为值、类型传递。

    在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型 type typeName func(input1 inputType1 , input ...

  2. JavaScript中函数作为值

    function myfunc() { // .. } 这是个函数,这样理解, myfunc只是外层作用域的一个变量,指向刚刚声明的function. 也就是说,function本身就是一个值, 就像 ...

  3. java中函数是值传递还是引用传递?

    相信有些同学跟我一样,曾经对这个问题很疑惑.在网上也看了一些别人说的观点,评论不一.有说有值传递和引用传递两种,也有说只有值传递的,这里只说下个人见解 先看一个例子 public class Test ...

  4. [转]Python中函数的值传递和引用传递

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...

  5. 2013年6月19日星期三java中函数地址值传递

    今天代码审核时确认了一个问题,理解了java中string和stringbuffer赋值问题,看到一个帖子很好,摘录如下: 理解这两个例子需要分清实参和形参的区别,引用和对象的区别 第一个例子的内部执 ...

  6. 《挑战30天C++入门极限》C++运算符重载函数基础及其值返回状态

        C++运算符重载函数基础及其值返回状态 运算符重载是C++的重要组成部分,它可以让程序更加的简单易懂,简单的运算符使用可以使复杂函数的理解更直观. 对于普通对象来说我们很自然的会频繁使用算数运 ...

  7. c语言中函数的简单介绍

    c语言中函数的介绍: 函数,简单的说就是代码的打包.存放在一个地方,当需要的时候调用. 函数分类: 1.无参无返回值函数 void func() 2.无参有返回值函数  int func() 3.有参 ...

  8. C语言中函数和指针的參数传递

    近期写二叉树的数据结构实验.想用一个没有返回值的函数来创建一个树,发现这个树就是建立不起来,那么我就用这个样例讨论一下c语言中指针作为形參的函数中传递中隐藏的东西. 大家知道C++中有引用的概念,两个 ...

  9. C#中 多线程执行含有返回值的函数

    C# 中,传统的多线程并不支持多线程执行含有返回结果的函数.虽然可以通过制作外壳类来使得返回结果得以保留,但如果一定时间内函数未执行完,简单的外壳类可能就无法满足需求了. class netHelpe ...

随机推荐

  1. [转]vue全面介绍--全家桶、项目实例

    慢慢了解vue及其全家桶的过程 原文http://blog.csdn.net/zhenghao35791/article/details/67639415 简介 “简单却不失优雅,小巧而不乏大匠”.  ...

  2. Scala学习之路 (十)Scala的Actor

    一.Scala中的并发编程 1.Java中的并发编程 ①Java中的并发编程基本上满足了事件之间相互独立,但是事件能够同时发生的场景的需要. ②Java中的并发编程是基于共享数据和加锁的一种机制,即会 ...

  3. SQL 提高性能

    参考博客:http://www.cnblogs.com/jiekzou/p/5988099.html  非常感谢博主分享. 1.set nocount on 关闭行基数信息,减少网络通信,提高程序性能 ...

  4. ChromeExtension入门浅谈

    0.写在前面的话 朋友上班时每天好几个时段都有个客流信息需要汇报到微信里,都是照着网页上的数据手动填写,着实麻烦.所以给写了个简单的函数每次到控制台里去运行,但是体验也并不好,今天就花了一整天的时间鼓 ...

  5. Redis Twemproxy

    主从复制+哨兵解决了读性能和高可用问题,但没有解决写性能问题. Twemproxy将写请求分配到不同节点处理. Twemproxy是Twitter开源的一个redis和memcache代理服务器. 允 ...

  6. 2017-2018 Exp1 PC平台逆向破解 20155214

    目录 Exp1 PC平台逆向破解 实验内容 知识点 官方源 中科大源 上海交大的源 新加坡源 debain源 debian安全更新源 163源的地址 阿里云kali源 启发 评论 Exp1 PC平台逆 ...

  7. RabbitMQ 汇总

    <RabbitMQ Tutorial>译文 第 1 章 简介 <RabbitMQ Tutorial>译文 第 2 章 工作队列 <RabbitMQ Tutorial> ...

  8. TMS320VC5509的MCBSP配置成SPI模式通信

    1. 首先是把MCBSP的配置 其次是时钟停止模式的配置,关闭大同小异 SPI有4中模式,怎么根据上面的寄存器选择哪种模式?下面展示了其中两种,CLKXP=1的时候有另外两种,暂时不整出来了 2. 代 ...

  9. CS50.2

    1,ssd硬盘(solid state disk),固态硬盘,固盘. 2,把电磁信号转化为0或者1 ps:记得吧图给加上 反向即从磁盘中得到数据 3,软盘,floppy disk.早期使用的一种存储. ...

  10. 数位DP模板详解

    // pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位) // status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回, // 给计 ...