Browse Source

Add support for C# 12 primary constructors.

pull/3235/head
Siegfried Pammer 10 months ago
parent
commit
2043e5dd6f
  1. 13
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs
  2. 14
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs
  3. 53
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
  4. 63
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  5. 70
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  6. 53
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  7. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  8. 10
      ILSpy/Properties/Resources.Designer.cs
  9. 3
      ILSpy/Properties/Resources.resx

13
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs

@ -50,24 +50,17 @@ public static class Program @@ -50,24 +50,17 @@ public static class Program
[Serializable]
[SpecialName]
[CompilationMapping(SourceConstructFlags.Closure)]
internal sealed class getSeq_00405 : GeneratedSequenceBase<int>
internal sealed class getSeq_00405(int pc, int current) : GeneratedSequenceBase<int>()
{
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int pc;
public int pc = pc;
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public int current = current;
public override int GenerateNext(ref IEnumerable<int> next)
{

14
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
// C:\Users\Siegfried\Documents\Visual Studio 2017\Projects\ConsoleApp13\ConsoleApplication1\bin\Release\ConsoleApplication1.exe
// ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Global type: <Module>
@ -51,24 +50,17 @@ public static class Program @@ -51,24 +50,17 @@ public static class Program
[Serializable]
[SpecialName]
[CompilationMapping(SourceConstructFlags.Closure)]
internal sealed class getSeq_00405 : GeneratedSequenceBase<int>
internal sealed class getSeq_00405(int pc, int current) : GeneratedSequenceBase<int>()
{
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int pc;
public int pc = pc;
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public int current = current;
public override int GenerateNext(ref IEnumerable<int> next)
{

53
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs

@ -80,5 +80,58 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -80,5 +80,58 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public unsafe static int StaticSizeOf = sizeof(SimpleStruct);
public unsafe int SizeOf = sizeof(SimpleStruct);
}
#if CS120
public class ClassWithPrimaryCtorUsingGlobalParameter(int a)
{
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToField(int a)
{
private readonly int a = a;
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToFieldAndUsedInMethod(int a)
{
#pragma warning disable CS9124 // Parameter is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
private readonly int _a = a;
#pragma warning restore CS9124 // Parameter is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToProperty(int a)
{
public int A { get; set; } = a;
public void Print()
{
Console.WriteLine(A);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToEvent(EventHandler a)
{
public event EventHandler A = a;
public void Print()
{
Console.WriteLine(this.A);
}
}
#endif
}
}

63
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -337,6 +337,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -337,6 +337,8 @@ namespace ICSharpCode.Decompiler.CSharp
{
if (settings.AnonymousMethods && IsAnonymousMethodCacheField(field, metadata))
return true;
if (settings.UsePrimaryConstructorSyntaxForNonRecordTypes && IsPrimaryConstructorParameterBackingField(field, metadata))
return true;
if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field, metadata, out var propertyName))
{
if (!settings.GetterOnlyAutomaticProperties && IsGetterOnlyProperty(propertyName))
@ -390,6 +392,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -390,6 +392,11 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
}
static bool IsPrimaryConstructorParameterBackingField(SRM.FieldDefinition field, MetadataReader metadata)
{
var name = metadata.GetString(field.Name);
return name.StartsWith("<", StringComparison.Ordinal) && name.EndsWith(">P", StringComparison.Ordinal);
}
static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata)
{
@ -1303,12 +1310,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1303,12 +1310,12 @@ namespace ICSharpCode.Decompiler.CSharp
// e.g. DelegateDeclaration
return entityDecl;
}
bool isRecord = typeDef.Kind switch {
TypeKind.Class => settings.RecordClasses && typeDef.IsRecord,
TypeKind.Struct => settings.RecordStructs && typeDef.IsRecord,
bool isRecordLike = typeDef.Kind switch {
TypeKind.Class => (settings.RecordClasses && typeDef.IsRecord) || settings.UsePrimaryConstructorSyntaxForNonRecordTypes,
TypeKind.Struct => (settings.RecordStructs && typeDef.IsRecord) || settings.UsePrimaryConstructorSyntaxForNonRecordTypes,
_ => false,
};
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
RecordDecompiler recordDecompiler = isRecordLike ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
if (recordDecompiler != null)
decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler);
@ -1318,33 +1325,41 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1318,33 +1325,41 @@ namespace ICSharpCode.Decompiler.CSharp
{
ParameterDeclaration pd = typeSystemAstBuilder.ConvertParameter(p);
(IProperty prop, IField field) = recordDecompiler.GetPropertyInfoByPrimaryConstructorParameter(p);
Syntax.Attribute[] attributes = prop.GetAttributes().Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes.Length > 0)
if (prop != null)
{
var section = new AttributeSection {
AttributeTarget = "property"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
var attributes = prop?.GetAttributes().Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes?.Length > 0)
{
var section = new AttributeSection {
AttributeTarget = "property"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
}
}
attributes = field.GetAttributes()
.Where(a => !PatternStatementTransform.attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName))
.Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes.Length > 0)
if (field != null && (recordDecompiler.FieldIsGenerated(field) || typeDef.IsRecord))
{
var section = new AttributeSection {
AttributeTarget = "field"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
var attributes = field.GetAttributes()
.Where(a => !PatternStatementTransform.attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName))
.Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes.Length > 0)
{
var section = new AttributeSection {
AttributeTarget = "field"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
}
}
typeDecl.PrimaryConstructorParameters.Add(pd);
}
}
// With C# 9 records, the relative order of fields and properties matters:
IEnumerable<IMember> fieldsAndProperties = recordDecompiler?.FieldsAndProperties
?? typeDef.Fields.Concat<IMember>(typeDef.Properties);
IEnumerable<IMember> fieldsAndProperties = isRecordLike && typeDef.IsRecord
? recordDecompiler.FieldsAndProperties
: typeDef.Fields.Concat<IMember>(typeDef.Properties);
// For COM interop scenarios, the relative order of virtual functions/properties matters:
IEnumerable<IMember> allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) :
@ -1481,6 +1496,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1481,6 +1496,10 @@ namespace ICSharpCode.Decompiler.CSharp
{
return;
}
if (recordDecompiler?.FieldIsGenerated(field) == true)
{
return;
}
entityDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field));
entityMap.Add(field, entityDecl);
break;

