追风赶月莫停留,平芜尽处是春山。这篇文章主要讲述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 CORECLR
internal 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 were trying to be compatible with the native compiler, we ignore
((object)member.ContainingAssembly != _sourceAssembly ||// imported fields
member.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,将不会显示编译错误。
虽然感觉知道了也并没什么鸟用
参考资料
[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?
【out参数不用赋值(这么神奇吗!)】
推荐阅读
- 针对prometheus监控系统的influxdb数据库内存优化 #yyds干货盘点#
- Spring Data开发手册|手摸手教你简化持久层开发工作
- 全网最硬核 Java 新内存模型解析与实验单篇版(不断更新QA中)
- Volcano(在离线作业混部管理平台,实现智能资源管理和作业调度)
- 记录一次离线下centos7.1升级到指定的centos7.4系统
- 单机MySQL多实例主从同步
- (胎教级教学)在腾讯云轻量应用服务器上部署javaweb项目
- centos7.4系统不正常断电后修复记录
- Flink SQL Client综合实战