Runtime: 重新考虑 corefx 中元组成员的推荐大小写/命名。

创建于 2018-11-16  ·  77评论  ·  资料来源: dotnet/runtime

https://github.com/dotnet/corefx/pull/26582为 IEnumerables 添加了一个新的“Zip”扩展,其签名如下:

c# public static IEnumerable<(TFirst First,TSecond Second)> Zip<TFirst, TSecond>( this IEnumerable<TFirst> first, IEnumerable<TSecond> second)

元组成员名称的大写(即First/Second )让我有些惊讶,因为这不是我认为我们在创建和呈现元组时通常遵循的命名模式,也不是我们的命名模式用于 dotnet/roslyn 本身。

这里的直觉是元组通常充当将轻量级数据捆绑在一起的速记方式,因此它们可以很容易地分组并传入和传出方法,而无需定义整个结构(以及所有其他仪式你会那里需要)。

这自然会导致对可能影响命名决策的事物的两种合理解释:

  1. 这只是处理命名结构的一种更简单的方法。 这些是字段,因此应该命名为类似于公共可变字段/属性(所以大概是PascalCased )。
  2. 这些是参数的集合。 元组本身旨在淡出,人们最自然地会将这些视为一堆参数/局部变量。 因此,它们应该被命名为相似的(所以大概是camelCased )。

IMO,根据我对 LDM 设计的回忆,我们一直在努力争取第二名。 我们付出了很多努力,让“splat/deconstruct”元组感觉非常自然,将 positoin 推得更多,元组不那么相关,它只是你将在本地使用的一组变量方法。 虽然这并不一定意味着元组字段名称必须与预期的使用名称匹配,但不幸的是这些名称现在必须有所不同以防万一。 这既适用于消费,也适用于构造,其中字段名称被拆分为不同的本地名称。 某人构造一个像这样的元组是非常自然的: (name, age) ,但是例如,现在创建一个名称实际上与返回类似(string Name, int Age)的方法不匹配的元组

这不是什么大不了的事。 但我认为这值得讨论,因为这里做出的任何决定都会有效地影响我们 API 和客户 API 中元组的所有未来使用。

非常感谢。

--

注意:附加数据点。 swift 和 go 都允许从一个方法/函数返回多个命名值。 在这两种情况下,这些多重命名值的命名与参数命名相匹配。

area-System.Runtime

最有用的评论

我个人认为元组元素是值的临时分组(如方法参数),而不是独立的 API 元素(如字段和属性)。 元组类型(具有名称)的创建者和使用者可以使用带有或不带有名称的元组。 与参数名称类比,其中名称由消费者选择使用,返回元组的方法应返回带有驼峰名称的它们,以(可选地)帮助消费者。 这就是我一直在使用它们的方式,感觉很好。

所有77条评论

标记@stephentoub @agocke @sharwell @bartonjs

也很好奇@MadsTorgersen @DustinCampbell对这个话题的看法。 你们两个在会议上演示了很多这些东西。 你们中的任何一个人是否对如何最自然地思考和命名这些事物有偏好? 谢谢!

虽然这并不一定意味着元组字段名称必须与预期的使用名称匹配,但不幸的是这些名称现在必须有所不同以防万一。

我不知道为什么这很不幸。 今天,将 API 返回的类型上的公共字段/属性的值存储到本地时就是这种情况:前者是 PascalCased,后者是 camelCased。

我们一直在努力争取第二名

我不确定这里的“我们”是谁,但我们从来没有在这里为公共 .NET API 争取过职位。 人们可以在他们的代码/存储库内部做他们想做的事,但是当我们开始谈论公共 API 时,需要有清晰、清晰的指导,这是我们在本次讨论中试图做的。

但我认为这值得讨论,因为这里做出的任何决定都会有效地影响我们 API 和客户 API 中元组的所有未来使用。

从框架设计指南的角度来看,这里有三种可能的结果:

  1. 永远不要使用元组作为公共 .NET API 的返回类型。 而是定义自定义结构。
  2. 考虑使用元组作为公共 .NET API 的返回类型(受制于要列出的一长串约束)。 命名元素时使用 PascalCasing。
  3. 考虑使用元组作为公共 .NET API 的返回类型(受制于要列出的一长串约束)。 命名元素时使用 camelCasing。

我真的,真的,真的不喜欢(3); 从字面上看,我宁愿我们根本不发布 API(例如 Enumerable.Zip 的新重载)而不是这样做。 从 API 的角度来看,这些是具有公共命名属性/字段的结构,使用 ValueTuple 而不是自定义结构的唯一原因是在定义另一个结构时节省一些精力(并且可能存在关于相同二进制大小的争论可以重用通用实例化)。 从这个角度来看,camelCasing 与所有其他公共 .NET API 完全不同,其中从方法返回的类型使用 PascalCasing 公开其数据。

我在(1)和(2)之间没有强烈的偏好,尽管我通常倾向于(1)。

抄送: @terrajobst

ps @CyrusNajmabadi ,感谢您打开新问题。 :)

抄送: @KrzysztofCwalina

我不知道为什么这很不幸。 今天将字段/属性的值存储到本地时就是这种情况:前者是 PascalCased,后者是 camelCased。

我试图解释那一点,但可能做得很差。 对不起!。 在这种情况下,因为这个想法(至少对我来说)是这确实只是这些价值观的捆绑。 我已经明确承认,这些只能被认为是一种更简单的方法来纠正命名结构。 但我认为这在某种程度上失去了元组真正试图跨越的东西,那就是它们真的可以“淡出”,而你并没有在那个层面上考虑它们。

即毫无疑问,在幕后这是一个真正的结构。 不问它。 但是元组适用于大量代码,以帮助您避免甚至需要考虑这一点。 与其将其视为具有这些公共字段的结构,不如将其视为“这里只是这几条数据”,并且元组会淡出。

“淡出位”是设计的一个非常有意的部分,也是我们为什么像解构一样投资于此。 没有必要,仅使用实际的元组实例值也没有那么糟糕。 但是对于元组本身不相关的信念有足够的推动力,但里面的东西才是最重要的。 在这种世界观中,世界的概念并不是你正在使用的带有字段的结构。 只是你在传递个人价值观。 而且,就像参数作为小写命名的数据流入方法一样,元组将允许您将小写命名的数据流出方法。