70
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -43,8 +43,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -43,8 +43,8 @@ namespace ICSharpCode.Decompiler.CSharp
readonly IType baseClass;
readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>();
readonly Dictionary<IProperty, IField> autoPropertyToBackingField = new Dictionary<IProperty, IField>();
readonly Dictionary<IParameter, IProperty> primaryCtorParameterToAutoProperty = new Dictionary<IParameter, IProperty>();
readonly Dictionary<IProperty, IParameter> autoPropertyToPrimaryCtorParameter = new Dictionary<IProperty, IParameter>();
readonly Dictionary<IParameter, IMember> primaryCtorParameterToAutoPropertyOrBackingField = new Dictionary<IParameter, IMember>();
readonly Dictionary<IMember, IParameter> autoPropertyOrBackingFieldToPrimaryCtorParameter = new Dictionary<IMember, IParameter>();
public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, DecompilerSettings settings, CancellationToken cancellationToken)
{
@ -78,6 +78,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -78,6 +78,8 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsAutoProperty(IProperty p, out IField field)
{
field = null;
if (p.IsStatic)
return false;
if (p.Parameters.Count != 0)
return false;
if (p.Getter != null)
@ -158,8 +160,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -158,8 +160,18 @@ namespace ICSharpCode.Decompiler.CSharp
IMethod DetectPrimaryConstructor()
{
if (!settings.UsePrimaryConstructorSyntax)
return null;
if (recordTypeDef.IsRecord)
{
if (!settings.UsePrimaryConstructorSyntax)
return null;
}
else
{
if (!settings.UsePrimaryConstructorSyntaxForNonRecordTypes)
return null;
if (isStruct)
return null;
}
var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
foreach (var method in recordTypeDef.Methods)
@ -170,8 +182,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -170,8 +182,8 @@ namespace ICSharpCode.Decompiler.CSharp
var m = method.Specialize(subst);
if (IsPrimaryConstructor(m, method))
return method;
primaryCtorParameterToAutoProperty.Clear();
autoPropertyToPrimaryCtorParameter.Clear();
primaryCtorParameterToAutoPropertyOrBackingField.Clear();
autoPropertyOrBackingFieldToPrimaryCtorParameter.Clear();
}
return null;
@ -205,10 +217,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -205,10 +217,18 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!(value.Kind == VariableKind.Parameter && value.Index == i))
return false;
if (!backingFieldToAutoProperty.TryGetValue(field, out var property))
return false;
primaryCtorParameterToAutoProperty.Add(unspecializedMethod.Parameters[i], property);
autoPropertyToPrimaryCtorParameter.Add(property, unspecializedMethod.Parameters[i]);
IMember backingMember;
if (backingFieldToAutoProperty.TryGetValue(field, out var property))
{
backingMember = property;
}
else
{
backingMember = field;
}
primaryCtorParameterToAutoPropertyOrBackingField.Add(unspecializedMethod.Parameters[i], backingMember);
autoPropertyOrBackingFieldToPrimaryCtorParameter.Add(backingMember, unspecializedMethod.Parameters[i]);
}
if (!isStruct)
@ -261,6 +281,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -261,6 +281,9 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
public bool MethodIsGenerated(IMethod method)
{
if (!recordTypeDef.IsRecord)
return false;
if (IsCopyConstructor(method))
{
return IsGeneratedCopyConstructor(method);
@ -320,6 +343,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -320,6 +343,9 @@ namespace ICSharpCode.Decompiler.CSharp
internal bool PropertyIsGenerated(IProperty property)
{
if (!recordTypeDef.IsRecord)
return false;
switch (property.Name)
{
case "EqualityContract" when !isStruct:
@ -329,17 +355,35 @@ namespace ICSharpCode.Decompiler.CSharp @@ -329,17 +355,35 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
internal bool FieldIsGenerated(IField field)
{
if (!settings.UsePrimaryConstructorSyntaxForNonRecordTypes)
return false;
var name = field.Name;
return name.StartsWith("<", StringComparison.Ordinal)
&& name.EndsWith(">P", StringComparison.Ordinal)
&& field.IsCompilerGenerated();
}
public bool IsPropertyDeclaredByPrimaryConstructor(IProperty property)
{
var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
return primaryCtor != null
&& autoPropertyToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst));
&& autoPropertyOrBackingFieldToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst));
}
internal (IProperty prop, IField field) GetPropertyInfoByPrimaryConstructorParameter(IParameter parameter)
{
var prop = primaryCtorParameterToAutoProperty[parameter];
return (prop, autoPropertyToBackingField[prop]);
var member = primaryCtorParameterToAutoPropertyOrBackingField[parameter];
if (member is IField field)
return (null, field);
return ((IProperty)member, autoPropertyToBackingField[(IProperty)member]);
}
internal IParameter GetPrimaryConstructorParameterFromBackingField(IField field)
{
return autoPropertyOrBackingFieldToPrimaryCtorParameter[field];
}
public bool IsCopyConstructor(IMethod method)

