diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs
index 8905736a8..06d88df04 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs
@@ -35,13 +35,15 @@ namespace Issue1325
internal class Test
{
- public string Parameterized {
- get {
- throw new NotImplementedException();
- }
- set {
- throw new NotImplementedException();
- }
+ // C# has no syntax for parameterized property 'Parameterized'.
+ public string get_Parameterized(int i)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void set_Parameterized(int i, string value)
+ {
+ throw new NotImplementedException();
}
public string Unparameterized { get; set; }
}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs
index 8f034867a..40caf9e62 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs
@@ -3,8 +3,7 @@ using System;
public interface IParameterized
{
- // C# has no syntax for parameterized properties; the accessors of
- // property 'IndexedValue' are emitted as ordinary methods.
+ // C# has no syntax for parameterized property 'IndexedValue'.
int get_IndexedValue(int index);
void set_IndexedValue(int index, int Value);
}
@@ -13,8 +12,7 @@ public class ParameterizedProperties : IParameterized
{
private int _field;
- // C# has no syntax for parameterized properties; the accessors of
- // property 'SharedProp' are emitted as ordinary methods.
+ // C# has no syntax for parameterized property 'SharedProp'.
public static int get_SharedProp(int index)
{
return index;
@@ -24,8 +22,7 @@ public class ParameterizedProperties : IParameterized
{
}
- // C# has no syntax for parameterized properties; the accessors of
- // property 'IndexedValue' are emitted as ordinary methods.
+ // C# has no syntax for parameterized property 'IndexedValue'.
public int get_IndexedValue(int index)
{
return _field;
@@ -36,16 +33,14 @@ public class ParameterizedProperties : IParameterized
_field = value;
}
- // C# has no syntax for parameterized properties; the accessors of
- // property 'ReadOnlyProp' are emitted as ordinary methods.
+ // C# has no syntax for parameterized property 'ReadOnlyProp'.
public int get_ReadOnlyProp(int index)
{
return index;
}
- // C# has no syntax for parameterized properties; the accessors of
- // property 'Attributed' are emitted as ordinary methods. The property's
- // attributes are kept below under the inert 'property:' target (CS0657).
+ // C# has no syntax for parameterized property 'Attributed'.
+ // Its 'property:' attributes below are ignored by the compiler (CS0657).
[property: Obsolete("read-write parameterized property")]
public int get_Attributed(int index)
{
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 3c5c61d41..9f16135a7 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -1338,7 +1338,14 @@ namespace ICSharpCode.Decompiler.CSharp
case HandleKind.PropertyDefinition:
IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity);
parentExtensionInfo = property.ResolveExtensionInfo();
- syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
+ if (property.IsParameterizedProperty())
+ {
+ syntaxTree.Members.AddRange(DecompileParameterizedProperty(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
+ }
+ else
+ {
+ syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
+ }
if (first)
{
parentTypeDef = property.DeclaringTypeDefinition;
@@ -1869,6 +1876,15 @@ namespace ICSharpCode.Decompiler.CSharp
{
return;
}
+ if (property.IsParameterizedProperty())
+ {
+ foreach (var accessorDecl in DecompileParameterizedProperty(property, decompileRun, decompilationContext, null))
+ {
+ entityMap.Add(property, accessorDecl);
+ EnqueueReferencedMembers(accessorDecl);
+ }
+ return;
+ }
entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property), null);
entityMap.Add(property, entityDecl);
break;
@@ -1897,19 +1913,24 @@ namespace ICSharpCode.Decompiler.CSharp
throw new ArgumentOutOfRangeException("Unexpected member type");
}
- foreach (var node in entityDecl.Descendants)
+ EnqueueReferencedMembers(entityDecl);
+
+ void EnqueueReferencedMembers(EntityDeclaration decl)
{
- var rr = node.GetResolveResult();
- if (rr is MemberResolveResult mrr
- && mrr.Member.DeclaringTypeDefinition == typeDef
- && !(mrr.Member is IMethod { IsLocalFunction: true }))
+ foreach (var node in decl.Descendants)
{
- workList.Enqueue(mrr.Member);
- }
- else if (rr is TypeResolveResult trr
- && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef)
- {
- workList.Enqueue(trr.Type.GetDefinition()!);
+ var rr = node.GetResolveResult();
+ if (rr is MemberResolveResult mrr
+ && mrr.Member.DeclaringTypeDefinition == typeDef
+ && !(mrr.Member is IMethod { IsLocalFunction: true }))
+ {
+ workList.Enqueue(mrr.Member);
+ }
+ else if (rr is TypeResolveResult trr
+ && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef)
+ {
+ workList.Enqueue(trr.Type.GetDefinition()!);
+ }
}
}
}
@@ -2047,7 +2068,10 @@ namespace ICSharpCode.Decompiler.CSharp
{
methodDecl.Modifiers |= Modifiers.Extern;
}
- if (method.SymbolKind == SymbolKind.Method && !method.IsExplicitInterfaceImplementation
+ // Accessors qualify only when they are emitted as ordinary methods (parameterized
+ // properties); an Accessor node cannot carry the 'new' modifier.
+ if ((method.SymbolKind == SymbolKind.Method || (method.SymbolKind == SymbolKind.Accessor && methodDecl is MethodDeclaration))
+ && !method.IsExplicitInterfaceImplementation
&& methodDefinition.HasFlag(System.Reflection.MethodAttributes.Virtual) == methodDefinition.HasFlag(System.Reflection.MethodAttributes.NewSlot))
{
SetNewModifier(methodDecl);
@@ -2456,6 +2480,40 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
}
+ ///
+ /// Decompiles a parameterized property (a named property with parameters, e.g. from
+ /// VB.NET) into declarations of its accessor methods, because C# has no syntax for
+ /// such properties. The property-level attributes are placed on the first accessor
+ /// under the 'property:' attribute target, which is not valid for methods and is
+ /// therefore ignored by the C# compiler (CS0657): recompilation neither loses the
+ /// attributes from the source nor misapplies them to the accessor method.
+ ///
+ List DecompileParameterizedProperty(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
+ {
+ var result = new List(2);
+ var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
+ foreach (var accessor in new[] { property.Getter, property.Setter })
+ {
+ if (accessor == null)
+ continue;
+ var accessorDecl = DoDecompile(accessor, decompileRun, decompilationContext.WithCurrentMember(accessor), extensionInfo);
+ if (result.Count == 0)
+ {
+ accessorDecl.AddLeadingTrivia(new Comment($" C# has no syntax for parameterized property '{property.Name}'."));
+ var attributes = property.GetAttributes().Select(typeSystemAstBuilder.ConvertAttribute).ToList();
+ if (attributes.Count > 0)
+ {
+ var attrSection = new AttributeSection { AttributeTarget = "property" };
+ attrSection.Attributes.AddRange(attributes);
+ accessorDecl.Attributes.InsertAfter(null, attrSection);
+ accessorDecl.AddLeadingTrivia(new Comment(" Its 'property:' attributes below are ignored by the compiler (CS0657)."));
+ }
+ }
+ result.Add(accessorDecl);
+ }
+ return result;
+ }
+
EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo)
{
Debug.Assert(decompilationContext.CurrentMember == property);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index 897e157a1..7f8a7259c 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -1871,6 +1871,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ConvertDestructor((IMethod)entity);
case SymbolKind.Accessor:
IMethod accessor = (IMethod)entity;
+ if (accessor.AccessorOwner is IProperty owner && owner.IsParameterizedProperty())
+ {
+ // C# cannot represent the parameterized property itself; its accessors
+ // are declared as ordinary methods.
+ return ConvertMethod(accessor);
+ }
Accessibility ownerAccessibility = accessor.AccessorOwner?.Accessibility ?? Accessibility.None;
return ConvertAccessor(accessor, accessor.AccessorKind, ownerAccessibility, false)!;
default:
diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
index 1b624c739..8d958ac73 100644
--- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
@@ -171,6 +171,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
}
+ ///
+ /// Gets whether the property is a parameterized property that is not an indexer,
+ /// i.e. a named property with parameters (a VB.NET parameterized property or a
+ /// C++/CLI indexed property). C# has no syntax for declaring or using such a
+ /// property; only its accessor methods can be represented.
+ ///
+ public static bool IsParameterizedProperty(this IProperty property)
+ {
+ return property.SymbolKind == SymbolKind.Property && property.Parameters.Count > 0;
+ }
+
///
/// Gets whether the type is an open type (contains type parameters).
///