Represent VB.NET parameterized properties as accessor methods in C# output - #3917
Merged
Conversation
C# has no syntax for named properties with parameters, so the expected output declares the accessors as ordinary methods, keeps the property-level attributes under the inert 'property:' attribute target (ignored by csc with CS0657), and consumes the properties through direct accessor calls, which Roslyn permits exactly for properties whose shape C# cannot bind. Red against the current decompiler, which emits a parameterless property whose body references the vanished parameters. Assisted-by: Claude:claude-fable-5:Claude Code
siegfriedpammer
force-pushed
the
parameterized-properties
branch
from
July 27, 2026 05:43
63bb1cf to
591759c
Compare
This was
linked to
issues
Jul 27, 2026
siegfriedpammer
force-pushed
the
parameterized-properties
branch
from
July 27, 2026 06:12
5d4e547 to
19bc00c
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves ILSpy’s C# decompilation for named, parameterized properties (e.g., VB.NET parameterized properties / C++/CLI indexed properties) by representing them as ordinary get_*/set_* accessor methods, avoiding uncompilable C# output and restoring accurate signature rendering and documentation behavior.
Changes:
- Decompile non-indexer parameterized properties to accessor method declarations (with explanatory comments and preserved property-level attributes via
property:target). - Restore parameter lists for parameterized properties in signature surfaces (tree/tooltip/search) and improve XML doc propagation to accessor-method representations.
- Add/adjust fixtures and tests covering parameterized properties and corrupt “no accessor” property metadata.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| ILSpy/TextView/DecompilerTextView.axaml.cs | Tooltip XML doc fallback: show owning property docs when hovering accessor-method representations. |
| ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs | Adds IsParameterizedProperty() helper to identify named parameterized (non-indexer) properties. |
| ICSharpCode.Decompiler/CSharp/Transforms/AddXmlDocumentationTransform.cs | Applies property XML docs to the first accessor when parameterized properties are emitted as methods. |
| ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs | Converts parameterized-property accessors to MethodDeclaration instead of C# accessor syntax. |
| ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs | Ensures signature surfaces show parameter lists for parameterized properties. |
| ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | Emits accessor methods for parameterized property declarations; preserves property attributes; updates referenced-member enqueue logic. |
| ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs | Adds VBPretty runner test for ParameterizedProperties. |
| ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.vb | New VB fixture input covering multiple parameterized-property shapes and call sites. |
| ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs | New expected C# output fixture validating accessor-method representation and call-site lowering. |
| ICSharpCode.Decompiler.Tests/TestCases/ILPretty/NoAccessorProperties.il | New IL fixture covering corrupt properties with no accessors (must not crash). |
| ICSharpCode.Decompiler.Tests/TestCases/ILPretty/NoAccessorProperties.cs | Expected C# output for the “no accessor” property metadata case. |
| ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs | Updates expected output to avoid uncompilable parameterized-property C#. |
| ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs | Adds unit test validating parameterized-property signature formatting. |
| ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs | Adds ILPretty runner test for NoAccessorProperties. |
| ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj | Includes new fixtures in the test project items. |
Comments suppressed due to low confidence (1)
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs:2075
- The expanded
SetNewModifiercondition now runs forSymbolKind.Accessorunconditionally. When an accessor is decompiled as anAccessornode (e.g., decompilingget_*/set_*directly),SetNewModifiercan addModifiers.New, and the output visitor will printnew get/new set, which is not valid C# syntax. RestrictSetNewModifierto accessors that are being emitted as ordinaryMethodDeclarations (parameterized-property path), notAccessornodes.
if (method.SymbolKind is SymbolKind.Method or SymbolKind.Accessor && !method.IsExplicitInterfaceImplementation
&& methodDefinition.HasFlag(System.Reflection.MethodAttributes.Virtual) == methodDefinition.HasFlag(System.Reflection.MethodAttributes.NewSlot))
{
SetNewModifier(methodDecl);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
C# cannot declare a named property with parameters: only the type's default member gets indexer syntax, and reusing it via [IndexerName] collapses for types with several differently-named indexed properties, static properties, or explicit interface implementations. Emitting the accessors as ordinary methods is the only fully general compilable form, matches how C# consumes such properties (Roslyn exposes the accessors of properties it cannot bind as regular methods, the same pattern C# 14 made user-facing for extension-member disambiguation), and round-trips call sites to identical IL. Call sites already lower to direct accessor calls. The property-level attributes are kept on the first accessor under the 'property:' attribute target: it is not valid on methods, so csc emits nothing for it (CS0657 warning) and recompilation neither loses the attributes from the source nor misapplies them to the accessor. A comment on the first accessor documents the deliberate deviation. Visual Studio's metadata-as-source view drops such properties' attributes entirely. The assembly tree and tooltips are unaffected: they keep rendering the property node with its parameter list. Known limitation, inherent to any C# projection: recompiling the output produces plain methods, so VB.NET consumers of the recompiled assembly lose property syntax. Assisted-by: Claude:claude-fable-5:Claude Code
The compiler-generated documentation file contains a P: entry for a parameterized property, but its accessors are emitted as ordinary methods, whose M: id has no documentation entry. Fall back to the owning property's documentation both when inserting XML documentation into decompiled output (on the first accessor only, to avoid duplicating it) and in the text view's tooltip (for either accessor). Assisted-by: Claude:claude-fable-5:Claude Code
The main tree, tooltips, and search results once showed the parameter list of a parameterized property; the ambience lost that when property rendering went through the converted AST node, whose C# property syntax cannot carry parameters. Take the parameter list from the symbol instead and render it in parentheses (matching VB.NET usage syntax and distinguishing these properties from indexers). Assisted-by: Claude:claude-fable-5:Claude Code
…rties The renamed-Implements case exposed a gap: the accessor-method declarations did not include the explicit-interface-implementation forwarders generated from .override directives, so decompiled types did not implement their interfaces and same-name implementations lost their interface mapping. DecompileParameterizedProperty now emits the same forwarder stubs as the ordinary method path. Assisted-by: Claude:claude-fable-5:Claude Code
Corrupt or hand-written metadata can contain a property with no MethodSemantics rows at all. MetadataTypeDefinition.Properties already skips properties with neither a visible getter nor setter, so neither the parameterized-property path nor the ordinary one ever sees them; the accessor-method emission also tolerates a missing accessor by construction. Pin that with an ILPretty case containing both a parameterized and an ordinary accessor-less property. Assisted-by: Claude:claude-fable-5:Claude Code
siegfriedpammer
force-pushed
the
parameterized-properties
branch
from
July 27, 2026 13:19
9292fd9 to
d624a28
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #1089 (and its sub-issues #1644, #2003; also the declaration half of the old #339/#1560 reports).
CLI metadata allows named properties with arbitrary parameter lists (VB.NET parameterized properties, C++/CLI indexed properties, COM PIAs); C# has syntax only for the default member. ILSpy previously emitted a parameterless property whose accessor bodies referenced the vanished parameters -- uncompilable output (see the
Issue1325fixture diff in this PR for a before/after).Example -- given this VB.NET input:
ILSpy previously produced (uncompilable --
indexis not declared anywhere):and now produces:
with call sites reading
x.set_Attributed(1, 42);/int v = x.get_Attributed(1);, and XML documentation (aP:entry) shown on the first accessor and in tooltips.Declarations (this PR): a parameterized non-indexer property now decompiles to its accessors as ordinary methods, with
property:attribute target. That target is not valid on method declarations, so the compiler ignores the attribute block with warning CS0657: the attributes stay visible and greppable in source, and recompilation neither drops them silently nor misapplies them to the accessor method. (For comparison, Visual Studio's metadata-as-source view renders such properties as bare accessor signatures and drops the property attributes entirely.)Call sites already lower to direct accessor calls (
t.set_Named(1, 2, value)) on master; the new fixture pins that behavior. This is the sanctioned C# consumption pattern: Roslyn exposes the accessors of properties whose shape C# cannot bind as regular callable methods (no CS0571), the same speakable-accessor pattern C# 14 made user-facing for extension-member disambiguation. Verified by compiling C# against a VB assembly usingget_/set_calls, including implicit and explicit interface implementations of the accessors.Why not indexer syntax +
[IndexerName]: all indexers of a type must share one name, so it fails for types with several differently-named indexed properties (the common COM-PIA shape), static parameterized properties, and explicit interface implementations -- and it would create a default member the original type does not have, while making direct accessor calls in sibling decompiled files illegal (CS0571). Not general enough to be worth the per-form call-site coupling.Documentation: the compiler-generated XML documentation file has a
P:entry for the property, which no emitted member id matches anymore; the decompiled output places the property's documentation on the first accessor, and the text-view tooltip shows it for either accessor.Signature surfaces: the tree/tooltip/search rendering of parameterized properties had lost its parameter list at some point long before the Avalonia rewrite (the ambience read parameters from the converted AST node, which C# property syntax cannot carry); this PR restores it, rendering
Data(int) : intwith parentheses to match VB.NET usage syntax and distinguish these from indexers.Known limitation (inherent to any C# projection): recompiling the output produces plain methods without property metadata, so VB.NET consumers of a recompiled assembly lose property-access syntax. Whole-project output currently surfaces CS0657 warnings; the generated projects define no
NoWarnmechanism today, so this PR does not introduce one.Tests: new
VBPretty/ParameterizedPropertiesfixture (interface + implicit/explicit implementation, static, read-only, attributed, virtual/override, overloaded, and renamed-Implementsproperties -- the last generating the same explicit-interface-implementation forwarder stubs as the ordinary method path -- plus consuming code), green across all 12 VB compiler configurations on Linux;ILPretty/Issue1325expectation updated from the old uncompilable output; full decompiler suite green (2517 passed / 0 failed / 38 skipped).This PR was prepared by an AI agent (Claude) on behalf of @siegfriedpammer.
🤖 Generated with Claude Code