另一种思考方式:如果我们允许您命名方法的返回值,我们是否会直觉该名称应该与我们处理参数的方式相匹配(因为它只是命名数据流入/流出方法)? 或者我们会用大写的名字来命名流出的数据吗? 老实说,我不知道 :) 但我的直觉告诉我我们会来的。 而且由于元组只是那些命名返回数据值的捆绑,所以骆驼大小写对我来说最有意义。

我不确定这里的“我们”是谁,但我们从来没有在这里为公共 .NET API 争取过职位。

抱歉,我的意思是:当我们在 LDM 中做事情时,在元组的原始设计和围绕它们的一般哲学中。 但是我在这里可能完全偏离了基础,这可能只是我自己对我个人如何看待元组的看法。 def 希望@MadsTorgersen @DustinCampbell参与进来。 @jcouv@gafter也大量参与了 IIRC。 我也很好奇他们的想法:)

人们可以在他们的代码/存储库内部做他们想做的事,但是当我们开始谈论公共 API 时,需要有清晰、清晰的指导,这是我们在本次讨论中试图做的。

绝对地。 毫无疑问 :) 这就是我想要讨论的原因。 我希望在这里获得完全清晰的指导。 而且我 100% 愿意接受,如果 corefx 只是巩固“他们是帕斯卡大小写。讨论完成”的立场:)

我只是想提出这个话题,因为我个人认为这至少值得再看一遍,并思考一下我们希望人们如何使用这些 API,以及在这些期望中什么会感觉更自然。 我个人认为 camelCasing 最适合这里(而且 roslyn 代码库本身的采用似乎支持了这一点),我认为 ti 对 corefx 也是有意义的。 如果你们同意,那就太好了。 如果您认为它们应该是 PascalCased,那也适用于我 :)

谢谢!

我只是想提出这个话题,因为我个人认为这至少值得再看一遍,并思考一下我们希望人们如何使用这些 API,以及在这些期望中什么会感觉更自然。

谢谢。

注意:由于有两种看似有效(至少在我看来)的思考方式,我不确定我是否可以添加任何其他有用的东西。 这里没有正确/错误的答案,所以最好不要听我的观点,如果这里有什么需要改变的话,可以重新评估并做出决定。 如果你坚持保持现状,那没有问题。

这将有望防止大量的帖子只是在圈子里:D

如果您需要我提供更多信息/观点,请随时询问。 但我现在就把它交给你了,因为我认为我没有更多要补充的了。 谢谢!

唯一对我来说似乎不是一个选择的是

永远不要使用元组作为公共 .NET API 的返回类型。 而是定义自定义结构。

卷积的重点是返回一个元组。 我最初有未命名的元组。 我不知道为什么我们需要参与这场辩论。

我最初有未命名的元组。

从消费的角度来看,我不知道“未命名”是什么意思。 然后属性/字段将被称为 Item1 和 Item2,因此它们具有名称(并且它们是 PascalCased)。

C# 将语法中没有名称的元组称为“未命名”元组。 它与属性名称无关。

好的。 但这是关于 API 的使用方式,以及返回的东西是否有成员:

  1. 项目 1/项目 2
  2. 第一秒
  3. 第一秒

我看不出 (1) 比 (2) 好多少,我也不喜欢 (3) :)

如果它在语法中没有命名,我们就不能进行这场辩论,因为你唯一可以拥有的是 (1)。 关键是不要谈论这个。

如果我返回System.ValueTuple<T1, T2>我们是否会就是否更改名称System.ValueTuple<T1, T2>.Item1/2进行辩论?

如果我返回 System.ValueTuple我们是否会就是否更改名称 System.ValueTuple 进行辩论?.项目1/2?

是的,在这种情况下,我们将讨论是否改为返回:
C# public readonly struct ZipResult<TFirst, TSecond> { public ZipResult(TFirst first, TSecond second); public TFirst First { get; } public TSecond Second { get; } }

哈好吧,那我出去了。 我不在乎。

@terrajobst我们可以在这里继续讨论吗? Twitter 并不是处理这类事情的好媒介。

请注意上面的帖子,它们指出如何将字段/属性等命名为完全合理的。 然而,我觉得硬币的另一面没有被考虑,它得到了几个 LDM 成员的相当多的支持。 如果至少可以考虑这个想法,我会很高兴,因为它当然也有一些优点。

谢谢!

@terrajobst

我不同意; 它打破了与今天所有其他成员访问的对称性。

这是问题的症结所在。 我们在设计和展示这个时的部分直觉是,根本不需要将其视为“成员访问”。 注意:要清楚,我并不是说考虑成员访问是错误的或不恰当的。 我只是指出还有其他的直觉。

在 roslyn 本身中,我们已经看到这种直觉得到了证实,许多代码使用元组,但根本不关心成员访问。 在这方面,这些不是“成员”,它们只是数据的集合。 有了对事物如何工作的同样有效的直觉,这些更自然地映射到参数之类的事物,并且自然地被命名为这样。

好的。 但这是关于 API 的使用方式,以及返回的东西是否有成员:

正确的。 还有一个核心争议点。 我们是否期望人们认为“我正在消费一个元组。它有我正在访问的成员。”? 或者我们是否期望人们认为“我正在消费两条数据,‘第一条,第二条’”?

鉴于此语言功能是如何创建的以及我们如何允许使用它,这两种观点同样有效。 对我来说,根本问题是:这些观点之一是我们想要给予更高优先级的。

就个人而言,我发现后一种观点更加直观和自然。 我们非常特别地塑造了元组来感受某种特定的方式,并与我们在语言中其他地方的做法相匹配。 我们在消息传递方面做了很多努力,即它只是一组数据。 你通常不会考虑或关心它作为这个容器与成员。

这里的问题是:CoreFx 想要对事物采取哪种观点? 注意:我认识到这是一个具有挑战性的话题,因为 CoreFx 不是 C#。 它必须考虑潜在的许多消费者以及他们如何看待这些类型。 但是,从 C# 的角度来看,元组是自然淡出的,可以自然地访问实际数据。 从事物的角度来看,名称最自然地匹配它们的使用方式,并且在消费/生产中具有对称性似乎非常好。

