out参数不用赋值?这么神奇吗!

首先提醒大家一下,docs.microsoft.com上的《C# 指南》是这样描述out 参数修饰符[1]的:

作为 out 参数传递的变量在方法调用中传递之前不必进行初始化。但是,被调用的方法需要在返回之前赋一个值。

请注意上面加粗的话,然后看看下面的代码片段,你觉得它能否编译通过:

private void Test(out System.Reflection.ParameterModifier obj)
{ 
//什么也不做
}

如果你很肯定地回答“不能”,那么恭喜你——答错了。我当初看到这段代码的第一感觉也是不能,但发现代码确实能够编译通过。

分析原因

难道是语法改变了,官方文档没更新? 我又测试了一下:

private void Test2(out string obj)//编译失败
{ 
}
private void Test3(out int obj)//编译失败
{ 
}

难道这个类型有什么特殊之处? 我把dotnet/runtime中的ParameterModifier源代码[2]复制到本地项目,编译同样提示CS0177错误,WTF!!!

private void Test(out ParameterModifier obj)
{ 
}
public readonly struct ParameterModifier
{private readonly bool[] _byRef;public ParameterModifier(int parameterCount){if (parameterCount <= 0)throw new ArgumentException();_byRef = new bool[parameterCount];}public bool this[int index]{get => _byRef[index];set => _byRef[index] = value;}#if CORECLRinternal bool[] IsByRefArray => _byRef;
#endif
}

深入Roslyn

应该是编译器做了什么特殊处理!

于是我clone了dotnet/roslyn源代码[3],本来想调试源代码的,结果由于编译时依赖包一直下载不下来,干脆直接读源代码了。

通过查找错误提示"must be assigned to before control leaves the current method",定位到CSharpResources.resx,确认错误编码为ERR_ParamUnassigned:

  <data name="ERR_ParamUnassigned" xml:space="preserve"><value>The out parameter '{0}' must be assigned to before control leaves the current method</value></data>

查找ERR_ParamUnassigned,定位到了编译错误信息被添加的位置(DefiniteAssignment.cs文件内的ReportUnassignedOutParameter方法);

protected virtual void ReportUnassignedOutParameter(ParameterSymbol parameter, SyntaxNode node, Location location)
{......if (Diagnostics != null && this.State.Reachable){......if (!reported){Debug.Assert(!parameter.IsThis);Diagnostics.Add(ErrorCode.ERR_ParamUnassigned, location, parameter.Name);}}
}

因为同样的方法定义,只是参数类型不一样导致编译报错,因此猜测这个方法肯定进入了,只是this.State.Reachable值不同的原因,Reachable的代码如下:

public bool Reachable
{get{return Assigned.Capacity <= 0 || !IsAssigned(0);}
}
public bool IsAssigned(int slot)
{return /*(slot == -1) || */Assigned[slot];
}public void Assign(int slot)
{if (slot == -1)return;Assigned[slot] = true;
}

继续查找Assign的调用位置,发现一段很有意思的代码:

Debug.Assert(!_emptyStructTypeCache.IsEmptyStructType(type));
......
state.Assign(slot);

IsEmptyStructType是不是意味着空Struct不检查?立马来试试:

private void Test(out EmptyStruct obj)///编译通过
{ 
}public struct EmptyStruct
{ 
}

继续探究

但是ParameterModifier明显不是空Struct,而且更奇怪的是为什么将源代码复制到本地项目又不能编译了。 带着这个疑问,我们继续深挖:

