The project exporter wrote every XAML document to the project root under
a fully-qualified name while the code-behind class went into a directory
named after its namespace, so the two halves of one partial class ended
up in different places. WPF tooling pairs MainWindow.xaml with
MainWindow.xaml.cs by name and location; anything else is an unrelated
file to it, and --nested-directories made the split wider still by moving
only the C# half.
Both now go through one function that decides where a type's files live,
so the document lands where the type's own C# file would have, and the
code-behind is named after the document. The BAML writers of the UI and
of the command line had grown their own copies of the naming, which is
how they came to disagree with the C# writer in the first place.
Assisted-by: Claude:claude-opus-5:Claude Code
A WPF assembly keeps its windows and pages as BAML, so a project exported
without converting them back is missing the parts that make it a WPF
application - and the reader has no XAML to look at either. The CLI could
do the conversion since --decompile-baml was added, but only if asked,
which meant that everything the project exporter learned about WPF (Page
and ApplicationDefinition items, resources, generated members removed
from the code-behind) was invisible to anyone following issue #2253 from
the command line.
The flag is kept and ignored: it is documented and scripted against, and
asking for what is now the default has to keep working.
The test fixture is a real .g.resources container rather than a directly
embedded .baml stream, because only entries inside a container reach
WriteResourceToFile - a standalone .baml is copied out untouched, which
is worth its own look.
Assisted-by: Claude:claude-opus-5:Claude Code
Every mode loaded the input file with its own `new PEFile(fileName)` call, so a
bundle - which the UI opens fine, because LoadedAssembly goes through the file
loader registry - failed identically in all of them. The five call sites now
share one load helper that runs the same registry, which also makes archives
work and keeps one detection point for both front ends.
Which assembly inside a bundle is meant is not something to guess, so a package
without --bundle-entry lists its managed entries and exits non-zero. The entry
point is only annotated in that listing: the manifest has no marker for it, so
it is derived the way the host does it, from the runtime-config entry's name,
and stays unannotated where that does not resolve.
Assisted-by: Claude:claude-opus-5:Claude Code
Dock 12.1.0.6 removed the public JsonConverterFactoryList/JsonConverterList<T>
converters from Dock.Serializer.SystemTextJson that ILSpyDockJson used to
deserialize IList<T> into ObservableCollection<T>. Dock now does the same
substitution with an internal JsonTypeInfo modifier that swaps CreateObject
for IList<T> enumerables. ILSpyDockJson mirrors that technique in its own
modifier chain, which also removes the per-element JsonSerializer.Serialize
side effect the old converter had.
Also bumps Xaml.Behaviors.Avalonia, ProDataGrid, AwesomeAssertions, CliWrap,
NUnit3TestAdapter, and the decompiler minor version to 11.1.
Assisted-by: Claude:claude-fable-5:Claude Code
Typing "M:System.Linq.Enumerable.Where" at a command line is a reasonable thing
to do, and it found nothing: resolution compares the whole id string, so a form
without the parameter list only ever matched a member that genuinely takes none.
Spelling the signature out is no answer, because it means knowing the overload
count before asking. The same goes for a generic arity - and the exact spelling,
Dictionary`2, does not even survive an unquoted bash prompt, where a backtick
starts command substitution.
None of that makes the short form legal. Measured against Roslyn: its own
DocumentationCommentId resolver accepts no abbreviation at all, and the compiler
never emits one - a cref is a different grammar, which the compiler binds and
rewrites into a full id, warning CS0419 and picking one member when the cref is
ambiguous. A prefixed cref is copied through unvalidated, so an id in a
documentation file can be anything a human typed.
So the id grammar stays exact and IdStringProvider stays with it, which is what
lets cref-following trust its answer. The tolerance belongs to the callers that
serve people typing, and lives in DocumentationIdSearch as a ladder that loosens
one thing at a time: the exact id, then the id without its parameter list, then
without generic arities. Stating a detail wrongly still finds nothing; only
leaving one out asks for any. A rung may match several members and all of them
are returned, because which to present is the caller's decision and hiding the
rest would hide that the id was ambiguous.
ilspycmd shows every member of the group, headed by a comment naming the
ambiguity, and accepts the shapes people actually type: no prefix, a shortened
namespace, and arity written the cref or C# way.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
The dump is only useful if it shows the same thing the real pipeline is fed and
covers everything a transform bug can hide in, so: accessor bodies are reached
through the metadata handles (ITypeDefinition.Methods hides every method that
has method semantics), the PDB reaches the ILReader (UseDebugSymbols alone is
inert without DebugInfo), and the writing options carry the same sugar as the
UI's ILAst pane.
A method whose body cannot be read, transformed or written no longer aborts the
run, and the failure now travels through the decompilation-error path, so a
crashing transform is visible in stderr and in the exit code instead of being
buried in the output a script just collected.
BlockILTransform entries name the transforms they contain: two of them ran as
identical rows before, and asking for a nested transform by name reported it as
unknown while the listing did in fact run it.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
The decompiler's intermediate representation was reachable only through the
GUI's ILAst language, so anyone debugging a transform or a matcher had to do it
by hand in the UI. --ilast writes the same representation to stdout, and
--after-transform truncates the pipeline at a chosen point, which makes the
effect of a single transform diffable and scriptable.
Debug-only, like the UI language it mirrors: ILAst serves ILSpy's own
development, not the users of the released tool, so it stays out of the shipped
NuGet package and out of the README's option list.
Nested per-block transforms stay unaddressable: they run inside a
BlockILTransform entry, and reaching them needs the Stepper, which is compiled
out of release builds anyway.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
* Set v11 RTM
* Update features in README.md
* Remove the two 900-iteration process-list scroll tests
* Keep Svg.Controls.Skia.Avalonia at 12.0.0.13
* 10.0.11 and Roslyn for net11p7
* Fix module-scan test failing when PowerShell's NGen images are stale
* Opt Pack NuGets out of the MSBuild server to fix SBOM generation
Decompiling one method or property previously required decompiling its
whole type and searching the output, which is wasteful for scripted and
agent-driven use against large assemblies. The new -m|--member option
accepts an XML documentation id string (the syntax of compiler-generated
documentation files and of the UI's --navigateto option) or a metadata
token in 0x06000005 form, resolves it against the main module (with a
bounds check for tokens and distinct error messages for malformed ids,
unknown members, and members of other modules), and prints just that
member through the engine's single-member decompilation path.
The tests seed a new ICSharpCode.ILSpyCmd.Tests project (part of the
XPlat and Desktop solution filters and of the per-project CI test
steps), driving the real Main in-process via InternalsVisibleTo, so
future ilspycmd features have a dedicated home for CLI tests.
Assisted-by: Claude:claude-fable-5:Claude Code