谢谢!

我个人认为元组元素是值的临时分组(如方法参数),而不是独立的 API 元素(如字段和属性)。 元组类型(具有名称)的创建者和使用者可以使用带有或不带有名称的元组。 与参数名称类比,其中名称由消费者选择使用,返回元组的方法应返回带有驼峰名称的它们,以(可选地)帮助消费者。 这就是我一直在使用它们的方式,感觉很好。

@terrajobst

我之所以做出回应,是因为您在 Twitter 上一直在说更多 :-) 总的来说,我认为在 GitHub 问题上没有更多要说的,因为 Stephen 已经大致概述了我们的立场。

是否可以根据有关几个 LDM 成员对此的看法以及我们可以期望人们如何考虑元组的多种有效方式的信息来重新考虑这一立场? 特别是,我认为这一点作为考虑的一部分非常重要:

正确的。 还有一个核心争议点。 我们是否期望人们认为“我正在消费一个元组。它有我正在访问的成员。”? 或者我们是否期望人们认为“我正在消费两条数据,‘第一条,第二条’”?
鉴于此语言功能是如何创建的以及我们如何允许使用它,这两种观点同样有效。 对我来说,根本问题是:这些观点之一是我们想要给予更高优先级的。

CoreFx 在这里提出了一种观点,我认为这不一定是我们引入此功能时 C# 想要的观点。 我希望再次进行讨论,以确保所有各方都同意这一点,或者在这里改变一些事情是否更合适。

谢谢!

在决定 BCL 的指导方针时,我们会考虑(有时比其他更成功) C# 以外的 CLR 语言。

例如,NETCOBOL 看起来不能使用解构(至少,乍一看我不认为它适合他们的语言),但我可以很容易地想象他们的工具消耗TupleElementNamesAttribute允许调用者使用别名而不是“Item1”和“Item2”来使用 ValueTuple。 在这种情况下,这些只是字段别名,应该像所有其他字段一样命名。 (我_认为_ NETCOBOL 在 CLR 调用时区分大小写,但我可能是错的。)

如果我理解正确, TupleElementNamesAttribute对 C# 编译器很重要的唯一地方是在执行成员访问解析时,此时它现在在语法上等同于属性或字段,因此名称应遵循属性和字段名称指导。 (我似乎无法找到解析器语法规范来确认这是属性值具有的唯一实际效果)

但是,从 C# 的角度来看,元组是自然淡出的,可以自然地访问实际数据。

分解的语法主要来自具有 Deconstruct 方法的 ValueTuple,IDE 上的任何用分解列表替换var的大小写都只是 IDE 主义,因为在该特定上下文中varValueTuple<T1,T2>[TupleElementNames]一起使用的已解析上下文。

据我所知,目前唯一真正的问题是“嘿,您可以解构这个var ”的建议/更正按原样复制名称,而不是应用与它相同的上下文样式转换将适用于任何其他成员访问本地(如果有任何其他)。

据我所知,目前唯一真正的问题是“嘿,你可以解构这个 var”的建议/更正会按原样复制名称,而不是应用与其他应用相同的上下文样式转换成员访问本地(如果有任何其他)。

问题在于概念不对称。 即使观点是值相同,也需要以不同的方式命名它们。 如果您不认为这些值是元组的一流性质,这不是问题,但如果您这样做的话。

分解的语法主要从具有 Deconstruct 方法的 ValueTuple 中脱落

我不相信那是真的。 我相信即使没有 Deconstruct 方法,也总是可以分解元组。 而且,即使使用解构方法,分解也可以对数据进行操作,因为它是由生产者自然表达的。

只是一种 IDE 主义,因为 var 在该特定上下文中是 ValueTuple与 [TupleElementNames] 一起使用的已解决上下文。

我并不是真正的 IDE 主义。 这就是 C# 语言如何看待这个符号。 该符号是“具有这些元素名称的元组”。 有一个特定的编码/解码 C# 用途(使用 ValueTuples、字段和属性),但其意图是只知道它是如何与其他语言进行互操作的。 这些名称专门用于命名数据​​。 这些数据最终存储在字段中并不是您通常需要知道或关心的事情。

从根本上说,C# 创建并编码这些(如尼尔所说)创建一个临时的值分组。 在处理值时,自然形式是直接获取 hte 数据并直接使用它(而且,c# coudl 一直认为这是处理元组数据的唯一方法)。 但是,坦率地说,这可能有点过于繁重,因此既可以使用元组实例也可以只使用数据。

据我所知,唯一真正的问题

这取决于你如何定义“真正的问题” :) 正如我在第一篇文章中提到的,使用以帕斯卡命名的元组元素时没有任何问题。 你绝对可以走这条路。 但是,就像尼尔所说的那样,“感觉不错”。 因此,这取决于您是否认为发布对人们“感觉不对”的命名约定(我们可能永远无法更改)是个问题。 IMO,这是一个重大问题,现在值得避免,这样我们就不会最终做出让我们后悔的事情,也不会让用户觉得不对劲。

谢谢!

我和@CyrusNajmabadi 在一起,我认为元组只不过是本地人和/或参数的松散分组。 “容器”仅作为必要存在,以便在传递这些元素时弥合语言和运行时中的技术/遗留差距。 元组不应该是具有适当字段的适当类型的廉价且容易的替代品。 使用PascalCase命名似乎意味着它就是这样。

@bartonjs

在决定 BCL 的指导方针时,我们会考虑(有时比其他更成功) C# 以外的 CLR 语言。

我可以理解这个论点。 对于不理解元组并将它们视为使用字段/属性名称准则的容器的语言来说是有意义的。 但是这些语言中有多少甚至能够理解元组以支持元素别名呢? 他们中的大多数人不会只看到Item1Item2吗? 为此,考虑到名称只是元组的默认字段名称的同义词,在这个特定的 API 中命名它们有什么意义?

我经常使用元组。 它们是我最喜欢的 C# 7 特性之一。在我能想到的语言团队的所有代码示例中,他们使用驼峰命名法作为这些元组的元素名称。 因此,这也是我遵循的惯例。