private bool IsEmptyStructType(TypeSymbol type, ConsList<NamedTypeSymbol> typesWithMembersOfThisType)
{......result = CheckStruct(typesWithMembersOfThisType, nts);......return result;
}private bool CheckStruct(ConsList<NamedTypeSymbol> typesWithMembersOfThisType, NamedTypeSymbol nts)
{if (!typesWithMembersOfThisType.ContainsReference(nts)){......return CheckStructInstanceFields(typesWithMembersOfThisType, nts);}return true;
}
private bool CheckStructInstanceFields(ConsList<NamedTypeSymbol> typesWithMembersOfThisType, NamedTypeSymbol type)
{// PERF: we get members of the OriginalDefinition to not create substituted members/types //       unless necessary.foreach (var member in type.OriginalDefinition.GetMembersUnordered()){if (member.IsStatic){continue;}var field = GetActualField(member, type);if ((object)field != null){var actualFieldType = field.Type;if (!IsEmptyStructType(actualFieldType, typesWithMembersOfThisType)){return false;}}}return true;
}

代码检查每个字段的类型是否是“空Struct”。这意味着如果所有实例字段都是“空Struct”,则原始类型也被视为“空Struct”,否则为“非空Struct”。看来关键就在GetActualField了:

private FieldSymbol GetActualField(Symbol member, NamedTypeSymbol type)
{switch (member.Kind){case SymbolKind.Field:var field = (FieldSymbol)member;// Do not report virtual tuple fields.// They are additional aliases to the fields of the underlying struct or nested extensions.// and as such are already accounted for via the nonvirtual fields.if (field.IsVirtualTupleField){return null;}return (field.IsFixedSizeBuffer || ShouldIgnoreStructField(field, field.Type)) ? null : field.AsMember(type);case SymbolKind.Event:var eventSymbol = (EventSymbol)member;return (!eventSymbol.HasAssociatedField || ShouldIgnoreStructField(eventSymbol, eventSymbol.Type)) ? null : eventSymbol.AssociatedField.AsMember(type);}return null;
}private bool ShouldIgnoreStructField(Symbol member, TypeSymbol memberType)
{return _dev12CompilerCompatibility &&                             // when we're trying to be compatible with the native compiler, we ignore((object)member.ContainingAssembly != _sourceAssembly ||   // imported fieldsmember.ContainingModule.Ordinal != 0) &&                      //     (an added module is imported)IsIgnorableType(memberType) &&                                 // of reference type (but not type parameters, looking through arrays)!IsAccessibleInAssembly(member, _sourceAssembly);          // that are inaccessible to our assembly.
}

必须是Struct和代码不在同一个程序集(((object)member.ContainingAssembly != _sourceAssembly),字段类型必须是引用类型或数组(IsIgnorableType),并且是私有的(!IsAccessibleInAssembly)。我们来验证一下,将ParameterModifier源代码复制到类库中:

//ConsoleApp1.csproj
private void Test(out ClassLibrary1.ParameterModifier obj)
{
}//ClassLibrary1.csproj
namespace ClassLibrary1
{public readonly struct ParameterModifier{private readonly bool[] _byRef; //编译通过//private readonly string _byRef; //编译通过//private readonly int _byRef; //编译失败//public readonly bool[] _byRef; //编译失败}
}

结论

今天我们深入了编译器的源代码分析了一个简单问题的成因:

一般来说,out参数必须在被调用方法将控制返回给调用方之前初始化。然而,编译器可以进行优化,在某些情况下,如类型是没有Public字段的Struct,将不会显示编译错误。

虽然感觉知道了也并没什么鸟用,但至少说明了好的代码风格还是非常重要的!希望这篇文章能够对你有所启发。

欢迎关注我的个人公众号”My IO“

参考资料

[1]

out 参数修饰符: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/out-parameter-modifier

[2]

ParameterModifier源代码: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Reflection/ParameterModifier.cs

[3]

dotnet/roslyn源代码: https://github.com/dotnet/roslyn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

年底求职难?起薪28万的数据岗位,人才缺口达150万,不限专业学历……

全世界只有3.14 % 的人关注了数据与算法之美2018下半年开始&#xff0c;“寒冬”说愈演愈烈。事实上企业的结构调整、人才升级是常见的现象&#xff0c;而且并非所有互联网企业都在“缩招”&#xff0c;很多企业甚至计划在明年扩大校招。人工智能、数据技术的迅猛发展&#xff…

在.NET 6中使用DateOnly和TimeOnly

千呼万唤始出来在.NET 6(preview 4)中引入了两个期待已久的类型&#xff0c;将作为核心库的一部分。DateOnly和TimeOnly允许开发人员表示DateTime的日期或时间部分。这两个类型为值类型&#xff08;struct type&#xff09;&#xff0c;可以在代码中独立处理日期或时间概念时使…

预售┃要孩子逻辑清晰、善于思考,别忽视空间想象力的游戏锻炼!

▲数据汪特别推荐点击上图进入玩酷屋在之前的文章时&#xff0c;马斯提到数学存在一种现象叫“梯次掉队”&#xff0c;原因在于孩子的数学思维地基没有打牢。&#xff08;传送门&#xff09;提到初中孩子需要空间想象能力时&#xff0c;很多父母疑惑为何需要&#xff1f;关于这…

通过Dapr实现一个简单的基于.net的微服务电商系统(十六)——dapr+sentinel中间件实现服务保护...

dapr目前更新到了1.2版本&#xff0c;在之前4月份的时候来自阿里的开发工程师发起了一个dapr集成Alibaba Sentinel的提案&#xff0c;很快被社区加入到了1.2的里程碑中并且在1.2 release 相关升级文档里可以看到已经实现了对Alibaba Sentinel的支持。今天我们就讲讲我们如何通过…

预售┃让苹果CEO库克折服的程序员仅10岁!?

▲数据汪特别推荐点击上图进入玩酷屋在国外&#xff0c;编程教育课早已普及&#xff0c;美国、英国、新加坡等国家少儿编程已进入小学标准必修课程体系。韩国、日本也相继在2017年和2020年开展一年级至初三的编程教育普及。美国总统孙女&#xff0c;五岁开始学习在电脑上编程最…

java 字符串小写_Java字符串如何转换大小写?

程序开发中,经常需要对字符串进行转换操作,例如将字符串转换成数组的形式,将字符串中的字符进行大小写转换等。接下来通过一个案例来演示字符串的转换操作。 public class string03 {public static void main(String[] args) {String str="abcd"; System.out.prin…

入门机器学习,就这么简单!

AI这个词相信大家都非常熟悉&#xff0c;近几年来人工智能圈子格外热闹&#xff0c;光是AlphoGo就让大家对它刮目相看。今天小天就来跟大家唠一唠如何进军人工智能的第一步——机器学习。在机器学习领域&#xff0c;Python已经成为了主流。一方面因为这门语言简单易上手&#x…

java 生成jar_java如何生成jar

将*.class文件压缩成一个文件交付给用户使用&#xff0c;那么这样的文件就称为jar文件。如果要想生成jar文件&#xff0c;直接使用JDK中bin目录里的jar.exe就可以将所有的类文件进行压缩。此命令是随JDK一起安装的&#xff0c;直接在命令行中输入jar&#xff0c;即可看到此命令…

windows server 2012 dhcp 配置故障转移

在前面&#xff0c;青年怪客搭建的活动目录服务&#xff0c;在前面的内容中&#xff0c;我希望能在一个企业中&#xff0c;有一到两台域服务器可以管理我们企业内容中的计算机&#xff0c;但是一般的企业都是需要DHCP服务器的&#xff0c;为此我在前面的内容中&#xff0c;也配…

程序猿秃顶算工伤吗?

全世界只有3.14 % 的人关注了数据与算法之美在各个年龄段的程序猿中&#xff0c;70后以年龄优势遥遥领先&#xff0c;80后开始油腻&#xff0c;90后开始掉发&#xff0c;掉发的迅猛程度隐隐有赶超80后的趋势&#xff0c;脱单已然不是他们最担心的问题&#xff0c;脱发才是!程序…

web容器获取SSL指纹实现和ByPass

前言前段时间对SSL指纹的获取实现很感兴趣&#xff0c;从表面到深入再到实现让我更加深刻理解SSL设计。本篇介绍&#xff1a;SSL指纹在web容器(Kestrel)下如何获取&#xff0c;并实现一个Middleware来很方便集成到web工程里面(下文附源码地址)。解析ClientHello的套路以及如何生…

许可证( License LicenseLicenseLicenseLicenseLicense)服务器配置

在这里我要说明一下&#xff0c;如果你没有lic文件&#xff0c;可以到官方注册帐号&#xff0c;进行申请&#xff0c;我这里我申请的是一个90天的试用的序列号&#xff0c;下面是我在配置过程中的一些截图发出来。大家可以看一下。 老样子&#xff0c;开机过几秒中会提示出来的…

java struts 文件下载_Struts2文件下载实例

通过《Struts2文件下载简介》教程的学习&#xff0c;读者已经了解了使用 Struts2 框架实现在指定的目录中下载指定文件的功能。下面通过案例演示文件下载功能。1)创建下载页面在 struts2Demo06 项目的 WebContent 目录下创建一个名称为 download.jsp 的页面文件&#xff0c;在文…

预售┃16个有趣的海洋实验,美到让人窒息!

▲数据汪特别推荐点击上图进入玩酷屋我们的孩子&#xff0c;在科技发展迅速的年代&#xff0c;从出生就接触着各种高科技&#xff0c;接触电子产品已经是必然。但小木时常在想&#xff0c;除了ipad和那些普通的玩具&#xff0c;我们还能给孩子玩儿些什么呢&#xff1f;有没有宝…

深入LINQ | 揭开IQueryable的面纱

原文&#xff1a;bit.ly/3uAXliC作者&#xff1a;Jeremy Likness译者&#xff1a;精致码农-王亮在上一篇深入LINQ | 动态构建LINQ表达式 博文中&#xff0c;我们探索了表达式的强大&#xff0c;并用它来动态地构建一个基于 JSON 的规则引擎。在这篇文章中&#xff0c;我们反过来…

科幻作家阿西莫夫上世纪预言2019: 计算机彻底变革教育,太空移民进行中

全世界只有3.14 % 的人关注了数据与算法之美2019年必将是充满机遇与挑战的一年&#xff0c;一年的时间世界可能发生很大的变化。我们应该如何期待新的一年&#xff1f;在这&#xff0c;数据汪带大家重新读1983年美国作家艾萨克阿西莫夫&#xff08;Isaac Asimov&#xff09;对2…

6月程序员平均工资出炉,你拖后腿了吗?

据有关部门统计&#xff1a;6月份全国招收程序员435501人&#xff0c;平均工资为15052元&#xff0c;很多小伙伴儿纷纷感慨工资被平均了。然而6月程序员工资的中位数却是13000元&#xff0c;这说明什么&#xff1f;也许不是被平均&#xff0c;而是真的拖后腿了&#xff0c;下面…

学习爬虫限时只需9.9,还在犹豫什么?

收拾行李回老家的小天&#xff0c;新春福利还是要准时送到大家手上的&#xff01;随着互联网的发展&#xff0c;google、百度等搜索引擎让我们获取信息愈加方便。Python是当今世界最热门的编程语言之一&#xff0c;在科研领域也发挥着强大的作用&#xff0c;尤其是在日常的学习…

如何快速正确的安装 Ruby, Rails 运行环境

2019独角兽企业重金招聘Python工程师标准>>> 系统需求 首先确定操作系统环境&#xff0c;不建议在 Windows 上面搞&#xff0c;所以你需要用: Mac OS X任意 Linux 发行版本(Ubuntu,CentOS, Redhat, ArchLinux ...)强烈新手使用 Ubuntu 省掉不必要的麻烦&#xff01;…

你怕是对MD5算法有误解

大家常听到“MD5加密”、“对称加密”、“非对称加密”&#xff0c;那么MD5属于哪种加密算法&#xff1f;面试官问这样的问题&#xff0c;准是在给你挖坑。"MD5加密"纯属口嗨&#xff0c;MD5不是加密算法&#xff0c;是摘要算法。今天小码甲带大家梳理加密算法、摘要…