53
ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -24,7 +23,9 @@ using System.Reflection; @@ -24,7 +23,9 @@ using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using SRM = System.Reflection.Metadata;
@ -37,10 +38,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -37,10 +38,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public class TransformFieldAndConstructorInitializers : DepthFirstAstVisitor, IAstTransform
{
TransformContext context;
Dictionary<IField, IL.ILVariable> fieldToVariableMap;
public void Run(AstNode node, TransformContext context)
{
this.context = context;
this.fieldToVariableMap = new();
try
{
@ -56,6 +59,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -56,6 +59,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
finally
{
this.context = null;
this.fieldToVariableMap = null;
}
}
@ -131,6 +135,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -131,6 +135,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
newBaseType.BaseType = baseType;
ci.Arguments.MoveTo(newBaseType.Arguments);
}
if (constructorDeclaration.Parent is TypeDeclaration { PrimaryConstructorParameters: var parameters })
{
foreach (var (cpd, ppd) in constructorDeclaration.Parameters.Zip(parameters))
{
ppd.CopyAnnotationsFrom(cpd);
}
}
constructorDeclaration.Remove();
}
}
@ -203,18 +214,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -203,18 +214,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (fieldOrPropertyOrEventDecl is CustomEventDeclaration)
break;
Expression initializer = m.Get<Expression>("initializer").Single();
// 'this'/'base' cannot be used in initializers
if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression))
break;
if (initializer.Annotation<ILVariableResolveResult>()?.Variable.Kind == IL.VariableKind.Parameter)
var v = initializer.Annotation<ILVariableResolveResult>()?.Variable;
if (v?.Kind == IL.VariableKind.Parameter)
{
// remove record ctor parameter assignments
if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record))
if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent, record))
break;
isStructPrimaryCtor = true;
if (fieldOrPropertyOrEvent is IField f)
fieldToVariableMap.Add(f, v);
}
else
{
@ -264,11 +276,21 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -264,11 +276,21 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
bool IsPropertyDeclaredByPrimaryCtor(IProperty p, RecordDecompiler record)
bool IsPropertyDeclaredByPrimaryCtor(IMember m, RecordDecompiler record)
{
if (p == null || record == null)
if (record == null)
return false;
return record.IsPropertyDeclaredByPrimaryConstructor(p);
switch (m)
{
case IProperty p:
return record.IsPropertyDeclaredByPrimaryConstructor(p);
case IField f:
return true;
case IEvent e:
return true;
default:
return false;
}
}
void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition)
@ -443,5 +465,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -443,5 +465,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false;
}
}
public override void VisitIdentifier(Identifier identifier)
{
if (identifier.Parent?.GetSymbol() is not IField field)
{
return;
}
if (!fieldToVariableMap.TryGetValue(field, out var v))
{
return;
}
identifier.Parent.RemoveAnnotations<MemberResolveResult>();
identifier.Parent.AddAnnotation(new ILVariableResolveResult(v));
identifier.ReplaceWith(Identifier.Create(v.Name));
}
}
}

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -162,12 +162,13 @@ namespace ICSharpCode.Decompiler @@ -162,12 +162,13 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp12_0)
{
refReadOnlyParameters = false;
usePrimaryConstructorSyntaxForNonRecordTypes = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (refReadOnlyParameters)
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0;
@ -2015,6 +2016,24 @@ namespace ICSharpCode.Decompiler @@ -2015,6 +2016,24 @@ namespace ICSharpCode.Decompiler
}
}
bool usePrimaryConstructorSyntaxForNonRecordTypes = true;
/// <summary>
/// Use primary constructor syntax with classes and structs.
/// </summary>
[Category("C# 12.0 / VS 2022.8")]
[Description("DecompilerSettings.UsePrimaryConstructorSyntaxForNonRecordTypes")]
public bool UsePrimaryConstructorSyntaxForNonRecordTypes {
get { return usePrimaryConstructorSyntaxForNonRecordTypes; }
set {
if (usePrimaryConstructorSyntaxForNonRecordTypes != value)
{
usePrimaryConstructorSyntaxForNonRecordTypes = value;
OnPropertyChanged();
}
}
}
bool separateLocalVariableDeclarations = false;
/// <summary>

10
ILSpy/Properties/Resources.Designer.cs generated

@ -1478,6 +1478,16 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1478,6 +1478,16 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Use primary constructor syntax for non-record types.
/// </summary>
public static string DecompilerSettings_UsePrimaryConstructorDecompilerSettings_SyntaxForNonRecordTypes {
get {
return ResourceManager.GetString("DecompilerSettings.UsePrimaryConstructorDecompilerSettings.SyntaxForNonRecordType" +
"s", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use primary constructor syntax with records.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -516,6 +516,9 @@ Are you sure you want to continue?</value> @@ -516,6 +516,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.UsePatternBasedFixedStatement" xml:space="preserve">
<value>Use pattern-based fixed statement</value>
</data>
<data name="DecompilerSettings.UsePrimaryConstructorDecompilerSettings.SyntaxForNonRecordTypes" xml:space="preserve">
<value>Use primary constructor syntax for non-record types</value>
</data>
<data name="DecompilerSettings.UsePrimaryConstructorSyntax" xml:space="preserve">
<value>Use primary constructor syntax with records</value>
</data>

Loading…
Cancel
Save