正如@gafter所说,元组是值的集合,很像局部变量或参数。 事实上,当元组语法用于解构时,这些元素局部元素。 因此,使用 camelCase 作为名称确实“感觉不错”。

在幕后,这些元组元素只是结构的属性。 但这是一个实现细节。 元组语法将实现细节隐藏在开发人员之外。 通过坚持,因为它们是属性,所以命名应该遵循属性的命名约定,通过呈现这些实现细节,您正在使抽象“泄漏”。

使用@CyrusNajmabadi提到的API,

public static IEnumerable<(TFirst First,TSecond Second)> Zip<TFirst, TSecond>(
    this IEnumerable<TFirst> first, IEnumerable<TSecond> second)

我们实际上是在做(First, Second) = (first, second) 。 我发现那些 PascalCase 元素真的很刺耳。 与语言约定相比,它们只是“错误的”。 所以我同意他的观点:不幸的是,BCL 以这种方式违背了惯例。

我认为元组成员应该匹配方法参数伪元组的大小写。 也就是骆驼案。

大多数语言仍将使用 ItemX 名称来访问它们,并且理想情况下应该在实现元组成员名称之前或同时实现元组解构。

@stephentoub :从框架设计指南的角度来看,这里有三种可能的结果:

  1. 永远不要使用元组作为公共 .NET API 的返回类型。 而是定义自定义结构。
  2. 考虑使用元组作为公共 .NET API 的返回类型(受制于要列出的一长串约束)。 命名元素时使用 PascalCasing。
  3. 考虑使用元组作为公共 .NET API 的返回类型(受制于要列出的一长串约束)。 命名元素时使用 camelCasing。

我认为这里应该有第四点:永远不要在公共 API 中使用元组名称。 特定名称在特定场景中很有用。 但是当涉及到公共 API 时,我认为我们不应该(通过任何理由)给元素赋予自以为是的名称,相反,我们可以只返回(TFirst, TSecond) 。 因此,元组将扮演作为值集合的唯一角色,解构将用于为每个元素提供特定名称。

就像@DavidArno一样,在我看过的所有 LDM 笔记中,在@MadsTorgersen@DustinCampbell将元组作为一种新语言特性的所有演示中,我看到我记得,它们总是驼峰式。 我的印象是这一直惯例,因此我一直遵循它。 我们现在制作 PascalCase 的事实让我大吃一惊。

使用 ValueTuple 而不是自定义结构的唯一原因是在定义另一个结构时节省一些精力

@stephentoub我不认为是这种情况。 到目前为止,在大多数情况下我都使用了元组,如果它们不存在,我就不会使用自定义结构。 我要么会用

  • a) 输出参数
  • b) 字典而不是元组列表
  • c) 根本不创建这个元组返回方法,而是有两个方法

事实上,如果不是元组,我认为根本没有人会提出这个 API。 我们总是将现有的 Zip 方法与 Func 一起使用,以便我们可以创建我们喜欢的任何容器(通常这实际上是一个匿名类型)。 因此,这不能替代具有两个属性的自定义结构。

从 API 的角度来看,这些是具有公共命名属性/字段的结构

  • 从“API”的角度来看,它返回的是ValueTuple<TFirst, TSecond> ,这是一个带有帕斯卡大小写字段的结构,所以一切都应该没问题。
  • 从 C# 的角度来看,它没有返回结构。 它返回一个元组类型。

因此,这不能替代具有两个属性的自定义结构。

out 参数在这里不相关。 您不能将它们存储在枚举器的 Current 中。

字典在这里不相关。 相比之下,这将是非常昂贵的,需要谁知道要访问结果的人进行索引,等等。

所以假设这种方法存在,我看不出我的说法是不正确的。

如果我们想删除此方法,我可以接受,正如我在本次讨论的多个其他线程之一中所说的那样。 但这并不涉及我所说的更大的问题。

我认为这里应该有第四点:永远不要在公共 API 中使用元组名称

那是另一种选择,是的,谢谢,我应该包括它。 不过,我个人不是它的粉丝。 当可能有更多描述性的名称时,为什么要强迫开发人员弄清楚 Item1 和 Item2 的含义?

不过,这有助于强调为什么我认为 camelCasing 是有问题的。 如果我们返回一个结构,名称将是 PascalCased。 如果我们返回 ValueTuple,名称将是 PascalCased。 .NET API 一直被定义为,从方法返回的值的点上为您提供 PascalCased 名称。 我们现在不应该脱离这一点。 开发人员可以随意命名他们的本地人。 IDE 可以根据需要公开重构。 自己代码上的方法可以使用开发人员想要的任何命名。 但是当涉及到公共 .NET API 时,这些 API 一直是 PascalCased,并且应该继续如此。

如果我理解正确,TupleElementNamesAttribute 对 C# 编译器很重要的唯一地方是在进行成员访问解析时,此时它现在在语法上等同于属性或字段,因此名称应遵循属性和字段名称指导.

@bartonjs这在具有递归模式的 C# 8.0 中将不再适用。 您将能够在解构模式中使用元组元素名称:
```c#
var foo =(第一个:0,第二个:0);

开关(富)
{
案例(第一个:1,第二个:1):
Console.WriteLine();
休息;
}
```
(https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLRIMaOQSwG4SoAOsMECAdsgD4ACADAAS0CMA3ALABQtAzMwCZGAYW4BvboynN+tACyMAsgAoAlJOkSu0nYzxQEjMAHtjjALyNlYHAmQwQjegBpGyCBmMUAJo/qrObV0pDWDkAHccGAwACysTY3Ug4K1g4Iwod3jbe0cWV3dPHzzVEFC04NYATjVAiorgJCgAazq0gF9yzq52oA===)

在这里将它们用 pascal 大小写会感觉很奇怪,因为这应该类似于一个参数列表,并且它在语法上等同于将这些作为参数名称的 Deconstruct 方法。

out 参数在这里不相关。 您不能将它们存储在枚举器的 Current 中。

字典在这里不相关。 相比之下,这将是非常昂贵的,需要谁知道要访问结果的人进行索引,等等。

我的前 2 个示例与此特定 API 无关,但作为反驳您的观点,即使用元组的唯一原因是作为自定义结构的替代品(通常)。 我在第二段中解释了如果不是元组,我认为这个 API 会是什么情况——我认为它不会存在,而不是添加自定义结构。

我个人认为元组元素是值的临时分组(如方法参数),而不是独立的 API 元素(如字段和属性)。 元组类型(具有名称)的创建者和使用者可以使用带有或不带有名称的元组。 与参数名称类比,其中名称由消费者选择使用,返回元组的方法应返回带有驼峰名称的它们,以(可选地)帮助消费者。 这就是我一直在使用它们的方式,感觉很好。

来这里说一些几乎与此相同的东西。 我非常强烈地感到帕斯卡套管会不必要地破坏有价值的平行。 传入参数列表使用驼峰命名,并且匹配的传出变量也应该是驼峰大小写,即使元组类型用作泛型类型参数。

当我一直选择骆驼案例时,从阅读 LDM 说明和讨论中,我似乎认为元组类型和参数列表之间的对称性对于保持尽可能远是必不可少的,并且元组类型和结构之间的相似性是偶然的,并不重要。

.NET API 一直被定义为,从方法返回的值的点上为您提供 PascalCased 名称。 我们现在不应该脱离这一点。

我认为镜像命名参数列表比确保点后面的字母大写更重要。 一旦您确定点后的驼峰大小写是可以的,在我看来,这并不比使用驼峰大小写来指定参数名称或私有字段名称更糟糕。 这只是另一个这样的场景:变量包。

例如,NETCOBOL 看起来不能使用解构(至少,乍一看我不认为它适合他们的语言),但我可以很容易地想象他们的工具使用 TupleElementNamesAttribute 来允许调用者使用别名而不是“Item1”和“Item2”来使用 ValueTuple。

它不像添加“使用 TupleElementNamesAttribute 的工具”那么简单。 他们需要更改语言才能支持这一点。

作为对您的观点的反驳,说使用元组的唯一原因是替代自定义结构(通常)

那不是我说的。 我说“使用 ValueTuple 而不是自定义结构的唯一原因”......有很大的不同。

对不起。 但这仍然假设如果不是元组,解决方案将是自定义结构。

那是另一种选择,是的,谢谢,我应该包括它。 不过,我个人不是它的粉丝。 当可能有更多描述性的名称时,为什么要强迫开发人员弄清楚 Item1 和 Item2 的含义?

Fwiw 如果名称是“第一”和“第二”,我认为这不会比 Item1 和 Item2 更具描述性。

这对我来说很奇怪。 不懂元组的语言会有完全好的体验。 他们会将这些视为 ValueTuple 并且能够正常访问成员。

这仅是关于包含元组名称的属性中的内容。 这些属性是由 C#/VB 创建的,用于对预期由 C#/VB 开发人员(或希望与它们互操作的其他语言)创作的名称进行编码。 尽管,尽管 C# 方面强烈争论为什么这些应该是骆驼案例(正如我们从难以置信的早期开始就提出的那样),但 corefx 仍然使用 PascalCase,尽管这对于这里的主要消费者来说似乎更糟。 我个人并没有真正看到这对谁有帮助。 但我肯定看到一大群人因此受到伤害。

tuplenamesattribute 存在于那些想要将其视为一组数据的语言。 支持这种语法的主要两种语言使用与参数相同的语法对数据进行分组。 所以银行应该遵循我们对这些语言的偏好。 它应该遵循参数,因为这就是你的东西自然投影的方式。

不关心投影的语言会很好。 他们有元组实例和字段。 这只是项目语言的正确名称。 C# 和 VB 从一开始就非常清楚正确的方式,所以 corefx 会做不同的事情似乎很奇怪,因为它们将像 95% 以上的命名元组的所有生产和消费。

我认为这里应该有第四点:永远不要在公共 API 中使用元组名称。 特定名称在特定场景中很有用。 但是当涉及到公共 API 时,我认为我们不应该(通过任何理由)给元素起自以为是的名字

我实际上同意这一点。 一个主要原因是这些 API 对最终语言没有用处,除非它们理解这个命名属性。 因此,如果您返回一个(string firstName, string? middleName, string lastName) ,它将被投射到任何其他语言(包括 C# 的早期版本)中,只是ValueTuple<string, string, string> 。 这意味着使用这些语言的任何人都只会看到 Item1/Item2/Item3,没有额外的语义信息来知道什么是什么。 更糟糕的是,如果这是一个大型元组,您将不得不在其中执行.Rest.Item3之类的操作。

如果要在 API 中使用 ValueTuple,我认为这仅适用于您只想拥有有序的数据集合的情况,并且仅此一项就明确定义了语义。 即根本没有名字。 因此,例如,对于“Zip”,如果您传入两个序列并从每个序列中获取成对元素的元组,那么事情通常会很明显。 但除此之外,实际上很少有 API 会使用元组,因为名称将是暴露合约的重要组成部分。

我认为镜像命名参数列表更重要

最有趣的一点是:“NamedArgs/Args”是 C#/Roslyn 实际表示元组的方式:

https://github.com/dotnet/roslyn/blob/578ce2e8cca3a3ebad7895c5e46855db5a405967/src/Compilers/CSharp/Portable/Syntax/Syntax.xml#L358 -L362

这里的对称性(正如 Mads/Neal/Julien/Myself/Others 所提到的)是非常有意的,并且一直是用于帮助塑造和指导这个和辅助特征的核心思想。 我认为人们总是理所当然地认为这些是一致的。 毕竟,这就是 C#/Roslyn/LDM 一直向这些人传达信息的方式。 所以我认为这个决定有点出人意料,特别是因为它似乎并没有由语言方面的相关利益相关者运行。

C# 和 VB 从一开始就非常清楚正确的方式,所以 corefx 会做不同的事情似乎很奇怪,因为它们将像 95% 以上的命名元组的所有生产和消费。

我不得不说,我发现这个和类似的论点非常令人沮丧。 两年多以前,在元组支持发布或被广泛宣传之前,我们这些代表 corefx 和框架设计指南的人表达了对 PascalCasing 的强烈偏好,然后就宣传骆驼式方法而言,有意或无意显然被忽略了。 现在说 corefx 是矛盾的,并且做不同的事情感觉就像是一记耳光。

无论如何,我已经听到了所有的争论,我已经编写了示例代码来探索各种方法,并且我仍然相信 PascalCasing 是任何公开暴露的 .NET API 的正确答案。 我认为那些有强烈不同意见的人也做了同样的事情,并且同样不会动摇。 我们都只是一遍又一遍地以不同的方式重新讨论相同的论点,所以我不希望任何一方有任何额外的论点会改变或说服任何人离开他们现有的立场。

此外,还有很多其他原因不在公共 .NET API 中使用元组,例如

  • 例如,如果您想在将来返回一条额外的数据,它们的版本/进化就不会很好。
  • 他们没有很好地记录或表现出对 IntelliSense 等的良好支持。
  • 不提供专门/优化数据存储方式的能力。

等等。这一切都同样适用于直接使用 ValueTuple 时,但现在关于命名元组如何浮出水面存在进一步的分歧,我希望这里选择 camelCasing 或 PascalCasing 只会激怒 .NET 开发人员的重要子集.

在提出这个 Enumerable.Zip API 之前,我最初的立场是我们不应该在 .NET API 中公开元组。 在这一点上,我又回到了那个立场。 我相信它应该是一个通用的框架设计指南,以避免元组,命名或其他,我们应该删除新添加的 Enumerable.Zip 重载,无论如何它增加的价值很少(你可以很容易地自己用Enumerable.Zip(first, second, (f,s) => (f,s)) )。

显然欢迎大家继续讨论和不同意我的意见。 但我将避免进一步回应这个帖子,因为除了我已经说过的内容之外,我没有任何有价值的东西可以补充,而且我很累,不想争论切线。 如果人们停止查找我的电子邮件地址并直接向我发送一次性电子邮件以告诉我为什么我错了,我也将不胜感激。

我不得不说,我发现这个和类似的论点非常令人沮丧。 两年多以前,在元组支持发布或被广泛宣传之前,我们这些代表 corefx 和框架设计指南的人表达了对 PascalCasing 的强烈偏好,然后就宣传骆驼式方法而言,有意或无意显然被忽略了。

我个人不记得在任何 LDM 会议中发生的任何事情。 我可以看到有一个关于该主题的电子邮件线程,不幸的是我没有被包括在内:-/

我在这里担心的只是我不清楚为什么现在要这样做。 LDM 有充分的理由推动采用 camelCase 方法(如上文所述,令人作呕)。 这就是事物的呈现和传达方式,似乎没有任何问题。 甚至我们自己使用这种第一手资料的直接经验也证明这种方法“感觉正确”。

所以,我的首要问题是:为什么现在走不同的方向? 鉴于 corefx 选择的任何东西肯定会成为人们做事的方式,感觉这会造成更大的分歧。 这个 convo 甚至开始于@sharwell关于工具现在需要如何遵循 Corefx 约定而不是 C# 约定的讨论。 这似乎是一个让 corefx 与 C# 发生冲突的决定,尽管 C# 设计这些东西是非常有意的。 正如我上面提到的,对称性不是偶然的。 它是如何考虑元组的核心部分,它已被编织到许多设计和围绕它们的预期编码模式中。 它们非常具体地不打算被视为成员,而是作为可以传递的松散数据集合(类似于参数是传递给某物的松散数据集合)。

这个命名并非无关紧要。 它对我们希望人们如何看待这些人有很大的影响,而且 hte 语言功能的设计绝对是有意的,因此将 htem 视为成员并不是思考事物的主要方式。

在提出这个 Enumerable.Zip API 之前,我最初的立场是我们不应该在 .NET API 中公开元组。 在这一点上,我又回到了那个立场。

我们应该删除新添加的 Enumerable.Zip 重载,无论如何它增加的价值很少

那很好啊。 在这一立场上取得进展需要什么? 如果我们可以做 htat,它就完全摆脱了整个争论,并且不会潜在地将用户群划分为关注 C# POV 的用户群和关注 Corefx POV 的用户群。

我们应该打开另一个错误吗? 或者这是处理它的正确错误?

我们应该打开另一个错误吗? 或者这是处理它的正确错误?

@stephentoub我知道你说过你要退出。 但是你能不能把这个问题作为一个过程来回答? 我不熟悉 corefx 喜欢如何做事。 所以我听从你的意见。 谢谢!

但是你能不能把这个问题作为一个过程来回答?

这个问题很好。 它被标记为 api-ready-for-review,因此它会出现在@terrajobst的查询中,并且它处于 3.0 里程碑。

只取消元组元素上的名称会满足两个阵营的要求吗? 如前所述,在这种特殊情况下,它们不会增加任何价值。

这个问题很好。 它被标记为 api-ready-for-review,因此它会出现在@terrajobst的查询中,并且它处于 3.0 里程碑。

非常感谢! 抱歉,这里有任何不愉快:-/

只取消元组元素上的名称会满足两个阵营的要求吗? 如前所述,在这种特殊情况下,它们不会增加任何价值。

我也会同意的。 实际上,我主要关心的是避免现在创建关于元组元素命名的严格规则。 我将把它留给@terrajobst和 corefx 审查来决定他们想要采取哪些前进的道路。

我个人一点也不担心骆驼案例名称会出现在会员访问中。 我认为这有助于说明没有封装的概念(从这个意义上说,您甚至可以将私有字段与骆驼案例相提并论),而我正在使用的只是一袋变量(而不是一个概念它自己的),我可能应该考虑解构它。

我能想到的语言团队的代码示例,他们使用了camelCase

Tuples 的官方 C# 语言文档中的示例混合使用了camelCasePascalCase

https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#tuples
https://docs.microsoft.com/en-us/dotnet/csharp/tuples#assignment -and-tuples
https://msdn.microsoft.com/en-us/magazine/mt493248.aspx

上一篇文章通过说“但是,元组项目名称的约定没有明确定义”准确地捕捉到了世界的状态。 然后继续推荐:“考虑对所有元组项名称使用 PascalCase。”。 同一作者撰写的经典“Essential C#”书的最后一版中也有同样的建议。

此外,还有很多其他原因不在公共 .NET API 中使用元组

另一个原因是性能。 与它们所做的相比,元组在运行时占用的空间很大。 ValueTuple类型实现了所有需要创建的非泛型和泛型接口,即使它们实际上并未使用(至少在当前的 .NET 实现中)。 在 Linq 中使用已经有大量运行时占用空间的元组可能是可以的,但我认为我们不想在核心 API 中开始大量使用它们。

我认为我们应该将命名与是否使用元组分开,因为它们是正交决策。 仅仅因为我们不能就名称达成一致,我们不会使用元组,这对我来说似乎很愚蠢。

  1. 在框架中有元组。 由于它们具有版本控制限制,我们当前在元组上的位置至少是AVOID 。 不过解构方法很好,因为我们可以稍后添加重载。 到目前为止,我还没有看到在框架中使用元组的令人信服的理由。 它们对Zip有意义,对于像(int, T) SelectWithIndex<T>(this IEnumerable<T> source)这样的(假设的)API 也有意义。 但是,这两个示例都不是一个非常引人注目的 API。

  2. 命名。 我觉得我们已经在这个线程中辩论了利弊。 最后,我发现自己与@stephentoub处于相同的位置,我发现骆驼套管的论点相当不令人信服。 元组镜像参数列表的参数同样可以应用于参数值与它初始化的属性/字段匹配的任何构造函数,但我们不这样做。 另一方面,我发现 PascalCasing 的论点非常有说服力:名称是字段的别名,因此出现在成员访问中。 因此,无论个人偏好如何,我都认为 PascalCasing 在客观上比 camelCasing 更符合现有的命名准则。

在公共 API 中避开元组的想法有什么问题,因为它们很难统一命名? API 元素的命名很重要。 元组是一种无需仪式即可传递数据的好方法,但公共 API 设计就是要选择适当的仪式。

元组镜像参数列表的参数同样可以应用于参数值与它初始化的属性/字段匹配的任何构造函数,但我们不这样做。

不,它不能。 参数是元组在语法上镜像参数和参数列表,而构造函数参数和字段声明是不相关的。

正如@CyrusNajmabadi所说,元组表达式实际上由语法中的参数列表组成 - 它是相同的语法节点,元组表达式和调用的参数列表之间没有区别。

讨论的激烈程度表明,此时元组不应出现在任何公共框架 API 中。 一旦他们进来,他们将永远存在。 设计错误无法修复。

稍后添加名称不会是一项重大更改。 我认为我们可以使用未命名的元组,看看客户端代码将如何使用它们。 我希望它大部分时间都会被当地人解构,但谁知道呢。

在框架中有元组。 由于元组的版本限制,我们目前对元组的立场至少是避免。 不过解构方法很好,因为我们可以稍后添加重载。 到目前为止,我还没有看到在框架中使用元组的令人信服的理由。 它们对 Zip 有意义,并且对 (int, T) SelectWithIndex 之类的(假设的)API 有意义(这个 IEnumerable资源)。 但是,这两个示例都不是一个非常引人注目的 API。

同意。 似乎一个很好的前进方式就是删除这些。

命名。 我觉得我们已经在这个线程中辩论了利弊。 最后,我发现自己与@stephentoub处于相同的位置,我发现骆驼套管的论点相当不令人信服。 元组镜像参数列表的参数同样可以应用于参数值与它初始化的属性/字段匹配的任何构造函数,但我们不这样做。 另一方面,我发现 PascalCasing 的论点非常有说服力:名称是字段的别名,因此出现在成员访问中。

这是底层的表示。 但这不是语言表示。 并且名称的存在是为了支持语言表示。 元组的设计目的是让您可以使用底层表示(毕竟,您可以是非 C# 或 C# 预元组)。 但是,命名部分是为了让 C#/VB 能够循环这些元素名称。 语言表示不是那些领域。 老实说,他们可以是任何东西。 相反,它们只是在语义和句法上捆绑在一起的命名元素,看起来像是参数。 通过以这种方式命名事物,它违背了设计语言特征所针对的对称性和直觉。

因此,无论个人偏好如何,我都认为 PascalCasing 在客观上比 camelCasing 更符合现有的命名准则。

这里的核心问题是透视问题,如果您认为名称映射到底层表示(即字段),或者您认为名称映射到语言投影。 IMO,这些名称是专门为后一种目的而设计的。 因此,虽然它们用于在这方面映射字段,但名称“客观上**更一致”,因为它们将它们映射到类似参数的数据。

我们没有将元组设计成看起来像{ int Field1; string Field2; }是非常有意的,它看起来和感觉更像是带有字段的实际结构/类型。 如果我们将它们视为类似于字段,我们将采用非常不同的句法外观。

相反,我们有意将它们设计为以(int param1, int param2)的形式投射到语言中。 从语法上讲,我们已经推动了这些就像参数列表的心态,并且命名已经与此一致。 将其作为结构+字段投射到元数据/IL 中并不是要考虑的主要方式。 鉴于此,我认为客观上**更好的是名称(为支持此 C#/VB 视角而创建)与这些人的 C#/VB POV 相匹配。

谢谢!

--

** 注意:我个人认为这不是客观的。 我认为这是相当主观的 TBH :) 关于这些事情是什么有两种观点,而 BCL 跨越了两个世界。 只是我认为 name-attribute 是为了在这里投射 C# 的观点,因此主观上与其保持一致而不是 IL/metadata 的观点会更好。

IMO,这些名称是专门为后一种目的而设计的。

我不相信这是他们存在的全部目的。 Mads 和我很早就讨论这个问题,我们得出的结论是,拥有名称是在公共 API 中使用它们的先决条件。

这里的核心问题是透视问题,如果您认为名称映射到底层表示(即字段),或者您认为名称映射到语言投影。

作为框架设计者,我们有责任让它被各种语言和工具使用。 原则上,C# 中没有关于元组或名称的特定内容。 其他语言,例如 F# 甚至 Cobol.NET 可以使用记录在元数据中的名称。

坦率地说,我认为这个线程的结论是遗憾的是,我们不能就允许这些类型在任何公共 API 中使用的最低限度达成一致。 我还发现自己同意@stephentoub的观点,即这次讨论的基调和演变方式相当令人沮丧。 由于我们现在有更大的问题需要解决,我认为这里的结论是:

不要在公共 API 中使用元组。

这意味着它们的使用和命名是私有的实现细节,因此任何人都可以自由决定如何命名它们。 就个人而言,我觉得这个结果相当不幸,因为它几乎肯定会造成混乱,但是,嘿。

是笑话吗?

我不喜欢camelCase,无处不在。
如果我可以选择,我会在 PascalCase 中编写与对象/变量相关的所有内容,甚至是 JavaScript 代码......

@TonyHenrique

我不喜欢camelCase,无处不在。

camelCase 在 .Net 中被广泛使用。 最重要的是,它用于本地/参数。 哪些是 C#/VB 元组旨在表示聚合的数据类型。 C#/VB 专门选择了有意反映这些结构的语法,因为直觉是认为它们是相同的。 即,您可以“拾取”您的参数列表并将其制成元组,然后将其传递(传入和传出方法)。 因此,C# 对这些人的看法是,他们自然会遵循参数/局部变量的命名方式。 几乎整个生态系统都将它们广泛编写为 camelCased。

如果 .net 切换到将参数设为 PascalCased,那么元组也会如此。

@terrajobst

我很失望你的结论是“不要在公共 API 中使用元组”。 在我看来,这不是一个好建议。

很遗憾,各个 Microsoft 团队无法就元组元素的命名约定达成一致的共同策略。 你是对的,它会造成混乱。 许多人,包括我自己,会忽略你的结论,当元组是正确的使用方法时,会继续在公共 API 中使用元组。 但是对于它们的名称,我们无法遵循公认的约定,因此camelCase 和PascalCase 都将被不同的人用于他们的API。

@DavidArno两种命名选择都有很大的缺点。 这不一定只是同意、和平和顺从的问题。 这不仅仅是一个社会问题。 根本没有可行的替代方案。

我在自己的代码中遇到了同样的命名难题。 我还得出结论,元组只能在内部位置使用(对于“内部”的一些模糊、直观的概念)。

使用KeyValuePair有什么问题? :巨魔的脸:

原则上,C# 中没有关于元组或名称的特定内容。 其他语言,例如 F# 甚至 Cobol.NET 可以使用记录在元数据中的名称。

好吧,命名元组是一种语言特性,所以它们将特定于定义它们的语言,不是吗?

AFAICT:

  • C# 将 VT 定义为参数/局部变量的瞬态集合,因此它们应该使用 camelCasing。
  • F# 似乎在我找到的文档中对它们进行了驼峰式命名,尽管通常有 PascalCasing 成员。
  • 即使在同一篇文章中,VB 文档似乎也混合了 camelCasing 和 PascalCasing。

我可以理解“不使用名称”或“不使用 VT”规则,因为 BCL 必须支持多种语言......但我没有看到任何 _language_ 推动 PascalCased(-only) 元组名称.

似乎只有 BCL 开发人员,这使得 CoreFX 与_所有它的主要消费者_... 出于意识形态的原因没有被任何语言设计团队采用? 😕

看起来现在已经设置了一个先例,即元组元素名称在用作返回类型时应该是帕斯卡大小写:dotnet/corefx#35595

我喜欢帕斯卡案例。 到处。

我认为我们不应该叫你@ tonyHenrique =)

是的, @bartonjs正在制定元组指南,它基本上是:

  1. 更喜欢自定义结构/类,因为它们可以进化
  2. 如果您使用元组,请使用ValueTuple<>
  3. 使用 PascalCase 命名元组元素
  1. 如果您使用元组,请使用 ValueTuple<>

更像是“如果您使用元组,请使用命名元组。” (这恰好意味着 ValueTuple + 属性)。 返回(未命名的)ValueTuple 是一个禁忌。

@terrajobst

令人非常失望的是,BCL 团队以任何方式试图提供有关语言功能的指南。 这三个准则都无济于事,并且完全忽略了元组的意义。

是否使用元组或自定义结构/类既是主观的,又取决于许多因素。 一个简单的“偏好一个胜过另一个”的指导方针是无稽之谈。

如果您使用元组,请使用()元组表示法。 该符号的底层类型与 99% 的用例无关,因此不应在任何指南中提及。

正如这个线程所示,BCL 希望
使用 PascalCase 命名元组元素违反约定,只会产生不必要的冲突。 充其量你应该使用camelCase,否则只能对这个主题保持沉默,让人们自行选择。

必须有关于它们在 BCL 中的使用指南,以保持 BCL API 的一致性。 这是 BCL 团队的工作。 这是一个命名约定,BCL 已经定义了许多(他不是什么新鲜事)以保持 BCL 的一致性。

应该注意的是,“使用 PascalCase 违反约定运行”不是给定的——它可能违反 _your_ 约定,但正如这个线程显示的那样,没有约定。 例如,我们在内部使用 Pascal。 BCL 中需要有一个定义好的约定,这就是为什么这是一个阻止使用它们公开 API 的原因。

试着想象一下,如果 BCL 中的_任何_其他外部 API_没有_有命名约定,我们会在哪里。

令人非常失望的是,BCL 团队以任何方式试图提供有关语言功能的指南

@davidarno它一直是 .NET 库团队(回到最初的框架设计指南)的一部分,与其他人(如语言设计者和社区)合作,提供旨在帮助创建更好、更一致的指南.NET 库。

@DavidArno我可以证明这是 .NET 团队内代表联合讨论的结果,包括语言/编译器和 BCL。 像许多这样的决定一样,这并不容易,但我认为达成总体协议和一致的方法最终会有所帮助。 因此,即使我们没有采用我最初喜欢的方法,我很高兴我们做出了决定,我对此感到满意。

我同意@jcouv 。 这绝对不是我的首选方法。 但是,我的主要愿望是与相关利益相关者就此事进行联合对话。 这似乎已经完成了。 而且,就像许多决定一样,并不是每个人都会感到满意。 然而,每个人都必须听到他们的声音,并在理解后果后做出决定。 这对我来说已经足够了。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

Timovzl picture Timovzl  ·  3评论

sahithreddyk picture sahithreddyk  ·  3评论

jchannon picture jchannon  ·  3评论

matty-hall picture matty-hall  ·  3评论

omariom picture omariom  ·  3评论