Version 12 — user guide
MCP server for inspecting .NET assemblies
An AI agent that needs to look inside a compiled .NET assembly usually shells out to
ildasm (an IL dump) or ilspycmd (a full C# decompile) through a
generic Bash tool. Both dump an entire file as text. That output stays in the
agent's context as input tokens and is sent again on every later turn, so inspecting a single
class can cost thousands of tokens, and the cost recurs for the rest of the session.
The target measured below is ICSharpCode.Decompiler.dll 11.2.0.10050, a 3.0 MB
assembly with 749 public types. A full decompile of it costs the same however narrow the
question is, which is why the baseline column repeats.
| Task | ilspycmd cost | ILens tool | ILens cost |
|---|---|---|---|
| List the public types in a namespace | 1,240,053 tokens | find_types | 987 tokens |
| See the API surface of one mid-sized class | 1,240,053 tokens | list_members | 692 tokens |
| Find which types expose a given method | 1,240,053 tokens | find_methods | 668 tokens |
Measured against ICSharpCode.Decompiler.dll 11.2.0.10050 on
2026-11-08; token figures are character counts divided by 4, not a real tokenizer.
ILens turns assembly inspection into bounded, targeted lookups instead of full-file dumps — which is what makes it cheap enough to leave registered as an always-available tool.
ILens is an MCP server that speaks JSON-RPC over stdio and exposes tools in four categories:
discovery (finding types and methods by name or signature), inspection (a type's members,
cross-reference analysis, and what a Harmony mod depends on), decompilation (C# source for a
type or member, and a pattern search through it), and comparison (what differs between two
builds). It is read-only, inspects only assemblies under the directories named by
--allow-root, makes no network calls, and ships as a single self-contained Windows
binary.
Scope. ILens is built for inspecting C# assemblies, and its output is C#.
The metadata-driven tools — find_types, find_methods,
list_members, analyze — read language-agnostic metadata and work on an
assembly produced by any .NET language. decompile and analyze's
call_sites kind always emit C#, and search_code searches that C#. An
assembly compiled from F#, VB.NET, or C++/CLI still decompiles, but constructs the source
language has and C# does not may render unfaithfully.
ILens would be nothing without ILSpy. The decompiler, the analyzer infrastructure, and the
type system that lets ILens talk about a .NET assembly in C# terms at all are not ILens's
work — they belong to the ILSpy team. ILens links ICSharpCode.Decompiler for the
decompiler proper and ICSharpCode.ILSpyX for the analyzer infrastructure, and is,
candidly, a thin MCP shim on top of both.
That upstream work is MIT-licensed, which is the only reason ILens can exist at all. If ILens is useful to you, please consider giving ILSpy a star. They earned it.
To be clear: bug reports and feature requests about ILens belong on the ILens issue tracker, not ILSpy's. The upstream maintainers should not have to inherit downstream problems on top of their own.
System requirements: Windows 10/11, x64. The binary is self-contained — no separate .NET runtime install is required.
winget install Tadis.ILens
A standard winget portable install. It downloads the release ZIP, extracts it under
%LOCALAPPDATA%\Microsoft\WinGet\Packages\Tadis.ILens_*\, and registers
ilens as a command alias in %LOCALAPPDATA%\Microsoft\WinGet\Links\ —
a directory already on your user PATH if you have ever installed another winget portable
package. No admin elevation is needed.
For alternative installers see Appendix A; for updating and uninstalling see Appendix B; for installation troubleshooting see Appendix C.
Add an entry to .mcp.json in your project root. The command field
is the literal string "ilens" — the installer puts the binary on your PATH, so no
full path is needed.
{
"mcpServers": {
"ilens": {
"command": "ilens",
"args": [
"--allow-root", "C:\\path\\to\\dlls"
]
}
}
}
A first install needs a full restart, not just a new session. Two
independent gates have to clear before a freshly installed ilens actually launches
as an MCP server in a Claude app session.
The first is PATH propagation. Windows captures a process's
PATH from the registry when that process starts, and later registry changes do not
retroactively reach already-running processes. If ilens is a brand-new entry on
your user PATH — true for a first-ever winget portable install, which adds
%LOCALAPPDATA%\Microsoft\WinGet\Links\, and for any install.ps1 run,
which adds %LOCALAPPDATA%\Programs\ILens\ — an already-running Claude app holds a
stale environment, and every Claude Code session inside it inherits that stale environment from
the parent app. Subsequent winget updates of an already-installed package do not touch PATH and
do not trigger this gate.
The second is MCP-server registration. Claude Code reads
.mcp.json only at session start, so editing it inside a running session does not
register the new server in that session.
Recovering takes both: fully exit and relaunch Claude app, so the new app process gets the current registry PATH, and then start a new Claude Code session in the relaunched app. Existing Claude Code sessions survive a Claude app restart — the relaunched app reattaches to surviving session processes with their original environments intact — so a resumed session picks up neither change. Only a freshly spawned session inside the freshly launched app clears both gates.
The recipe for a first install via the Claude app: close Claude app entirely → install via
winget (or install.ps1) → relaunch Claude app → start a new Claude
Code session, not a resumed one.
Each --allow-root flag adds a directory tree from which assemblies may be
loaded. Without any --allow-root flags the server cannot load anything at all. Pass
the flag more than once to configure several roots. ILens also looks at the top of each root for
the assemblies an inspected assembly references, so the assemblies a mod builds against can sit
in a root of their own and still resolve when the mod is decompiled.
Three more options exist. --max-total-size <MB> sets the memory budget
described under Security model. --max-search-seconds <n>
sets how long one search_code call may decompile before it answers with how far it
came, 45 seconds unless given. A client built on the MCP SDK's defaults gives up on a request
after 60 seconds, and the 45 seconds leave a call the time to finish its answer within that, with
room for a slower machine; with 0, each call finishes at least one type. --version prints the
installed version and exits. Any other argument stops the server at startup.
For the equivalent claude mcp add CLI command see
Appendix D; for Claude Desktop see
Appendix E.
Registering the server makes the tools available. It does not by itself make Claude
prefer them: left to its habits, a model may still run ildasm or
ilspycmd in a shell, read the assembly file directly, or search the web for source
that does not match your build. ILens closes that gap itself, through the MCP server
instructions it sends, so no project needs a block that names ILens's tools. The README divides
setup into two steps: Step 1 registers the server, as described under
Integrating with Claude Code, and Step 2 reads as follows.
When a client connects, ILens sends it MCP server instructions: what ILens is for, which directories it can read, and which tool answers which question. Claude Code reads them at the start of every session, in the terminal and in the Claude desktop app's Code tab, so with Claude Code, Step 1 is the whole setup.
Claude Desktop's chat does not read server instructions. For it, and for any other client that
does not, add this paragraph to the client's instructions and replace
<allow-root> with the directory configured above. It names no tool, so it stays
correct when a release renames or merges tools:
## Inspecting .NET assemblies
Assemblies under `<allow-root>` are reachable through the `ilens` MCP server.
Prefer its tools over running `ildasm` or `ilspycmd` in a shell, reading
assembly files directly, or searching the web for source.
If your project's CLAUDE.md or a client's instructions hold the block an earlier
ILens README asked you to paste, delete it: it names tools that no longer exist. A client that
does not read server instructions takes the paragraph above in its place.
--allow-root flags are mandatory: with no roots configured, no assembly can be
loaded at all...) are rejected outright.--max-total-size <MB>): when a call ends above it, ILens
closes the assemblies used longest ago until it is back under three quarters of it, so the
budget can be exceeded only while calls run; an assembly that alone needs more is reopened for
each call, one whose file alone is larger than the budget is refused, and the decompiled text
search_code keeps for later searches takes at most a quarter of the budget.NUGET_PACKAGES or %USERPROFILE%\.nuget\packages)
that a .deps.json beside the target names.Nine tools, all read-only. Every tool except list_allowed_roots takes an
assembly path that must resolve inside a configured allow-root;
compare takes two, assembly_a and assembly_b. The path and
budget errors every assembly-loading tool shares are collected under
Troubleshooting rather than repeated per tool.
A value with exactly one reading is taken as the form the schema has, on
every parameter of every tool: a lone value for a parameter typed as an array
(kinds: "field" means ["field"], parameter_types: "string"
means ["string"]); a listed name in another case, without its underscores or with
spaces around it (UsedBy means used_by); a whole number given as text,
with a zero fraction or with an exponent; "true" or "false" given as
text; and null for an optional parameter, which counts as the parameter left out.
parameter_types also takes several types in one text, split by the tools themselves:
"string, int" means ["string", "int"], and a comma inside angle or
square brackets stays in its pattern, as in "Dictionary<int, string>". Any
other value a parameter does not take is an error, see
Strict arguments.
Lists. Every tool that answers with a list takes
limit (default 100); only decompile and list_allowed_roots
take none. The answer opens with a header that counts every entry, not just the ones shown, and
an empty answer is the header's own form, No types or No users of ...,
so it cannot be mistaken for a truncated one. When the limit cuts the list, a line after it says by
how much. A limit of 0 lists nothing, so the answer is the header, which holds the count, and
that line; a negative limit counts as 0. Lists are sorted by the name they print, so a limit cuts predictably
and the order stays stable across builds of the inspected assembly. Every answer is plain text.
find_types on ICSharpCode.Decompiler.dll with namespace
ICSharpCode.Decompiler.Metadata and limit 3:
44 types:
ICSharpCode.Decompiler.Metadata.AssemblyNameReference
ICSharpCode.Decompiler.Metadata.AssemblyReference
ICSharpCode.Decompiler.Metadata.AssemblyReferenceClassifier
... (41 more; raise limit to see all)
Not shown: 12 internal or private
The last line is the one find_types, find_methods and
list_members end with when their accessibility filter left matches out, so that a
list without a private member does not read as "the type has none"; with
accessibility public it counts them as non-public. Under an
empty answer the line also counts the compiler-generated matches and names the settings that list
everything counted, as for the namespace CallShapes.Concealed of the test assembly
CallShapes.dll:
No types
Not shown: 1 internal or private, 1 compiler-generated; include_compiler_generated with accessibility all lists them
analyze, which has no accessibility filter, puts such a line under the header of
an answer that is empty only because compiler-generated results were left out, before any line
of its own, and adds none under a list: No members exposing CallShapes.Callers,
then Not shown: 2 compiler-generated; include_compiler_generated lists them. A list
whose entries belong to members, the calls analyze lists for
call_sites and the lines search_code matches, names each member once,
on a line of its own, with its entries beneath it, indented by two spaces and in the order they
stand in the code. The header and limit count the entries, not the lines, so a limit
can cut inside a member's entries.
Names. ILens prints a type by its fully qualified name, with
. between a nested type and the type enclosing it, a generic type definition in C#'s
unbound form, System.Func<,> or
System.Collections.Generic.Dictionary<,>.Enumerator, and a constructed type
with its type arguments, System.Collections.Generic.List<int>. It prints a
member in reference form, Type.Method(int, string), with a constructor as
.ctor, a generic method with its type parameters after its name,
CallShapes.Namesakes.Store.Get<T>(string), a method made for type arguments
with those arguments, CallShapes.Namesakes.Store.Get<int>(string) in a
uses answer, and a conversion operator with its return type after it,
CallShapes.Namesakes.Amount.op_Explicit(Amount) → long, since two of them can differ
in nothing else. Parameter types and type arguments are written as C# writes them, by keyword or
short name: a parameter passed by reference with its keyword, ref, out,
in or ref readonly; a nullable value type as int? or
T?, except before a member's name,
System.Nullable<int>.GetValueOrDefault(); a two-dimensional array as
int[,]; and an array of arrays with the outermost array's brackets first,
int[][,] for an array of two-dimensional arrays. A line that would read the same as
another line of its answer in this short form is written in full, every type with its namespace
and the classes enclosing it, as find_methods writes the two explicit conversions of
CallShapes.Namesakes.Voucher, whose return types share a short name:
static CallShapes.Namesakes.Voucher.op_Explicit(CallShapes.Namesakes.Voucher) → CallShapes.Namesakes.Alpha.Badge
static CallShapes.Namesakes.Voucher.op_Explicit(CallShapes.Namesakes.Voucher) → CallShapes.Namesakes.Beta.Badge
Only list_members and compare's member lists print declarations,
which show what the reference form leaves out: this before an extension method's
first parameter, params, a parameter's default, and the @ C# needs
before a name that is a keyword, object @object.
Every tool takes these forms back, so a name from one answer can be passed to the next tool.
type_name also accepts the reflection form (Outer+Inner,
System.Func`2) and an argument list (System.Func<int, bool>, whose
argument count is the arity). A name is looked up among the types the assembly defines,
references and forwards to another assembly together. A name written without arity means the one
type of that name without type parameters where there is one, and otherwise the only type of that
name, so a name the assembly gives only to generic types of its own means the type without type
parameters it references or forwards. A name several generic types share, such as
System.Func, is an error that lists them. compare first reads a name as
written in both builds, so a class only one build has is answered as that build's, and a name
without brackets that finds a different class in each build is an error naming both:
Type name 'BaseShift.Arity.Crate' matches BaseShift.Arity.Crate<> in A and
BaseShift.Arity.Crate<,> in B. Name one as listed. An array type written as C#
writes it, byte[] or Shop.Part[][,], is taken by analyze
with kind extension_methods and by decompile, analyze and
compare with member_name, to find the extension methods code can call
on such an array; every other question refuses it.
Member lookup. decompile,
analyze and compare find a member of type_name by
member_name. A name without a hint means the member of the most derived class that
declares it, whatever kind of member that is, so a field that hides a base class's method of its
name is the field; the method stays reachable through a hint or by asking the base class. A
constructor is .ctor or the type's own simple name, Vault for
CallShapes.Vault, and is looked for on that type alone. A static constructor is
.cctor only, and a struct that declares no constructor has none, so where one is
created is a question for analyze's instantiated_by. An accessor is
named by its IL name, get_Count, set_Count, add_Changed or
remove_Changed. A member C# implements explicitly, which it names after the
interface, System.IDisposable.Dispose, is reached by its plain name only where no
other member has that name. A name C# writes with @ before a keyword,
@base, means the keyword.
Brackets after a method's name give its type parameters: Get<T>,
Get<int> and Get<> each mean the Get with one
type parameter, and Take<U> beside Take<T> selects by the
names. A name without brackets means the methods without type parameters wherever the name has
both, so Get on CallShapes.Namesakes.Store is
Get(string), not Get<T>(string). Overloads are told apart by a
hint of up to three parameters, each of which needs member_name:
parameter_count; parameter_types, the parameter types in order as
type patterns; and return_type, which selects
among conversion operators that differ in their return type alone. The hint is a filter, not a
tie-breaker: a lone method that does not satisfy it is not returned, and that no overload matches
is an answer of its own. It is matched against the type, then each base class, then the extension
methods, then the explicit interface implementations, and the most derived level that satisfies
it wins, so an overload a base class declares is found. It is held as an exact fit first, through
that whole search, and only where nothing fits exactly is it held loosely, as
find_methods holds a pattern, so an exact fit on a base class comes before a loose
one on the class asked about. So
Truncate with parameter_types decimal means
Truncate(decimal) beside Truncate(ref decimal). A hint that no method
without type parameters satisfies is held against the generic ones as well, so Put
with parameter_types T on CallShapes.Namesakes.Store means
Put<T>(T) beside Put(string). Where the name or the
hint leaves several methods, or none, the error lists them and says what tells them apart, see
Troubleshooting.
Where no member of the name answers, the name finds an extension method the inspected assembly
declares for the type, one of its base classes or one of its interfaces: any static method marked
as an extension method, as a C# static class, a Visual Basic module or an F# class declares one,
with its receiver passed by value or by ref, in or ref
readonly. One whose receiver is a type parameter is found through every type that meets its
constraints, by C#'s rule as the decompiler checks it, which differs from C# in four cases: it
takes unmanaged for struct, lets a ref struct meet a method without
constraints, fails a constraint that names a second type parameter, and lets a generic type asked
without type arguments meet a constraint only where every construction of it does. A property,
field or event of the name comes before any number of extension methods, except for
call_sites and compare, which look for a method. The answer names the
method under the class that declares it and marks it with the type it extends, here asked through
another library's class of the test assembly ExternalUser.dll:
// ExternalUser.Extended.KettleExtensions.Boil(Kettle) → void [extension method on ExternalLib.Extended.Kettle]
A method whose receiver is a type parameter is marked with the type parameter and its
constraints, [extension method on T where T : ExternalLib.Extended.IVessel], and one
for an array type with the array, [extension method on System.Byte[]]. Through an
array type, a public instance member of System.Array or System.Object
of the name comes first and is answered as through that class, while a static one such as
System.Array.Reverse does not; then come the extension methods C# would bind on such
an array, except those for Span<T> and ReadOnlySpan<T>, to
which C# 14 converts an array, so a method for a span is not found through an array. Through a
type another
assembly defines, see Types from other assemblies.
Type patterns. parameter_types and
return_type take patterns rather than exact names. A pattern is a type's name, its
full name, the end of its full name after any ., or a C# keyword (int,
string). It matches the declared type only, not a base or derived one.
List matches any List<T>, List<> any
one-argument List, and List<int> only that one. Brackets at the
end are read as C# writes an array, the outermost array's first and a comma for each dimension
past the first: int[,] matches a two-dimensional array and int[][,] an
array of them. A trailing * matches a pointer, and a tuple matches as the
ValueTuple<...> it prints as. A trailing ? is read as C# reads
it: int? matches the nullable type alone, string? what
string matches, and T? both the nullable form of a struct's type
parameter and a type parameter. A ref, out or in prefix
matches only a parameter passed by reference, whichever keyword it has. A type copied from a
line that names a member as declared, as find_methods and the headers write it,
matches what it came from, and so does a nullable type copied from the decompiled code, such as
string? or T?. A uses line names a member as the code
uses it, so its parameter list passes back only where it reads as the declaration's:
Crate<T>.Fill(T, List<T>) does not find
Crate<>.Fill(TItem, List<TItem>).
find_methods holds a pattern loosely: int also matches
int?, [] an array of any number of dimensions, and a pattern without a
prefix a parameter passed by value or by reference. An overload hint in decompile,
analyze or compare is held exactly first, see
Member lookup: a pattern without a prefix fits only a parameter
passed by value, int does not fit int?, a name without type arguments
fits only a type without them, [] fits only an array of one dimension, and
T? fits a type parameter only where the code marks it T?.
Generated code. The tools that list types or members,
find_types, find_methods, list_members,
search_code, analyze and compare, take
include_compiler_generated. Left false, they hide what the compiler or a source
generator created: closures, lambdas, iterator and async state machines, anonymous types,
backing fields and the other fields a compiler adds beside the member they belong to, the field
an enum keeps its value in, <PrivateImplementationDetails>, and Unity and
Burst source-generator output. A generated type named by type_name is listed or
searched whatever the switch says, in list_members, find_methods and
search_code, since it was asked for by name; search_code searches it where
the decompiler writes it as a type of its own. In search_code, a nested type the
decompiler writes inside the type around it, such as a lambda's class or an iterator's or async
method's state machine, has no lines of its own in the decompiled code, so naming it in
type_name is an error that names the nearest type around it with lines of its own,
whose search finds its contents. analyze lists generated results when the symbol it
is asked about is generated itself: the member where member_name is given, and the
type otherwise. Code the compiler moves out of the method it is written in, a lambda, a local
function, an iterator or an async method, is reported under that method by analyze
and search_code and compared as part of it by compare.
Accessibility. The tools that list API,
find_types, find_methods and list_members, take
accessibility: public, public_protected (the default, which
also covers protected internal and private protected), or
all, which adds internal and private. What it leaves out is
counted in a Not shown: line, see Lists. The other tools
are never filtered by accessibility, because an analysis, a comparison or a search of the code
must see everything.
Types from other assemblies. A type the inspected
assembly uses but another assembly defines is known to ILens by the assembly's references to it,
and a type the assembly forwards to another assembly by that forward. A tool that needs the
type's definition says where it lies, so you know which assembly to inspect instead:
System.String is not defined in this assembly; it comes from netstandard,
Version=2.0.0.0. for the test assembly CallShapes.dll, and System.String
is not defined in this assembly, which forwards it to System.Private.CoreLib,
Version=10.0.0.0. for the .NET runtime's System.Runtime.dll.
analyze answers questions about such a type from the inspected assembly's own
references to it, list_members with include_inherited reads the members
of base classes another assembly defines, and decompile, analyze and
compare find by name an extension method the inspected assembly declares for such a
type, as through a type it defines.
Through another library's type, a member of the type with the name comes first where C# would
bind it for a call from the inspected assembly, and keeps its answer, which for
decompile is the error naming where the type comes from. That is a public instance
member, an internal or protected internal one where the library grants the inspected assembly its
internals, and for an interface a member of a base interface. A protected, private protected,
private or static member, and an internal one the library does not grant, do not keep the
extension method out. With an overload hint, every method the inspected assembly's code can name
comes first where the hint fits it, static and protected ones included. Where the type's
library is not found, the members the inspected assembly's references name on the type come
first, an extension method is found only for the type itself or through a type parameter without
constraints, and the answer says what was not read:
// Not checked: whether ExternalLib.Extended.Kettle or its base types have a member Boil, which C# would take first; ILens could not load ExternalLib, Version=1.0.0.0
An answer that leaves something out for want of another assembly's class says so in a clause
of an error, a Not shown: line under list_members, or a
Not checked: line under decompile, analyze and
compare, and gives one of three reasons. ILens takes the first file of the library's
name it finds, beside the inspected assembly, then at the top of each allow-root in the order
given, then among the installed .NET libraries, so the remedy is always to make the right file the
first one found:
ILens could not load ExternalLib, Version=1.0.0.0: no file of the library's name
was found. Give the folder that holds it as an allow-root.<library> as loaded from <file>, Version=<version found>, does not
hold <class>; referenced is Version=<version referenced>, the last clause only
where the versions differ: the file found is another build than the one the assembly was made
against. Give the folder of the right build as an allow-root ahead of it. For a framework class,
which ILens otherwise reads from the .NET installed on the machine, that is the folder of the
framework the assembly runs on, such as a game's own libraries.ILens could not load <library>, Version=<version>: the file found,
<file>, holds the assembly <other>: the file is not the library its name
says.Reference assemblies. An assembly marked as a
reference assembly holds a library's declarations without their code: a .NET reference pack, a
.NET Framework reference assembly, a compiler's reference assembly, or a copy made for building
against, such as a publicized assembly in a build folder. Its method bodies are placeholders, so a
question that reads code is refused there rather than answered from them, where "nothing uses it"
would read as a fact about the library and a placeholder as the member's code:
analyze with used_by, instantiated_by,
read_by, assigned_by, call_sites or uses,
decompile, search_code, and find_harmony_dependencies. The
error says where to ask instead, here for System.Collections.dll of the .NET 10
reference pack:
Error: ArgumentException: This assembly is a reference assembly, which holds a library's declarations without their code, so 'used_by' cannot be answered from it; ask the build the library runs as. exposed_by, extension_methods, applied_to, overridden_by, implemented_by and implements read declarations alone and answer here.
decompile answers, for a whole type and for any member alike: This assembly
is a reference assembly, which holds a library's declarations without their code, so there is no
code to decompile; ask the build the library runs as. find_methods and list_members read
declarations alone and answer here. search_code answers the same, with
no code to search. The build the library runs as is the one the runtime loads, such
as the library of the same name in the installed .NET's shared folder for a .NET
reference pack. The questions that read declarations alone answer as for any assembly, and
compare compares no body of a reference assembly and says so beneath its lists.
Strict arguments. An argument name a tool does not declare
is rejected with the list of valid names. A parameter that does not apply to the question asked
is rejected too, such as parameter_name with a kind other than
call_sites, or an overload hint without member_name in
decompile, analyze or compare. A value a parameter
does not take is an error that names the parameter and what it takes, with every wrong value of a
call in one answer, as here for a find_types call:
Error: ArgumentException: Invalid value 7 for argument 'accessibility'. Valid values: public, public_protected, all. Invalid value 1.5 for argument 'limit'. Expected an integer.
A required parameter left out or null is Missing required argument 'kind'. An
exact type_name or namespace that names nothing is an error too, not an
empty answer; analyze, which answers about types the assembly only uses, ends its
empty answer about such a name with a line saying that the assembly neither defines nor
references a type of that name. A silently dropped filter, or a value read as another one, would
return a plausible answer to a question nobody asked.
find_methods read-onlyFind methods across the assembly by name, parameter types or return type, or list one type's
methods with type_name. Any combination of filters narrows the search.
assembly required stringname_contains optional string.ctor.return_type optional stringparameter_types.parameter_types optional string[]int also matches int?, and a trailing [] an array of any
number of dimensions, while int[,] matches a two-dimensional one. See
type patterns.parameter_count optional intparameter_types is also given, the two
must agree.namespace optional stringSystem.IO; its sub-namespaces are
not included.type_name optional stringSystem.IO.File. An exact name asserts that
the type exists, so a name that finds no type is an error rather than an empty list. A
compiler-generated type named here has its methods listed whatever
include_compiler_generated says.type_name_contains optional stringtype_name. Matching nothing is an ordinary empty result.accessibility optional public | public_protected | allpublic_protected, which also covers protected internal and
private protected; all adds internal and private. It filters the
methods by their own accessibility. See accessibility.include_compiler_generated optional bool<. Default false. See generated code.limit optional intA count line, then one method per line, sorted: static when it is, then the
method in reference form and its return type. Asked of ICSharpCode.Decompiler.dll
with name_contains DecompileTypeAsString:
1 method:
ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileTypeAsString(FullTypeName) → string
That is the form decompile and analyze take, and a parameter list
copied from it works as parameter_types. Constructors appear as .ctor,
the static one as .cctor, and operators as op_*; a struct or an enum
that declares no constructor has no .ctor line. Property and event accessors are left
out; decompile and analyze reach one by its IL name, such as
get_Count. A generic method carries its type parameters after its name, so it prints
apart from a namesake without them, as
CallShapes.Namesakes.Store.Get(string) → object and
CallShapes.Namesakes.Store.Get<T>(string) → T do. A parameter passed by
reference shows its keyword, ref, out, in or
ref readonly, but no parameter shows this or params, which
tell no two methods apart. Lines that would read alike, such as
overloads that take classes of one name from two namespaces, are written in full; see
names.
When the accessibility filter left methods out, the list ends with a line that
counts them. Asked of CallShapes.dll, one of the test assemblies in ILens's
repository, for the methods of a class with an internal method, a private one and a static
constructor:
5 methods:
CallShapes.Vault..ctor() → void
CallShapes.Vault..ctor(int) → void
CallShapes.Vault.Meter(int) → Func<int>
CallShapes.Vault.Read() → int
CallShapes.Vault.Rekey() → void
Not shown: 3 internal or private
No match: No methods, followed, where the default filters left matches out, by
the line that counts them and names the settings that list them, as in
Not shown: 2 internal or private; accessibility all lists them. See
lists.
type_name and type_name_contains given:
type_name and type_name_contains are mutually exclusive — type_name is an exact fully
qualified name, type_name_contains is a substring of the short name. Pass whichever one you
mean.type_name names no type the assembly defines: it finds none, a type the
assembly only references or forwards, or several types of the name. The messages are under
Troubleshooting.type_name names an array type, such as byte[]:
byte[] is an array type, which only analyze with kind 'extension_methods' and decompile,
analyze and compare with member_name take, to find the extension methods code can call on
it.namespace names a namespace in which no type of the assembly lies, not even in a
sub-namespace: Namespace not found: ...parameter_count contradicts the length of parameter_types:
parameter_count (1) contradicts parameter_types.Length (2).find_types to find the type first; decompile with
member_name to read a match; analyze with call_sites for
its callers.
find_types read-onlyFind the types the assembly defines, by namespace or by a substring of their name; with no filter, every type.
assembly required stringnamespace optional stringSystem.IO; its sub-namespaces are not included.name_contains optional stringStream finds MemoryStream and FileStream.accessibility optional public | public_protected | allpublic_protected, which also covers protected internal and
private protected; all adds internal and private. See
accessibility.include_compiler_generated optional boolfalse. See generated code.limit optional intA count line, then one fully qualified type name per line, sorted, in the form
type_name takes. Asked of ICSharpCode.Decompiler.dll with
namespace ICSharpCode.Decompiler.TypeSystem and limit 2:
79 types:
ICSharpCode.Decompiler.TypeSystem.Accessibility
ICSharpCode.Decompiler.TypeSystem.AccessibilityExtensions
... (77 more; raise limit to see all)
Not shown: 8 internal or private
The last line counts the types the accessibility filter left out; with
accessibility public it reads Not shown: 8 non-public. An
empty answer counts the compiler-generated types as well and names the settings that list
everything it counts. Asked of CallShapes.dll, one of the test assemblies in ILens's
repository, for the namespace CallShapes.Concealed, whose one type is internal and
holds a class the compiler generated for a lambda:
No types
Not shown: 1 internal or private, 1 compiler-generated; include_compiler_generated with accessibility all lists them
See lists for the header, the truncation line and the
Not shown: line every listing tool shares.
namespace names a namespace in which no type of the assembly lies, not even in a
sub-namespace: Namespace not found: CallShapes.Nowhere.list_members to read one result's members; find_methods to search by
signature instead of by type name; search_code to search the code itself.
list_allowed_roots read-onlyList the directories from which assemblies can be loaded. Every assembly argument
passed to another tool must point to a file inside one of them.
None.
One configured root per line, each as the --allow-root argument gave it, made
absolute; or, when the server was started without one,
No allowed roots configured. The server cannot load any assemblies. A root given
through a symbolic link or junction is listed as given, and an assembly under it may be asked for
through the link or by its real path; see the security model. The server
instructions name the same directories, the last ones only as a count when the full list would
make them too long.
None of its own; an argument it does not declare is refused as for every tool, see Troubleshooting.
Any tool taking an assembly parameter.
analyze read-onlyRun cross-reference analysis on a type or one of its members: who uses, reads, writes, instantiates, overrides or implements it, what it uses, and every call to a method with the arguments each call passes.
assembly required stringtype_name required stringSystem.IO.File. A type the assembly only
references or forwards is answered from the assembly's references to it, and an array type such
as byte[] is taken by kind extension_methods and with
member_name; see Types from other assemblies and Extension methods
below.kind required enumused_by, instantiated_by,
exposed_by, extension_methods, applied_to,
implemented_byused_by,
call_sites, overridden_by, implemented_by,
uses, implementsused_by, read_by,
assigned_by, uses, overridden_by,
implemented_by, implementsread_by, assigned_byused_by, overridden_by,
implemented_by, implementsmember_name optional stringReadAllText; omit it to analyze the type itself.
.ctor or the type's own name, such as Vault, names a constructor and
.cctor the static one; get_X, set_X, add_X or
remove_X names an accessor. Type parameters in brackets select a generic method,
Get<T>, and a name written with the @ C# needs before a keyword,
@base, is read as C# reads it. See member
lookup.parameter_count optional intparameter_types is also given, the two must agree. Needs
member_name.parameter_types optional string[]int means int before int?; see
member lookup and type
patterns. Needs member_name.return_type optional stringop_Explicit with return_type
int. Needs member_name.parameter_name optional stringcall_sites, show only the argument passed for this parameter;
overloads without it are left out. Written with the @ C# needs before a keyword,
@switch, it is read as C# reads it.non_default_only optional boolparameter_name, keep only the calls whose argument is not provably the
parameter's default. Default false.include_compiler_generated optional bool<. Default false. When the symbol asked about is itself
compiler-generated, the answer lists such results without it. Does not apply to kind
call_sites, which reports a call in generated code under the member it was written
in. See generated code.limit optional intcall_sites, of calls. Default 100.A header that counts the results in the kind's own words, then one result per line, sorted.
The examples below are asked of ICSharpCode.Decompiler.dll and of the test
assemblies in ILens's repository. Asked of ICSharpCode.Decompiler.dll who uses one
overload of DecompileType:
1 user of ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileType(FullTypeName) [declared]:
ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileTypeAsString(FullTypeName)
The other kinds count readers of, writers of, symbols instantiating, members exposing,
extension methods for, symbols marked with, overrides of, implementations of, symbols used by,
interface members implemented by, and calls to. No result: the header's own form,
No users of .... A result is a type, a member in reference form, or an attribute's
place on the assembly or the module (below). A member's header says where it was found:
[declared], [inherited from Base], or [extension method on
X]. An extension method found through the type it extends is named under the class that
declares it, and X is the type it extends. Where another member of its name would read alike, the
header writes the member in full:
1 user of CallShapes.Namesakes.Basket.Take(CallShapes.Namesakes.Alpha.Badge) [declared]:.
A use inside a lambda, local function, iterator or async method is reported under the member it
was written in, and a use inside an accessor under its property or event.
An answer that is empty only because compiler-generated results were left out says so under
its header. Asked of CallShapes.dll what an async method uses whose code only
awaits a task:
No symbols used by CallShapes.AsyncUses.Clerk.AwaitOnly(Task) [declared]
Not shown: 17 compiler-generated; include_compiler_generated lists them
Lines after the results name the route by which code can reach the member without naming it,
Override: and Implementation:, or what the answer could not check,
Not checked:; each is described below with the question it ends. A
Not checked: line that names a class ILens did not read ends with what the
reference search made of the class's library, which other
assemblies explains with what to do for each case. Where it reads ILens could not load
Lib, Version=1.0.0.0 and nothing more, giving the folder that holds that library as another
--allow-root lets ILens read it.
Who uses a type. used_by on a type lists the types and members
that name it: in their code, their signatures, their base types and interfaces, the constraints
on their type parameters or their attributes, using a member the type declares included. An
enum is a user of its underlying type. The type's own members and nested types are not listed,
nor the classes the compiler generated inside it; a member of a nested class someone wrote is.
An attribute names a type when its class is that type or names it as a type argument, and when
an argument names it: a typeof on its own, in an array or of a type built from it,
as typeof(List<Part>) names Part, or a value of it for an enum.
So used_by on an attribute class lists every place the attribute is applied, and on
an enum every place that carries an attribute given a value of it. An enum value in an attribute
is read at any width, whether or not the enum's assembly can be loaded. Where an attribute's
bytes fit two widths and the two readings disagree on whether it names the type, its carrier is
not listed, and the answer ends with a line such as Not checked: whether
ExternalUser.Widths.GaugeAttribute on ExternalUser.Widths.ReadTwoWays names it; its bytes fit more
than one size of ExternalLib.Grain. An attribute on the assembly or on the module is a
line of its own, the word C# writes the target with and the name the assembly or the module has.
Asked of ExternalUser.dll, which applies ExternalLib.ScopeAttribute to
both:
2 users of ExternalLib.ScopeAttribute:
assembly ExternalUser
module ExternalUser.dll
instantiated_by lists the members that create an instance of the type, with
new or, for a struct, default, and exposed_by the members,
private ones aside, whose type, return type or parameters name the type.
applied_to lists what carries an
attribute, the assembly and the module included. It requires an attribute class, also one whose
base attribute class another assembly defines. Where a base class cannot be loaded, the question
is answered, and an answer without any result ends with Not checked: whether
ExternalUser.DerivedWidget is an attribute; its base class ExternalLib.Widget was not read, since
ILens could not load ExternalLib, Version=1.0.0.0.
Who uses a method. used_by on a method lists the members whose
code names the method itself: a call, a new, or a reference to it as a delegate. A
call of a base class's member that the method hides names that member and is listed under it. C#
compiles a call to a virtual method as a call to the declaration that introduced it, so
used_by and call_sites on that declaration list every such call, also a call on an object of a
subclass whose compiled form names the subclass's override. A C# compiler names the override for
a method with a type parameter of its own and for an override that returns a more specific type,
the Visual Basic and F# compilers for any virtual method. So used_by on
CallShapes.BaseCalls.Gate.Pass<T>(T) lists
CallShapes.BaseCalls.Porter.Hand(Vault), whose call vault.Pass(5) the
compiler wrote as a call to the override in Vault. This holds for a method the
inspected assembly defines; for a method of another assembly's class, the answer lists what
names that method itself, so a call that names an override of it stands under that override. A
call through base. inside a lambda, an iterator or an async method counts under the
member it is written in, although the compiler moves it into a method of its own. On a
constructor, what carries an attribute applied through exactly that constructor counts beside
the code that calls it, an attribute on a lambda or a local function under the method it is
written in. This tells the constructors of an attribute class apart, where
applied_to on the class lists every place the attribute is applied.
Fields. read_by and assigned_by on a field follow
every use of it, including the uses that take its address: calling a method on a struct field,
passing the field by ref, or writing one of its members. When the code that
receives the address is in the inspected assembly, ILens reads it to decide, and a method of a
framework type that never changes its value, such as int or
DateTime, only reads it. A field passed by in or ref
readonly is read, whatever the method. Otherwise ILens cannot tell whether the field is
read or written, so the method is listed under both questions, and its line names what received
the address. Asked of FieldAccess.dll:
5 writers of FieldAccess.Holder.Number [declared]:
FieldAccess.Holder.BumpNumber()
FieldAccess.Holder.FillNumber()
FieldAccess.Holder.OverwriteNumber()
FieldAccess.Holder.ParseNumber(string) (may write: passes it by ref to System.Int32.TryParse(string, ref int))
FieldAccess.Holder.SetThroughRefLocal(bool)
The read_by answer marks the same line (may read: ...). The mark
names the method as the compiled call writes it, with ref for every parameter passed
by reference, so TryParse's out parameter reads ref int
there. A
constant, a decimal constant included, cannot be asked about: the compiler copies its value into
each place that uses it, so no instruction names it, and the question is an error that says so.
For a value of an enum, the error points to used_by on the enum.
Properties and events. On a property or event, the usage questions are
answered through the accessors, because that is what code references: read_by
consults the getter, assigned_by the setter, used_by both, and
uses both for the outgoing direction. On an event, used_by covers the
add, remove and invoke accessors and, for a field-like event of C# or Visual Basic, the field
holding its subscriber list, so the answer includes the code that raises the event and not only
the code that subscribes. The header names what was consulted:
(via get_Level, set_Level), or for an event
(via add_Announced, remove_Announced, the subscriber list). uses is
not offered on an event; uses on a method names the event whose subscriber list its
code reads or writes.
Attributes that set a member. An attribute that sets a field or property of
its class by name, as [Label(Text = "x")] sets Text, writes the member
without any instruction. assigned_by on such a field or property, and
used_by on the property or on its setter, list what carries such an attribute,
unmarked, beside the code that sets the member; call_sites on the setter counts those
attributes on a line of its own (see Call sites). An attribute of a class that inherits
the member counts for the class that declares
it, one of a class that declares the name anew does not, and one of a class that overrides the
property counts for the declaration it overrides. A member of another assembly's attribute class
is known from the attributes alone. Where ILens cannot read the library that says whether an
attribute class inherits the name, the answer ends with a line such as Not checked: whether
ExternalLib.OwnTraitAttribute, ExternalLib.SubTraitAttribute inherit Kind from
ExternalLib.TraitAttribute; ILens could not load ExternalLib, Version=1.0.0.0. An
attribute the compiler stores as flags or table rows rather than as an attribute, such as
StructLayout, DllImport and MarshalAs, is not read.
What a member uses. uses lists the members a method's code uses
and the types it names outright. The code of its lambdas, local functions, iterator and async
body counts as its own, a call through base. there included. A type gets a line where
the code names it in a cast, a type test (is, as), an
array creation, a default or new of a struct, a sizeof or
a typeof. A type built from others gives a line for each, so
(Rack<Bolt>)value gives CallShapes.TypeUses.Bolt and
CallShapes.TypeUses.Rack<>. It leaves out a type that only a local has or that
only the signature of a member used names; used_by on the type is the full test. The
compiler's own use of the same instructions gives a line too: the element type of the array a
params call builds, and System.Nullable<> where a nullable is set
to null. A member is written as the code uses it, with its type arguments, and a type parameter of
the method or its class by its name, so each instantiation of a generic method has a line of its
own. Asked of CallShapes.dll about a method that calls a generic method and its
namesakes:
7 symbols used by CallShapes.Namesakes.Customer.Use(Store) [declared]:
CallShapes.Namesakes.Store.Get(string)
CallShapes.Namesakes.Store.Get<int>(string)
CallShapes.Namesakes.Store.Only<int>(int)
CallShapes.Namesakes.Store.Run()
CallShapes.Namesakes.Store.Run<List<int>>()
CallShapes.Namesakes.Store.Take<int>(int)
CallShapes.Namesakes.Store.Take<string, int>(string)
In a generic class, a line names the class's type parameter,
CallShapes.GenericUses.Crate<T>.Fill(T, List<T>). On an async method,
uses lists what its author wrote: the task builder's calls, the awaiter's calls and
type, and the GetAwaiter call an await compiles to are left out, as in
the AwaitOnly answer above, and include_compiler_generated lists them. A
GetAwaiter().GetResult() the
author writes in the same method, on an awaiter type one of its awaits uses, is left out with
them; the rethrow an await foreach brings stays. A called method's parameter passed
by reference is written with the keyword its declaration has, out, in
or ref readonly; ILens looks for the declaration where decompile looks
for an assembly's references. Where it reads no declaration, it writes ref and the
answer ends with a line that says why. Asked of ExternalUser.dll, whose library
ExternalLib.dll lies in a folder that is no allow-root:
3 symbols used by ExternalUser.Keywords.Clerk.Count(Vault) [declared]:
ExternalLib.Vault.Peek(ref int)
ExternalLib.Vault.TryTake(int, ref string)
ExternalLib.Vault.Weigh(ref int, ref int)
Not checked: whether ref on a method of ExternalLib.Vault is out, in or ref readonly; ILens could not load ExternalLib, Version=1.0.0.0
With that folder as another --allow-root, the lines read
ExternalLib.Vault.Peek(ref readonly int),
ExternalLib.Vault.TryTake(int, out string) and
ExternalLib.Vault.Weigh(in int, ref int), and the last line goes. Where the library
found does not declare the method as called, the line ends ExternalLib.Vault as loaded
declares no such method; for a framework class, that happens where the .NET installed on
the machine is another version than the one the assembly runs on.
Call sites. call_sites lists every call to a method, each
calling member once on a line of its own with its calls beneath it, indented by two spaces and in
the order the code makes them. It covers all overloads of the name unless a hint selects one.
limit counts calls, not lines, and can cut inside a member's calls. Asked of
CallShapes.dll, where Thrice calls three times and
WithLambda once in its own code and once in a lambda:
6 calls to CallShapes.Repeats.Bell.Ring(int):
CallShapes.Repeats.Ringers.Once(Bell)
bell.Ring()
CallShapes.Repeats.Ringers.Thrice(Bell)
bell.Ring(3)
bell.Ring()
bell.Ring(2)
CallShapes.Repeats.Ringers.WithLambda(Bell)
bell.Ring(5)
bell.Ring(4)
A call is written as decompiled C# with its arguments in parameter order, so it need not
read as the source spelled it: a named or reordered argument stands at its parameter's position,
with the defaults the compiler filled in before it, trailing arguments equal to their default are
left out, and a call to an extension method is written as a static call with the receiver as its
first argument, KettleExtensions.Boil(kettle) for kettle.Boil(). A
call through base. in a lambda, an iterator or an async method stands under the
member it is written in: call_sites on CallShapes.BaseCalls.Gate.Open(int)
shows base.Open(width) and base.Open(width + 1) beneath
CallShapes.BaseCalls.Lodge.OpenTwice(int), which makes both calls in lambdas. With
parameter_name, each line shows only the argument that parameter receives, as the
compiler bound it, so an omitted argument shows the parameter's default; a value that provably
is the default is marked (default). non_default_only keeps the other
calls, and its header says how many of all calls they are, naming the default where every
overload has the same: 4 of 6 calls to CallShapes.Repeats.Bell.Ring(int) pass times other
than its default 1:.
A call that C# writes without call syntax is listed like any other, in the form the code has,
and the header's count includes it, so an answer about an operator or a base class's constructor
can hold thousands of calls. Asked of CallShapes.dll, these lines stand beneath
their members:
a + b, a += b(Coin)5GetEnumerator, MoveNext and Current calls of a
foreach, each as the loop's head: foreach (Coin item in till)Add call behind an entry of a collection initializer, the entry in braces:
{ b, "note" }Spare = { a }{ Empty: false }Deconstruct call of a deconstruction: var (num2, flag2) = till: base()Every call is counted twice, once in the IL and once in the decompiled C#. A caller whose
counts differ is not dropped but named after the calls, in a block for each kind of difference:
a sentence with the sum and the members beneath it. In a Not shown: block a member's
count follows its name unless it is its one call, as in : 2 of 3. Each block is cut
by limit on its own, and its
sentence stays when the limit shows no member, so an empty answer is a proof of absence. Asked
about the true operator, which a condition calls:
0 calls to CallShapes.Forms.Coin.op_True(Coin):
Not shown: 1 call in 1 member, which the decompiled C# does not show as a call:
CallShapes.Forms.Sums.IfSet(Coin)
The decompiled C# does not show as a call the creation of a delegate, the true
and false operators a condition calls, a property read through a pointer, an
indexer with a params array, or the copy method of a record that a
with expression calls. The members that fail to decompile stand in a block whose
sentence ends which failed to decompile:, and those whose decompiled C# shows more
calls than their IL holds in one that begins Check:, as in Check: 1 member
shows more calls in the decompiled C# than its IL holds:. On a constructor or a property's
setter, the answer keeps to calls and ends with a line that counts the attributes applied through
the constructor or setting the property by name, as for a constructor of an attribute class of
ExternalUser.dll: Not shown: 9 uses as an attribute; used_by lists
them.
Overrides. overridden_by lists the members that override the one
asked about, directly or through further overrides, an override that returns a more specific type
included; an override of a member declared anew with new virtual stands under that
member, not under the member it hides. On an accessor, it lists the accessors that override it.
call_sites on an override lists the calls whose receiver's type is the override's
class or derives from it, including calls through base., and used_by on
an override lists what names the override itself. Both end with a line naming the declaration
code can call instead. Asked of CallShapes.dll:
3 calls to CallShapes.Animal.Die(int):
CallShapes.Dog.Die(int)
base.Die(cause + 1)
CallShapes.Undertaker.OnAnimal(Animal)
animal.Die(2)
CallShapes.Undertaker.OnDog(Dog)
dog.Die(3)
Override: code can call CallShapes.Creature.Die(int) instead; listed are those calls whose receiver's type is CallShapes.Animal or derives from it.
Under used_by, the line ends so only references to this override are
listed; 'call_sites' lists those calls whose receiver's type is CallShapes.Animal or derives from
it., naming the accessors for a property or an event. The line says "can call", since a
call whose compiled form names the override itself stands in the list above it. When the
declaration lies in another assembly, the line says so, and call_sites cannot count
the calls made through it: Override: code can call the ExternalLib.Widget member it
overrides instead, which another assembly defines, so those calls are not counted here.
Interfaces. implements lists the interface members a method,
property, event or accessor implements, also of interfaces another assembly or the core library
defines; an accessor is answered with the interface's accessor. implemented_by on an
interface member lists the members classes bind it to, and an override of such a member that runs
in its place; a member that only shares the interface member's name and parameters, such as one
that hides the bound member, has another return type or is not public, is not listed. On an
accessor it lists the accessors that implement it, and a line under the question about a property
can be a method, where a compiler wrote accessor methods without a property. A member that only
classes below its own bind, such as a member of a base class without the interface, is marked
with those classes, since it implements the interface member for their objects alone. Asked of
ExplicitMembers.dll, in which five classes list ITag and inherit
Tag from TagBase:
1 implementation of ExplicitMembers.Inherited.ITag.Tag() [declared]:
ExplicitMembers.Inherited.TagBase.Tag() (for ExplicitMembers.Inherited.TagA, ExplicitMembers.Inherited.TagB, ExplicitMembers.Inherited.TagC and 2 more)
implements on TagBase.Tag() marks the line of
ExplicitMembers.Inherited.ITag.Tag() the same way, and answers for a member that
implements by a metadata row alone, whatever its name, such as the MoveNext of a
class the compiler generated or a Visual Basic member that implements under a name of its own.
implemented_by on a type requires an interface and lists the types that implement
it. Where a base type could not be read, either answer ends with a line such as Not
checked: what ExternalLib.IGauge and its base types declare; ILens could not load ExternalLib,
Version=1.0.0.0; implements has it only for a member a base type could bind,
one that is public and not static, or overrides.
A call through an interface names the interface's member, so used_by,
read_by, assigned_by and call_sites on a member that
implements one list only what names the member itself, and end with a line naming the interface
members code can call instead. Asked of ExplicitMembers.dll about a method that
implements the Reset of two interfaces:
No users of ExplicitMembers.Twins.Both.Reset() [declared]
Implementation: code can call ExplicitMembers.Twins.ILeft.Reset(), ExplicitMembers.Twins.IRight.Reset() instead; ask about those members for their uses.
With one interface member, the line ends ask about that member for its uses. It
stands for every member that implements, an ordinary public one, an explicit implementation and
one bound by a metadata row alike. A member that implements and overrides has both lines,
Implementation: first. Where a base type of the member's class was not read, the
answer ends with Not checked: whether it implements an interface member; ILens could not
load ExternalLib, Version=1.0.0.0, or more interface members where some were
found.
Extension methods. extension_methods lists the extension methods
whose receiver is the type, passed by value or by ref, in or
ref readonly: every static method marked as an extension method, in a C# static
class, a Visual Basic module or an F# class, for a type of another assembly as well. It lists the
type's own, not those for its base types. A method whose receiver is a type parameter is listed
under each type its constraints name, and one whose constraints name no type under
System.ValueType for a struct constraint and System.Object otherwise.
type_name takes an array type here as C# writes it, such as byte[],
int[,] or int[][], and the list holds the methods for exactly that array
and for an array of a type parameter the array fits; a method for an interface,
System.Array, object or a type parameter itself stays under that type.
A receiver written nint[] or dynamic[] counts for
System.IntPtr[] or object[]. Asked of CallShapes.dll:
3 extension methods for System.Byte[]:
CallShapes.Arrays.ArrayExtensions.Crest<T>(T[])
CallShapes.Arrays.ArrayExtensions.Hex(byte[])
CallShapes.Arrays.ArrayExtensions.InBounds<T>(T[], int)
With member_name, every kind is answered for an extension method found through
the type it extends, by the rules under member lookup. The
mark of a method whose receiver is a type parameter shows its constraints:
[extension method on T where T : CallShapes.Constraints.IShape],
[extension method on T[] where T : struct, System.IComparable<T>], and the mark
of one found through an array type names the array:
1 user of CallShapes.Arrays.ArrayExtensions.Hex(byte[]) [extension method on System.Byte[]]:.
An extension method the inspected assembly declares for a type of another assembly is found
through that type and answered with every kind, call_sites and uses
included. Types from other assemblies says which member of
that type comes first, and member lookup that
call_sites looks for a method alone. Asked of
ExternalUser.dll with the folder of its library ExternalLib.dll as an
allow-root, Pour through
ExternalLib.Extended.Kettle answers for Kettle.Pour(int),
Pour with parameter_types Kettle, string for the extension
method KettleExtensions.Pour(Kettle, string), and Reset for the
extension method, since Kettle's own Reset is static. Where the library
is not read, the members this assembly's references name on the type come first, only the
extension methods for the type itself are found, and the answer says what it could not check.
Asked with that folder outside the allow-roots:
1 user of ExternalUser.Extended.KettleExtensions.Boil(Kettle) [extension method on ExternalLib.Extended.Kettle]:
ExternalUser.Extended.Cook.Brew(Kettle)
Not checked: whether ExternalLib.Extended.Kettle or its base types have a member Boil, which C# would take first; ILens could not load ExternalLib, Version=1.0.0.0
The same line stands where a base class of a type of this assembly could not be read. Through
another assembly's type whose library is not read, a method whose receiver is a type parameter is
found only where it has no constraint. Through an array of such a type, a list ends with
Not checked: whether more extension methods apply to ExternalLib.Arrays.Beam[] through what
ExternalLib.Arrays.Beam derives from; ..., a method found by name with Not checked:
whether other extension methods named Glue apply to ExternalLib.Arrays.Beam[] through what
ExternalLib.Arrays.Beam derives from; ..., and the error for a name that finds nothing with
the clause what ExternalLib.Arrays.Beam derives from was not read, since ....
Types from other assemblies. For a type the assembly does not define, every
type kind and used_by, read_by and assigned_by on a member
are answered from the inspected assembly's references; see other
assemblies. A method's header writes each parameter passed by reference with the keyword its
declaration has, 2 users of ExternalLib.Vault.TryTake(int, out string): where
ExternalUser.dll is asked with its library's folder as an allow-root; where ILens
read no declaration, it writes ref and the answer ends with the Not checked:
whether ref on a method of ... line shown under What a member uses.
call_sites, uses, implements, overridden_by
and implemented_by on such a member are refused with the reason, unless the name
finds an extension method this assembly declares, which every kind answers. An empty answer says
why it is empty:
This assembly neither defines nor
references a type of that name., followed by the types the caller may have meant where
types of that name lie in another namespace or class:
Types named Basket: CallShapes.Namesakes.Basket.System.dll
of .NET 10: This assembly forwards it to System.Net.Requests, Version=0.0.0.0., and
about a member of it,
This assembly forwards System.Net.ServicePoint to System.Net.Requests, Version=0.0.0.0.This assembly references no member Fold of ExternalLib.Widget. It references: .ctor,
Changed, Count, Label, Paint, Size.A constant leaves no reference, since the compiler copies its value into each use. A
question about a member that is a constant in the library ILens finds is the constant error, and
where no assembly ILens found defines the type, the empty answer ends with ILens cannot tell
whether Fold is a constant, whose value the compiler copies into each use: no assembly it found
defines ExternalLib.Widget, which comes from ExternalLib, Version=1.0.0.0.
implemented_by on such a type is an error only when the assembly derives a class
from it, and its answer ends with Types deriving from a class in another assembly may also
implement it through that class; this assembly does not show which.
kind does not apply to the symbol's category:
Analysis kind 'read_by' is not valid for Method. Valid kinds for Method: used_by,
overridden_by, implemented_by, uses, implements, call_sites.applied_to on a type that does not derive from System.Attribute:
Analysis kind 'applied_to' requires an attribute type; CallShapes.Repeats.Ringers does not
derive from System.Attribute. An empty result would read as "applied nowhere". A class
whose base class cannot be loaded is answered instead, as above.implemented_by on a type that is not an interface:
Analysis kind 'implemented_by' on a type requires an interface; CallShapes.Repeats.Ringers
has kind Class. For a type of another assembly, only when the inspected assembly derives a
class from it: ... is a class, since ... derives from it.implemented_by on a member of a class or struct: Analysis kind
'implemented_by' on a member requires a member of an interface;
ExplicitMembers.Inherited.PlainBase.Name is declared by the class
ExplicitMembers.Inherited.PlainBase. 'overridden_by' lists its overrides., the last
sentence for a virtual, abstract or overriding member.read_by or assigned_by on a constant:
CallShapes.Defaults.Pace.Slow is a constant: the compiler copies its value into each place
that uses it, so the compiled code does not show which code reads it. Kind 'used_by' on
CallShapes.Defaults.Pace lists the methods that work with that enum through a variable, parameter
or member of its type. For assigned_by the first sentence reads ... is
a constant: only its declaration sets it, and the compiler copies its value into each place that
uses it., and for a constant of another assembly's type ExternalLib.Limits.Most is a
constant in ExternalLib: ...read_by on a write-only property, or assigned_by on a read-only one:
Analysis kind 'read_by' on a property resolves to its getter, and ... is write-only. Use
kind 'used_by' for every call site of the property.... declares no accessors, so it has no call sites to analyze.parameter_name and non_default_only
apply only to kind 'call_sites'., include_compiler_generated does not apply to kind
'call_sites'.; non_default_only without parameter_name:
non_default_only needs parameter_name: it compares the argument passed for that parameter
with the parameter's default.; a parameter_name no overload has:
No overload of 'Ring' on CallShapes.Repeats.Bell has a parameter named 'count'. Its
parameters: times.; non_default_only for a parameter without a default:
'...' has no default value in ..., so non_default_only has nothing to compare its arguments
with.parameter_count, parameter_types or return_type
without member_name: parameter_count, parameter_types and return_type select a
method overload, so they need member_name.uses, Analysis kind 'uses'
reads the member's body, which is in the assembly that defines ExternalLib.Widget, not in this
one. Analyze that assembly instead., and implements, which needs the class
that declares the member; call_sites, Analysis kind 'call_sites' is not
available on a member of ExternalLib.Widget, which this assembly does not define; 'used_by' lists
what uses it.; overridden_by and implemented_by, since an
override or implementation in this assembly leaves no reference to the member it overrides or
implements; a type-only kind, Analysis kind 'instantiated_by' is valid only for a type;
omit member_name to analyze ExternalLib.Widget itself. A hint for a name the assembly
references only as a field, property or event, or only as a setter attributes use, is refused,
and a name that finds several overloads or several kinds of member is an error listing them.extension_methods or one with
member_name: byte[] is an array type, which only analyze with kind
'extension_methods' and decompile, analyze and compare with member_name take, to find the
extension methods code can call on it. A name nothing on the array answers to:
Member 'Saw' not found on ExternalLib.Arrays.Beam[]: no public instance member of
System.Array or System.Object and no extension method of this assembly that applies to it has
that name, with the clause above where the element's library was not read.used_by,
instantiated_by, read_by, assigned_by,
call_sites or uses, is refused before any name is looked up:
This assembly is a reference assembly, which holds a library's declarations without their
code, so 'used_by' cannot be answered from it; ask the build the library runs as. exposed_by,
extension_methods, applied_to, overridden_by, implemented_by and implements read declarations
alone and answer here. See reference
assemblies.decompile to read a result's body; find_methods for name-shaped
rather than reference-shaped search; search_code for text in the decompiled
code.
find_harmony_dependencies read-onlyList what a Harmony-patching assembly depends on: every patch target, every field it reaches by
reflection, in a transpiler or as a ___name patch parameter, and every method,
property, event or constructor of another assembly it looks up by reflection, names in a
transpiler or takes as a delegate patch parameter. Built to answer "do my patches still bind on
the new version?"
Harmony, by Andreas Pardeike, is the library mods use to patch .NET methods at run time. ILens reads its attributes and calls in an assembly without including or running Harmony.
assembly required stringlimit optional intA count line, then one dependency per line, sorted by target so that everything depending on
one member stands together. A line holds its first word, the target, <-, the
method that depends on it, or the class for a target a class names itself, and in brackets how
the target was found. The answer for HarmonyMod.dll, a test assembly of the ILens
repository, has 373 lines; ten of them, in the answer's order:
Postfix ? <- HarmonyMod.LoopPatches.AfterAny [PatchCall in HarmonyMod.LoopPatches.Apply] ×3
Prefix HarmonyMod.TargetA.Compute(int) <- HarmonyMod.FieldAccessPatch.Prefix [TypedAttribute]
Postfix HarmonyMod.TargetA.PrivateMethod <- HarmonyMod.StringTargetedPatch.Postfix [StringTargeted]
Unapplied HarmonyMod.TargetB.SomeMethod <- HarmonyMod.MisnamedPatch.SomeMethodPostfix [TypedAttribute]
Field HarmonyMod.TargetI.count <- HarmonyMod.InjectedFieldsPatch.Prefix [Injected]
Prefix HarmonyMod.TargetK.Run <- HarmonyMod.ManualPatches.BeforeRun [PatchCall in HarmonyMod.ManualPatches.Apply]
Event System.AppDomain.AssemblyLoad <- HarmonyMod.MemberLookups.Use [GetEvent]
Member System.Text.StringBuilder.? <- HarmonyMod.MemberLookups.Use [GetMembers]
Method System.Text.StringBuilder.Append(string) <- HarmonyMod.MemberLookups.Use [AccessToolsMethod]
Property System.Text.StringBuilder.Length <- HarmonyMod.MemberLookups.Use [AccessToolsProperty]
Each line stands once, however many places in the code give it. A ? marks a part
the scan could not read, and a line with one that the scan found more than once ends with
×N, how often, since what those places name may differ; the ? of a
nullable value type, as in int?, marks nothing unread. Nothing found:
No dependencies.
Patches. The patch kinds are Prefix, Postfix,
Transpiler, Finalizer and ReversePatch, found as Harmony
finds them: by the method's exact name or by its marker attribute, such as
[HarmonyPrefix]. A class counts as a patch class when it carries
[HarmonyPatch], has a TargetMethod or TargetMethods, or has
a method carrying a [HarmonyPatch] of its own. Patches applied through
PatchProcessor, Harmony 2.4's inner prefixes and postfixes, and a delegate type's
attributes give no patch line. Unapplied stands in place of the patch kind for a
target named where Harmony applies no patch: the target of a method whose own
[HarmonyPatch] names one but whose name and attributes make it no patch method, and,
for a class without a patch method, what its TargetMethod gives or else its own
attribute's target:
Unapplied HarmonyMod.TargetB.SomeMethod <- HarmonyMod.MisnamedPatch.SomeMethodPostfix [TypedAttribute]
Unapplied HarmonyMod.TargetA.Compute(string) <- HarmonyMod.TargetOnlyPatch [TargetMethod]
Harmony never applies such a patch. It still runs such a class's TargetMethod,
and a target missing there stops the mod's PatchAll. An Unapplied line
needs a target with a class, so it never reads ?. A method a
Harmony.Patch call passes gets the call's line instead, and a class that patches
nothing and names no target, such as a container of nested patch classes, gets no line. A patch
target is found by one of these:
| Label | Where the target comes from |
|---|---|
TypedAttribute, StringTargeted, Attribute |
The [HarmonyPatch] attributes on the patch class and the patch method, merged as
Harmony merges them. How the three are told apart is explained below. |
TargetMethod | The one target a TargetMethod returns. |
DynamicTargetMethod | A TargetMethod with more than one
target, typically chosen by a ternary or a switch, or with a target the scan cannot read, so its
binding depends on the branch taken at run time. |
TargetMethods | Each target a TargetMethods returns or
yields. |
TargetMethodSearch, TargetMethodsSearch | A target the body finds by searching one class's methods for an exact name. The line names no overload, since the search's own tests pick one. |
PatchCall in X, PatchCallSearch in X | A
Harmony.Patch call: a line for each patch method it passes, of the kind its
argument's position gives, and each target. X is the member that writes the values
out: the calling member, or the caller or construction its values come through. The
Search form marks a target found by a search, as above. |
Fields. A Field line names a field the assembly looks up by
reflection, names in a transpiler, or takes from Harmony as a patch parameter. Lookups are read in
every class of the assembly, such as the static holders mods keep their field references in, not
only in patch classes, and a field of the mod's own classes is listed too:
| Label | Lookup |
|---|---|
AccessToolsField | AccessTools.Field, which also
searches the base classes |
DeclaredField | AccessTools.DeclaredField, which looks in
the class alone |
FieldRefAccess, StaticFieldRefAccess,
StructFieldRefAccess | Harmony's field-reference accessors, in all their overloads |
TraverseField | Traverse.Field, also as
Field<T> |
GetField | .NET's Type.GetField |
GetFields, GetDeclaredFields, GetFieldNames |
An enumeration of a class's fields, whose field is written ? |
LoadField, StoreField | A transpiler's
CodeInstruction.LoadField and StoreField, read as
AccessTools.Field is |
LoadsField, StoresField, GetFieldInfo |
CodeMatch.LoadsField and StoresField and
SymbolExtensions.GetFieldInfo, given a lambda written at the call that reads the
field |
Injected | A ___name parameter of a prefix, postfix or
finalizer, applied by attribute or by a Harmony.Patch call, through which Harmony
hands the patch a field. It is listed on each class the patch targets; Harmony looks the field up
in the class that declares the patched method, which differs when that method is inherited.
___ followed by digits names a field by its index and prints ?. |
Members looked up. A Method, Property,
Event or Member line names a member of another assembly's class that the
assembly looks up by reflection, names in a transpiler, or takes as a delegate:
Method for a method, an accessor or a constructor, Property for a
property or an indexer, Event for an event, and Member for
Type.GetMembers, which finds every kind. A lookup gets a line only where it adds
something: none for a member of a class the scanned assembly defines, which no other assembly's
update can break, and none where a patch line shows its result as the target. A second lookup of
the same member keeps its line, and so does one the scan cannot read in full. A property or event
lookup names the property or event, unless the code takes an accessor from it right at the call:
typeof(Version).GetProperty("Build").GetGetMethod() gives
Method System.Version.get_Build <- HarmonyMod.MemberLookups.Use [GetProperty]. An
enumeration names the class alone, Event System.AppDomain.? <-
HarmonyMod.MemberLookups.Use [GetEvents]. The label is the name of the call, of the
property read or of the class constructed, with AccessTools or Traverse
before it where the bare name is a kind of member:
| Label | Lookup | Line |
|---|---|---|
AccessToolsMethod, DeclaredMethod, TraverseMethod,
GetMethod | AccessTools.Method and DeclaredMethod,
Traverse.Method, Type.GetMethod | Method T.M, with
the parameter types where the call pins an overload |
MethodDelegate | AccessTools.MethodDelegate given a
method as "Namespace.Type:Method" text | Method T.M |
PropertyGetter, PropertySetter,
DeclaredPropertyGetter, DeclaredPropertySetter | AccessTools' accessor lookups | Method T.get_P or T.set_P |
AccessToolsProperty, DeclaredProperty,
TraverseProperty, GetProperty | AccessTools.Property
and DeclaredProperty, Traverse.Property,
Type.GetProperty | Property T.P |
Indexer, DeclaredIndexer, IndexerGetter,
IndexerSetter, DeclaredIndexerGetter,
DeclaredIndexerSetter | AccessTools' indexer lookups | Property
T.Item, or its accessor, Method T.get_Item(int) or
T.set_Item(int, ?) |
AccessToolsEvent, DeclaredEvent, GetEvent |
AccessTools.Event and DeclaredEvent,
Type.GetEvent | Event T.E |
EventAdder, EventRemover, DeclaredEventAdder,
DeclaredEventRemover | AccessTools' event accessor lookups | Method T.add_E or T.remove_E |
AccessToolsConstructor, DeclaredConstructor,
GetConstructor, TypeInitializer | AccessTools.Constructor
and DeclaredConstructor, Type.GetConstructor,
Type.TypeInitializer | Method T..ctor(int),
T..ctor() without parameter types, or T..cctor |
AccessToolsFinalizer, DeclaredFinalizer |
AccessTools.Finalizer and DeclaredFinalizer | Method
T.Finalize |
GetDeclaredMethods, FirstMethod, GetMethodNames,
TraverseMethods, GetMethods | An enumeration of a class's methods | Method T.? |
GetDeclaredProperties, FirstProperty,
GetPropertyNames, TraverseProperties, GetProperties |
An enumeration of a class's properties | Property T.? |
GetDeclaredConstructors, FirstConstructor,
GetConstructors | An enumeration of a class's constructors | Method
T..ctor or T..cctor |
GetEvents, GetMembers | Type.GetEvents,
Type.GetMembers | Event T.?, Member T.? |
GetMethodInfo | The method a SymbolExtensions.GetMethodInfo
lambda calls | Method T.M(int), the overload the lambda calls |
Call, Calls, CodeMatch | A transpiler's
CodeInstruction.Call, by class and name, as "Namespace.Type:Method"
text, or in a lambda written at the call; CodeMatch.Calls and new
CodeMatch(...) in such a lambda | Method T.M |
Injected, HarmonyDelegate | A delegate patch parameter;
AccessTools.HarmonyDelegate<T>() | Method T.M |
GetMethodByModuleAndToken | AccessTools.GetMethodByModuleAndToken,
which names a method by a module and a metadata token | Method ? |
A lambda that makes a delegate from a method group, () => Helper, names that
method, for the transpiler's helpers and for SymbolExtensions.GetMethodInfo alike; a
lambda the call is given through a parameter or a local reads ?. A method of the
mod's own class named in a transpiler adds no line, as no other lookup of one does, while a field
of it does. A prefix, postfix or finalizer that takes a parameter of a delegate type the mod
defines and marks with [HarmonyDelegate] or [HarmonyPatch], naming a
method of another assembly's class, gives a Method line labeled
Injected, since Harmony hands the patch a delegate to that method and fails the patch
where it is missing: Method System.Random.Next <- HarmonyMod.DelegatePatches.Before
[Injected]. A parameter whose name starts with __ is one of Harmony's own and
gets none, nor does a postfix's first parameter where the postfix returns its type, which takes
the result. AccessTools.HarmonyDelegate<T>() gives the same line labeled
HarmonyDelegate, and ? where T is a type parameter or a
delegate type of another assembly. A marked delegate type that no patch method takes gives no
line, since Harmony then looks nothing up.
What the scan reads. Harmony's extension spelling of a lookup,
typeof(X).Method("Y"), counts as the lookup it spells. In TargetMethod
and TargetMethods bodies, Harmony.Patch calls and lookups alike, the
scan reads a method from AccessTools' method, accessor, constructor and indexer lookups and
Harmony 2.4's event and finalizer lookups, from AccessTools.EnumeratorMoveNext and
AsyncMoveNext and Harmony 3.0's StateMachineMoveNext,
Lambdas and LocalFunction, from Type.GetMethod, Type.GetConstructor and
Type.TypeInitializer, from a property's accessor taken from its
PropertyInfo and an event's add or remove method taken from its
EventInfo, from the method a SymbolExtensions.GetMethodInfo lambda calls
or names as a method group, and from a search of one class's methods by an exact name, each also
when the code makes a generic method of it with MakeGenericMethod. It reads a class
from typeof; from text, as in "Namespace.Type:Member",
AccessTools.TypeByName, Type.GetType, Assembly.GetType and
Traverse.CreateWithType, as the class typeof would give where exactly
one assembly ILens loads defines that full name or the text qualifies the name with the assembly
that defines it, and otherwise by the name as written; from
AccessTools.Inner or GetNestedType for a nested class; and from
GetType() on the calling object, read as the class that declares the calling member.
It follows a value back to where the assembly writes it: through locals, through a helper's
parameters to each call of the helper, through the fields of a patch object to each place that
builds it, through the mod's own methods to what they return, at most four members away, and
through a list of names the code writes out and loops over. A Traverse takes the
class the decompiler gives the object it was created from; that is a static type, so it names a
base class when the field is declared in a subclass.
How a target is written. Targets are written as the other tools name them, so
a target passes back to decompile, analyze and compare: a
generic class without type arguments in C#'s unbound form,
System.Collections.Generic.Dictionary<,>.Enumerator.MoveNext, a constructed
class with each class's own type arguments, HarmonyMod.TargetO<int>.Slot<string>.Fill,
and parameter types by keyword or short name, int?, int[,],
List<int>[], Dictionary<int, string>.Enumerator, with a
generic class named without type arguments by its name alone and a class nested in another by
its own name, HarmonyMod.TargetN.Part.Join(Part). A type
the code makes with MakeByRefType is written ref int, since Harmony
binds it to a parameter passed by ref, out or in alike:
Prefix System.Int32.TryParse(string, ref int). A generic method made for type
arguments carries them, Postfix HarmonyMod.TargetL.Parse<int>, with
? for one the scan cannot read, whether MakeGenericMethod,
AccessTools.Method's generics argument or a GetMethodInfo lambda makes
it; the lambda's target keeps the parameter types the method declares,
HarmonyMod.TargetM.Store<int>(T), and a lookup line names the type arguments of
a method the code makes generic right at the lookup.
A target carries its parameter types where the patch pins an overload and Harmony reads them;
without them the line names no overload. Harmony binds a static constructor, a finalizer and an
accessor named by its property or event without reading parameter types, so such a target prints
none even where its attribute names some:
Prefix HarmonyMod.TargetH..cctor <- HarmonyMod.IgnoredTypesStaticConstructorPatch.Prefix
[TypedAttribute]. A getter or setter patch names the accessor, get_X or
set_X; an indexer patch the indexer's accessor, named from the class when the scanned
assembly defines it and otherwise by C#'s default, get_Item or
set_Item, with ? for a setter's value type it cannot read; an event
patch add_X or remove_X; a constructor patch .ctor or
.cctor, written .ctor() without argument types because Harmony then
binds the parameterless one; a finalizer patch Finalize; and an operator patch the
operator's method, such as op_Addition. Code the compiler generates for a method is
named by that method with the kind after it, (Enumerator), (Async),
(StateMachine), (Lambdas) or (LocalFunction Name), as in
Postfix System.IO.File.ReadLines(string) (Enumerator), since compare
judges a method together with its generated code. A constructor lookup names the static
constructor, .cctor, where its searchForStatic is true or its binding
flags hold Static and not Instance; given parameter types as well, it
names .cctor(int), a constructor no class has. One that asks for both kinds, as
AccessTools.all does, names the instance constructors, and a flag the scan cannot
read gives both, which makes a TargetMethod a DynamicTargetMethod.
What the scan cannot read prints ? instead of being left out, so
a missing line is a proof of absence. A target it cannot determine is ?. A field or
member is written ?.name or Class.? when one part is unread, and
? when both are; a field name a helper takes from its callers gets a line of its own,
?.name while the class stays unread. A Harmony.Patch call the IL shows
but the decompiled code does not prints ? ? <- ? [PatchCall in X], and such a
field lookup a Field ? line. Two arguments of one call that each have several values
are not paired, and the call reads as ?. A TargetMethod's targets are
the values it returns or yields: one it cannot read adds a ? line beside the targets
it reads, so an incomplete list says so, while a lookup that only serves a condition counts for
nothing. A lookup given a FieldInfo or MethodInfo prints nothing of its
own, since the lookup that found it is listed on its own.
TypedAttribute and StringTargeted tell a target named through
typeof and nameof from one named by a string, which breaks silently
when the member is renamed. ILens tells them apart by whether the named member is public on the
target class, since nameof cannot reach a non-public member, and it can check that
only when the target class lives in the scanned assembly. A class named by a string, through
HarmonyPatch(string typeName, string methodName, MethodType), is
StringTargeted wherever it lives, and a patch Harmony binds by its kind alone, on a
constructor, finalizer, operator or indexer, is TypedAttribute wherever it lives,
since typeof then names the whole target. Other targets in another assembly, the
usual case for a mod, show as Attribute. Harmony 2.x and 3.0, the
HarmonyLib namespace, are recognized; the attributes and calls of Harmony 1.x, under
the Harmony namespace, are not read.
This assembly is a reference assembly, which holds a library's declarations without their
code, so the patches it applies and the members it looks up cannot be read from it; ask the build
the mod runs as. See Reference assemblies.compare on two builds of the patched assembly, to see
whether each target and lookup still binds and what changed in it.
list_members read-onlyList a type's members, grouped by kind, one declaration per line without bodies.
assembly required stringtype_name required stringSystem.IO.File. A compiler-generated type named
here has its members listed whatever include_compiler_generated says.kinds optional (method | property | field | event | nested_type)[]accessibility optional public | public_protected | allpublic_protected, which also covers protected internal and
private protected; all adds internal and private. See
accessibility.name_contains optional string.ctor or .cctor.include_inherited optional boolSystem.Object, base classes
another assembly defines included. Default false.include_compiler_generated optional boolfalse. See generated
code.limit optional intA header with the total and the type's heading as C# declares it, then a count line per
non-empty kind and its members, sorted by name with constructors first. The groups stand in the
order methods, properties, fields, events, extension blocks, nested types. Asked of
BaseShift.dll, one of the test assemblies in ILens's repository:
5 members of public class BaseShift.Gauge:
1 method:
public Gauge()
3 properties:
public int Level { get; set; }
public int Limit { get; set; }
public int Steady { get; set; }
1 event:
public event EventHandler Changed
Not shown: 3 internal or private
The last line counts the members the accessibility filter left out. An empty
answer counts the compiler-generated members as well and names the settings that list them: an
enum without constants answers
No members of public enum CallShapes.Defaults.Blank followed by
Not shown: 1 compiler-generated; include_compiler_generated lists it, the field every
enum keeps its value in. See lists.
The heading. The header writes the type's accessibility, modifiers, kind and
name, its base types, the in or out of a variant type parameter and the
conditions on the type's own type parameters, as in
1 member of public interface BaseShift.Variance.IMapper<in TIn, TKeep, out TOut> where TKeep : class:
and 4 members of public class InterfaceShapes.Conditions.Holder<T> : Base where T : class, ISlot, new():.
Declarations. Each line reads as C# declares the member. A constructor is
listed under the type's name among the methods, public Vault(int stored), and the
static one as static Vault(); a struct or an enum that declares no constructor has
no constructor line. A generic method's line ends with the conditions on its type parameters,
public void Pair<TKey, TValue>(TKey key, TValue value) where TKey : class, IComparable<TKey>, new() where TValue : struct,
which an override and an explicit implementation do not repeat. An extension method has
this before its first parameter, public static void Drain(this ref Charge charge),
and a parameter that takes any number of arguments has params, on an array or on a
collection, public int Count(params ReadOnlySpan<int> values); the reference
form that find_methods and the other answers print writes neither. A parameter's
default and a constant's value read as C# writes them, an enum's value by its member's name:
public void Go(Pace pace = Pace.Fast),
public void Odd(Pace pace = (Pace)7) where no member has the value or the enum's
assembly was not read, public const decimal Price = 1.25m,
public const int Most = int.MaxValue. A name that C# reads as a keyword carries the
@ C# declares it with, public Lever(bool @switch). An override reads
override, also one that returns a more specific type than the member it overrides,
public override Letter Copy(), and an abstract one abstract override. An
accessor shows its own accessibility where it differs from the property's, and
init where it is one. An enum lists its constants,
public const Pace Slow = 1. A member inherited from a base type is marked
[from Base] after its line, and an obsolete one [Obsolete] before it:
[Obsolete] public int Attempts { get; }.
Every member. Two members of one type are never merged: a method beside a
generic method of its name and parameters, and conversion operators that differ in their return
type alone, each get a line. Lines that would read alike are written in full, every type with
its namespace and enclosing classes. Asked of CallShapes.dll, another test
assembly:
7 members of public class CallShapes.Namesakes.Basket:
7 methods:
public Basket()
public void Hold(System.Collections.Generic.List<CallShapes.Namesakes.Alpha.Badge> badges)
public void Hold(System.Collections.Generic.List<CallShapes.Namesakes.Beta.Badge> badges)
public void Take(CallShapes.Namesakes.Alpha.Badge badge)
public void Take(CallShapes.Namesakes.Bag.Entry entry)
public void Take(CallShapes.Namesakes.Beta.Badge badge)
public void Take(CallShapes.Namesakes.Box.Entry entry)
Interfaces. An interface's method, property or event carries the word that
stands for the body its line does not show: none for a member the implementing type supplies,
virtual for one with a default implementation, sealed for one no
implementing type can replace, and static, static abstract or
static virtual for a static one; every line carries its accessibility. On a class's
member, no modifier means a body that is not virtual. An interface's implementation of a base
interface's member reads as a class's explicit implementation does, and one that makes the
member abstract again has abstract. Asked of InterfaceShapes.dll, a
test assembly:
4 members of public interface InterfaceShapes.IFeed:
2 methods:
public virtual void Close()
public void Open()
1 property:
public int Count { get; }
1 event:
public event EventHandler Moved
Extension blocks. A class that declares C# 14 extension blocks lists the
members of each block under a count line that names the block,
5 members of extension(Gauge gauge):, each member as the decompiled class declares
it. A property is followed by the names of the methods the compiler made of its accessors, in
brackets, which the other tools take: public int Doubled { get; } [get_Doubled].
The methods the compiler made of the blocks' members are not listed among the methods. Two blocks
whose lines read alike, such as two for one receiver that differ only in an attribute on it, share
one group. kinds takes a block's member by its own kind, so property
lists Doubled, and name_contains finds a block's property by the names
of its accessors' methods as well. Asked of InterfaceShapes.dll, where
IsZero stands in a second block for Gauge whose receiver carries an
attribute:
6 members of public static class InterfaceShapes.Language.GaugeExtensions:
5 members of extension(Gauge gauge):
public int Doubled { get; } [get_Doubled]
public static Gauge Empty()
public bool IsZero()
public int Offset { get; set; } [get_Offset, set_Offset]
public void Reset()
1 member of extension<T>(List<T> readings) where T : IComparable<T>:
public T First()
Not shown: 1 internal or private
Fields a compiler adds. The fields a compiler adds beside the member they
belong to are left out unless include_compiler_generated is set: the field that
holds an event's subscribers, Visual Basic's field for an auto-property or a
WithEvents variable, the fields F# adds, the lambdas an older C# compiler kept in
fields, and the field every enum keeps its value in.
Inherited members. With include_inherited, a base class's member
is left out only where a class below declares one that hides it as C# does: of the same kind and
name, and for a method or an indexer with the same parameter types and, for a method, the same
count of type parameters. An override hides what it overrides, while Find(string)
leaves a base class's Find<T>(string) listed. The members of a base class
another assembly defines are read from that assembly where ILens finds it: beside the inspected
one, at the top of an allow-root, or in the installed .NET. An enum or a struct thereby also lists
what System.Enum or System.ValueType declares. A base class ILens cannot
read ends the list with a line that names it and why. Asked of ExternalUser.dll, a
test assembly whose base class Widget comes from ExternalLib.dll, kept in
a folder that is no allow-root:
3 members of public class ExternalUser.DerivedWidget : Widget:
2 methods:
public DerivedWidget()
public override void Draw()
1 property:
public int Size { get; set; }
Not shown: what ExternalLib.Widget and its base types declare; ILens could not load ExternalLib, Version=1.0.0.0
The reason after the semicolon is one of three, which
other assemblies explains with what to do for each. Here,
with that folder as another --allow-root, the answer lists the 10 members, those of
Widget marked [from ExternalLib.Widget].
type_name names an array type, such as byte[]:
byte[] is an array type, which only analyze with kind 'extension_methods' and decompile,
analyze and compare with member_name take, to find the extension methods code can call on
it.decompile when bodies are needed; find_methods with
type_name for a type's methods in the form the other tools take.
decompile read-onlyDecompile a type, or one of its members, to C#. Omit member_name for the whole
type.
assembly required stringtype_name required stringSystem.IO.File; see
names. With member_name, it may also be an array type as
C# writes it, such as byte[] or int[,], to read an extension method
code can call on such an array.member_name optional stringReadAllText. A constructor is .ctor or the type's
own name, the static constructor only .cctor, and an accessor
get_X, set_X, add_X or remove_X. A method name
may end in its type parameters, Get<T>, Get<int> or
Get<>, and Get alone means the method without type parameters
where the name has both; see member lookup for how a name and
the three hints below find a member.parameter_count optional intparameter_types is also given, the two must agree.parameter_types optional string[]["int", "bool"]; see
type patterns.return_type optional stringCallShapes.dll, op_Explicit with
return_type long picks
CallShapes.Namesakes.Amount.op_Explicit(Amount) → long among three.Without member_name, the whole type as C#: the most expensive read in the tool
set. With it, a comment that names what the lookup picked, then the code. Asked of
ICSharpCode.Decompiler.dll for DecompileTypeAsString on
ICSharpCode.Decompiler.CSharp.CSharpDecompiler:
// ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileTypeAsString(FullTypeName) → string
using ICSharpCode.Decompiler.TypeSystem;
public string DecompileTypeAsString(FullTypeName fullTypeName)
{
return SyntaxTreeToString(DecompileType(fullTypeName));
}
The comment names a method in reference form with its return type, so an overload other than
the one meant shows at once, and its parameter list works as parameter_types. A
generic method carries its type parameters, CallShapes.Namesakes.Store.Get<T>(string)
→ T, and a constructor reads ..ctor, as in
ICSharpCode.Decompiler.TypeSystem.FullTypeName..ctor(string) → void. Where another
member of the type would read alike in that short form, the comment writes every type with its
namespace: CallShapes.Namesakes.Basket.Take(CallShapes.Namesakes.Beta.Badge) → void,
beside a Take that takes the Badge of another namespace.
A method gives its body. A property or event name gives the whole declaration
with its accessor bodies, and a field name the field's declaration with the initializer the
compiler moved into the constructors: on BaseShift.dll,
BaseShift.Fields.Gauge.Size gives public int Size = 5;. A member that
implements an interface member explicitly is reached by the interface member's plain name when no
ordinary member has that name, and the comment names it in full:
// ExplicitMembers.Circle.ExplicitMembers.IShape.Area() → double.
An accessor, get_X, set_X, add_X or
remove_X, gives its part of the declaration of the property, indexer or event that
owns it, with the using lines the whole declaration needs: an auto-property's getter reads
get;, an init setter init;, and an accessor of a property written with
C# 14's field keyword writes field for the backing field the compiler
declares. An accessor the declaration does not write, such as an accessor of a field-like event or
the getter of a property written with =>, comes on its own, with its body. Asked
of InterfaceShapes.dll for get_Level on
InterfaceShapes.Language.Gauge, whose setter calls Math.Max:
// InterfaceShapes.Language.Gauge.get_Level() → int
using System;
get
{
return field;
}
Where a member was found. When the type asked about does not declare the
member, the comment ends with a mark. [inherited from X] names the base class that
declares it. [extension method on X] marks an extension method found from the type
it extends: the comment names the method under the class that declares it, and X is the type its
first parameter extends. Asked of CallShapes.dll for Dim on
CallShapes.Receivers.DeskLamp, which derives from Lamp:
// CallShapes.Receivers.LampExtensions.Dim(Lamp) → void [extension method on CallShapes.Receivers.Lamp]
public static void Dim(this Lamp lamp)
{
}
Any static method marked as an extension method counts, whether a C# static class, a Visual
Basic module or an F# class declares it, also with its first parameter passed by
ref, in or ref readonly:
CallShapes.Receivers.LampExtensions.Drain(ref Charge) → void [extension method on
CallShapes.Receivers.Charge]. X can be a base class or an interface of the type asked
about, a type of another assembly that the inspected assembly declares the method for, or an
array type. For a method whose first parameter is a type parameter, X is that type parameter with
its constraints, as C# writes them: [extension method on T where T :
CallShapes.Constraints.IShape], or [extension method on T[] where T : struct,
System.IComparable<T>] for an array of one. Which member a name finds first, through
a type of this assembly, of another assembly or an array type, is under
member lookup.
What the lookup could not read. Where the lookup that found an extension
method could not read a class it needed, such as one that might declare a member of the name,
which C# would bind first, a second comment line starting Not checked: says so.
Asked of ExternalUser.dll through a class of a library that lies in no
allow-root:
// ExternalUser.Extended.KettleExtensions.Boil(Kettle) → void [extension method on ExternalLib.Extended.Kettle]
// Not checked: whether ExternalLib.Extended.Kettle or its base types have a member Boil, which C# would take first; ILens could not load ExternalLib, Version=1.0.0.0
using ExternalLib.Extended;
public static void Boil(this Kettle kettle)
{
((Appliance)kettle).Plug();
}
Give the folder that holds the library as an allow-root, and the line goes; the line's other reasons are under types from other assemblies. A member that a base class from another assembly declares is not decompiled here: the error names the file that holds it.
A whole type. A top-level type comes with its using lines, its namespace and
its nested types. A nested type comes alone, under a comment that names it,
// SearchText.Outer.Inner, with the using lines it needs and without the types around
it. A type the compiler generates for a lambda, an iterator or an async method, which the
decompiler writes into a method of the class around it, comes alone in the same way, as the
compiler generated it: with its fields and the methods that hold the code, such as
<InCapturingLambda>b__0() or the iterator's MoveNext(). Asked of
SearchText.dll, a test assembly, for the class of a lambda that captures a
variable:
// SearchText.Shapes.<>c__DisplayClass1_0
using System.Runtime.CompilerServices;
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public string where;
internal void <InCapturingLambda>b__0()
{
Marker.Hit(where + " captured");
}
}
A top-level type the decompiler leaves out of the code it writes comes the same way, as the
compiler generated it, under a comment that names it: the class that holds the compiler's
helpers, <PrivateImplementationDetails>, with the data of constant arrays and
the structs nested in it; an anonymous type such as <>f__AnonymousType0<,>,
with its properties and its Equals; and a <Module> without
members, as internal class <Module> with empty braces.
The messages shared with analyze and compare are explained under
Troubleshooting.
parameter_count, parameter_types or return_type
without member_name: parameter_count, parameter_types and return_type select a
method overload, so they need member_name.parameter_count (N) contradicts parameter_types.Length (M).Type not found: ..., which
names the types the name may have meant; ... is not defined in this assembly; it comes from
... or ..., which forwards it to ... for a type of another assembly, unless
member_name finds an extension method this assembly declares for it;
Type name '...' matches N types: ... Name one as listed.member_name: byte[] is an array type, which only
analyze with kind 'extension_methods' and decompile, analyze and compare with member_name take,
to find the extension methods code can call on it.Member '...' not found on ..., its base types, or as an extension method
(Method '...' with a hint). The message ends with what applies:
; a struct that declares no constructor has none in the assembly, and instantiated_by lists
where it is created, ; an enum has no constructor, ; its static
constructor is named '.cctor', ; its indexer is named Entry: ...get_Entry(int),
...set_Entry(int, int) for Item, get_Item or
set_Item, ; the methods named Run are ... for a count of type
parameters no method has, or that a base class ILens could not load was not searched. For the
name of a property of a C# 14 extension block, which the class holds only as the methods the
compiler made of its accessors, the message ends with those methods, the ones to ask instead:
; Doubled is a property of an extension block: ...get_Doubled(Gauge). A method of an
extension block is found by its own name.Member 'Nope' not found on
System.Int32[]: no public instance member of System.Array or System.Object and no extension
method of this assembly that applies to it has that name. Where the element type's
library was not loaded, the message also names what was not read. Where a member of
System.Array or System.Object comes first and the assembly does not
name that class: System.Array is not defined in this assembly; it comes from
...'Take' on
CallShapes.Namesakes.Store has 2 matching overloads; write the type parameters of the one meant
after its name, as listed: ...; the other advice texts point to
parameter_count or parameter_types, to return_type, to
types written as listed, or say that the candidates cannot be narrowed further.No overload of '...' on ... matches ... Available:
..., listing every overload of the name across the base classes.Member '...' on ... is ambiguous — it exists as more than one member kind ...),
several indexers (Property 'Entry' on ... has 2 overloads — ...; name an accessor
with parameter_types), several explicit interface implementations ('...' on
... names 2 explicit interface implementations: ... Pass the full name of one instead.),
or several events in non-C# metadata (Event '...' on ... has 2 entries with the same name —
non-C# metadata. Candidates: ...).Property 'Text' on
BaseShift.Sides.Banner has no setter., or Event '...' on ... has no add
accessor.... inherits ... from
a base class this assembly does not define; ... comes from <file>.This assembly is a reference assembly,
which holds a library's declarations without their code, so there is no code to decompile; ask the
build the library runs as. find_methods and list_members read declarations alone and answer
here. See reference assemblies.list_members first, to see what a type has without paying for bodies;
search_code to find where the code does something; analyze for who
uses what you are reading; compare for what changed between two builds.
search_code read-onlySearch the decompiled C# of one type, one namespace or the whole assembly for lines that match a regular expression, each listed under the member it belongs to.
assembly required stringregex required string(?i) to ignore case. E.g. File\.Delete\(.type_name optional stringnamespace optional stringtype_name and namespace, the whole assembly is
searched.include_compiler_generated optional boolfalse. A generated type that
type_name names is searched either way where the decompiler writes it as a type of
its own, such as <>z__ReadOnlyArray<>.limit optional intA header with the number of matching lines, the pattern and the scope, then each member that
holds a match, sorted by name, with its matching lines beneath it, indented by two spaces, in the
order they stand in the code. Asked of the test assembly SearchText.dll with
catch \( in SearchText.Reader:
3 lines matching catch \( in SearchText.Reader:
SearchText.Reader.Load(string)
catch (IOException)
catch (Exception ex2)
SearchText.Reader.TryParse(string, out int)
catch (FormatException)
A line reads as decompile prints the top-level type that holds it, without its
indentation: the source names the
second variable ex, the decompiled code ex2. A member is named in the
reference form the other tools take, see names. The header ends
in namespace <name> for a namespace and names no scope for the whole assembly.
limit counts matching lines, not members, so it can cut inside a member's lines; see
lists. No match: No lines matching catch \( in
SearchText.Reader.
What is searched. The lines of the decompiled C# that belong to a type or a
member: bodies, signatures, attributes, fields with their initial values, and a type's own
header, its attributes, its declaration line and its opening brace, which stand under the type's
name. The lines of a lambda, a local function, an iterator or an async method stand under the
member they are written in, and an accessor's lines under its property or event. The
using and namespace lines are not searched, since no member holds them. Asked of
SearchText.dll with Serializable|Retries|IDisposable in
SearchText.Settings:
3 lines matching Serializable|Retries|IDisposable in SearchText.Settings:
SearchText.Settings
[Serializable]
public class Settings : IDisposable
SearchText.Settings.Retries
public int Retries = 3;
The comments the decompiler writes above a declaration stand under that declaration: its
documentation, where an XML documentation file lies beside the assembly, a member's under the
member and a type's under the type, and notes of its own, such as // C# has no syntax for
parameterized property 'Cell'. above the method it writes for the getter of a Visual Basic
property that takes a parameter and is not its class's default, since C# cannot declare one. A
C# 14 extension block reads as decompile writes its class, and a line of a block's
member stands under the method the compiler made of it, whose name the other tools take: an
accessor's lines under its accessor's method, and a property's other lines under its getter's, or
its setter's where it has none. The block's own lines, extension(Gauge gauge) and its
braces, stand under the class. On the test assembly InterfaceShapes.dll, the line
public int Doubled => gauge.Level * 2; stands under
InterfaceShapes.Language.GaugeExtensions.get_Doubled(Gauge).
One line at a time. The pattern sees each line on its own, without its
indentation, so ^ stands before the first character of the code and a match never
spans two lines. A statement the decompiler prints over several lines, such as an object creation
with its initializer, is found only through a part that stands on one line: on
SearchText.dll, Name = "plain", Size finds nothing, while
Name = "plain" finds the line Name = "plain",. A blank line never
matches. A pattern runs on .NET's non-backtracking engine, which cannot run away; one with a
lookaround or a backreference, which that engine does not take, runs on the backtracking engine
with one second for each line.
Generated code. Without include_compiler_generated, the types and
members the compiler generated are neither searched nor decompiled. The code the compiler moves
out of a method, such as a lambda or an iterator, is searched either way, under the method it is
written in, since the decompiled C# shows it there. The class that holds the compiler's helpers,
<PrivateImplementationDetails>, a struct nested in it, an anonymous type and
<Module> are searched as the compiler generated them where
type_name names them, and in a search of the whole assembly only with
include_compiler_generated, as any generated type is. A nested type the decompiler
writes inside the type around it, such as a lambda's class, the class that caches the lambdas that
capture nothing, an iterator's or async method's state machine, or a C# 14 extension block's
grouping class, has no lines of its own in the decompiled code, so naming it in
type_name is an error that names the nearest type around it with lines of its own,
whose search finds its contents.
The first search. A search decompiles the types of its scope that no earlier
search decompiled, and keeps their text in the server's memory, so later searches of the assembly,
in any scope, answer from that text. On a large assembly, the first search over everything takes
about a minute, later ones a fraction of a second. A call decompiles for at most 45 seconds, a
limit the startup option --max-search-seconds sets, see
Integrating with Claude Code. An unfinished call answers with how far it
came and with no match, and repeating the call goes on from there. On
ICSharpCode.Decompiler.dll, with the server started with
--max-search-seconds 5, the first four calls of one search over the whole assembly
answered:
Not finished: 296 of 888 types decompiled. Repeat the call to continue; what is done is kept.
Not finished: 527 of 888 types decompiled. Repeat the call to continue; what is done is kept.
Not finished: 664 of 888 types decompiled. Repeat the call to continue; what is done is kept.
Not finished: 871 of 888 types decompiled. Repeat the call to continue; what is done is kept.
The fifth call answered with the matches after 1 second, and the same search then took about a tenth of a second. The numbers count the top-level types of the scope. Every call finishes at least one type, so each repeat gets further. A second search of the same assembly while one decompiles waits for it within its own time limit.
The text is kept for as long as the server runs and the file stays as it was; a file that
changed is decompiled anew. The kept text has a quarter of the memory budget that
--max-total-size sets. When that share is full, the text of the assembly searched longest
ago gives way, and a later search of that assembly decompiles it again. A scope whose text alone
does not fit ends in an error, see Errors.
A type that failed to decompile is named in a last line, Not searched: N types that
failed to decompile: ..., also under an empty answer, so an answer without that line covers
the whole scope.
type_name and namespace are two scopes; give one of them.regex is empty.Error: RegexParseException: Invalid pattern 'catch (' at offset 7. Not enough )'s.The pattern took longer than 1 s on one line of <member>. A pattern without a lookaround
or a backreference cannot run away.ICSharpCode.Decompiler.dll on a server started with --max-total-size 4,
whose share is 1 MB: The decompiled text of ICSharpCode.Decompiler.dll needs more than the
1 MB kept for searches. Narrow the search with type_name or namespace, or raise
--max-total-size. A search of one of its namespaces then answers.type_name or namespace names nothing the assembly defines:
Type not found: ..., ... is not defined in this assembly; it comes from
..., Type name '...' matches N types: ..., Namespace not found:
..., or for an array type byte[] is an array type, which only analyze with kind
'extension_methods' and decompile, analyze and compare with member_name take, ...; see
Troubleshooting.type_name names a nested type the decompiler writes inside the type around it,
see Generated code, here a lambda's class of SearchText.Shapes:
SearchText.Shapes.<>c__DisplayClass1_0 has no declaration of its own in the decompiled
code of SearchText.Shapes, where a search finds its contents.This assembly is a reference
assembly, which holds a library's declarations without their code, so there is no code to search;
ask the build the library runs as. find_methods and list_members read declarations alone and
answer here. See reference assemblies.decompile to read a member a match stands in; analyze with
call_sites for what every call to one method passes, which a pattern finds only
where a call stands on one line; find_methods for methods by name or signature.
compare read-onlyCompare two builds of an assembly, from the types that differ down to one member: without
type_name the changed types, with type_name what changed in that type,
and with member_name a diff of a method's or accessor's two bodies, a verdict when
they print alike, or a constant's two values.
assembly_a required stringassembly_b required stringtype_name optional stringmember_name, it may also be an array type
such as byte[], to compare an extension method code can call on such an array.member_name optional stringtype_name.
Each build looks the name up as decompile does: .ctor or the type's own
name for a constructor, get_X, set_X, add_X or
remove_X for an accessor, and a method's type parameters after its name,
Get<T>; see member lookup. Where A has the
type and no member of the name, B is asked, so a member only B has is found.parameter_count optional intmember_name; if
parameter_types is also given, the two must agree.parameter_types optional string[]member_name.return_type optional stringmember_name.namespace optional stringtype_name.include_compiler_generated optional boolmember_name. Default false. Either way, a method's
generated code is compared as part of the method; see generated
code.format optional csharp | ilcsharp (the default) decompiles them,
il disassembles them as the comparison reads them. Applies only with
member_name.context optional intmember_name.limit optional intmember_name.A line of a list starts with + for what only B has, - for what only
A has, and ~ for what both have but changed. The examples compare the test
assemblies BaseShift.dll as A and BaseShift2.dll as B, which differ on
purpose.
Changed types. Without type_name, here with
namespace BaseShift.Declarations:
11 changed types:
~ BaseShift.Declarations.Accessors [declarations,bodies]
~ BaseShift.Declarations.Cabinet [declarations]
~ BaseShift.Declarations.Conditions [declarations,bodies]
~ BaseShift.Declarations.Fields [declarations,values]
~ BaseShift.Declarations.Holder<> [metadata]
~ BaseShift.Declarations.Kinds [members,declarations]
~ BaseShift.Declarations.Methods [declarations]
~ BaseShift.Declarations.Motor [declarations]
~ BaseShift.Declarations.Receivers [declarations]
~ BaseShift.Declarations.Reworked [declarations,bodies]
~ BaseShift.Declarations.Shelf [declarations,bodies]
A changed type names what changed, in this order:
metadata: what the type's heading shows, as list_members writes it:
its kind and accessibility, the words before its name (static, abstract, sealed, readonly, ref,
record), its base class or an enum's underlying type, its interfaces, and its own type
parameters, with their number, their in or out and their conditions. A
base class or interface that gives way to another one is marked, whichever assembly defines it. A
renamed type parameter or a reordered where clause alone is no change, and neither is a base class
or interface that keeps its name and only moves to another assembly.members: a member that only one build has.declarations: a member that keeps its name, parameter types and return type and
whose declaration line reads otherwise.values: a constant or an enum member whose value changed.bodies: the body of a method or an accessor changed.A type only one build defines is a + or - line. Where the other
build's metadata says where it takes the type from, as after the type moved to a library, the
line names that assembly: - <type>, which in B comes from <assembly> for a
type B references, or - <type>, which B forwards to <assembly>. A nested
type that build does not name takes the clause of the type enclosing it. No change:
No changed types.
One type. With type_name, one line per changed member, written as
its declaration. A member whose body alone changed is its declaration; a property or event names
the accessors whose bodies changed, which are what member_name takes next:
3 member changes in BaseShift.Gauge:
~ public event EventHandler Changed [add_Changed]
~ public int Level { get; set; } [get_Level]
~ public int Limit { get; set; } [set_Limit]
A member whose declaration changed carries both declarations, A's and B's. It ends with
[body] where its body changed as well, as in ~ public int Total(int a) →
internal int Total(int a) [body], and a property or event with its changed accessors in
brackets, as in ~ public int Depth { get; } → public int Depth { get; set; }
[set_Depth]:
9 member changes in BaseShift.Declarations.Methods:
~ public Methods() → protected Methods()
~ public void Close() → public virtual void Close()
~ public int Count() → public static int Count()
~ public void Fill(int count) → public void Fill(int amount)
~ public int Gather(IEnumerable<int> values) → public int Gather(params IEnumerable<int> values)
~ public void Open() → private void Open()
~ public T Pick<T>(T item) → public TItem Pick<TItem>(TItem item)
~ public int Spread(params IEnumerable<int> values) → public int Spread(IEnumerable<int> values)
~ public int Sum(int[] values) → public int Sum(params int[] values)
The declaration line is the one list_members
prints, so everything it shows counts: accessibility, static, virtual, abstract, sealed, override,
readonly, volatile, const, params, this before an extension method's first
parameter, the names of parameters and type parameters, the conditions on a method's type
parameters, a parameter's default value, as in ~ public void Open(int width = 5) → public
void Open(int width = 6), an accessor's own accessibility, set or
init, and an accessor gained or lost. A renamed parameter matters to a Harmony
patch, whose parameters bind to the patched method's by name. An attribute the line does not show
is not compared.
A constant whose value changed shows both values, and one whose type changed is a removal and an addition:
7 member changes in BaseShift.Values.Limits:
+ public const int Added = 7
~ public const int Changed = 5 → 6
~ public const string Empty = null → "set"
~ public const bool Open = false → true
~ public const string Text = "before" → "after"
- public const int Wide = 5
+ public const long Wide = 5L
The compiler copies a constant's value into every place that uses it, so code built against A
keeps A's value until it is built again, and the value line is the only place the change shows.
An enum member is such a constant: a member inserted before Second in the enum
BaseShift.Values.Stage gives ~ public const Stage Second = 1 → 2. Two
integral values are the same when they are the same number, so an enum whose underlying type
changed with its values kept shows under metadata alone. Two constants or defaults
that name the same member of an enum whose members were numbered anew are the same as well.
A type whose own declaration changed, which the list marks metadata, ends its view
with both declarations as list_members writes the heading:
No member changes in BaseShift.InterfaceArgumentMover
Declaration: public class BaseShift.InterfaceArgumentMover : IOwner<int> → public class BaseShift.InterfaceArgumentMover : IOwner<string>
A type only one build has lists all its members, and the header says so: 2 member
changes in BaseShift.Arity.Command, which only A has: for a class that B replaced by generic
classes of its name. Where the other build's metadata says where it takes the type from, the
header goes on as the list of changed types does, ..., which only A has; in B it comes from
<assembly>: or ; B forwards it to <assembly>:. A member of
an interface is written as list_members writes it, with public and
without a modifier where the implementing type supplies the member, so + public void
Added() in BaseShift.Members.IGrowing is a method the interface gained,
although the header does not say that the type is an interface. The fields a compiler adds beside
a member, such as an event's subscriber field or an auto-property's backing field, and the field
an enum keeps its value in, are listed only with include_compiler_generated. No
change: No member changes in ....
How members are matched. A member of A and a member of B are one member when they have the same name, the same count of type parameters, the same parameters with their passing keywords, and the same return type. Each type in that comparison is the type itself, with its namespace, its enclosing classes and an array's number of dimensions, and a type parameter counts by its position. So a changed signature is a removal and an addition, also where a parameter's class gave way to one of the same name in another namespace, while a renamed type parameter is a changed declaration. Lines that would read alike in the short form, or in which one short name would stand for two different classes, are written with every type's namespace:
3 member changes in BaseShift.Overloads.Replaced:
- public int Hold(BaseShift.Overloads.Left.Token token)
+ public int Hold(BaseShift.Overloads.Right.Token token)
~ public T Pass<T>(T item) → public TItem Pass<TItem>(TItem item)
Bodies are compared as IL with tokens resolved to names and file positions left out, so a method a rebuild leaves unchanged does not show as changed. The code the compiler generates for a method, such as its lambdas, local functions, iterator and async state machines, and the copy of its code Burst compiles, is compared as part of that method, with the numbers the compiler gives generated names by position set aside: a method added before others does not make them show as changed, and a change inside a lambda shows on the method that contains it.
One member. With member_name, two bodies that differ come as a
unified diff: the two paths, then a hunk for each run of changes under
@@ -start,count +start,count @@, with context unchanged lines around it,
each line marked with a space, with - for a line only A's body has, or with
+ for one only B's has. For
Settle on BaseShift.Diffs.Ledger, paths shortened:
--- A: ...\BaseShift.dll
+++ B: ...\BaseShift2.dll
@@ -24,6 +24,10 @@
_count += amounts.Length;
int num6 = num2 - num3;
int num7 = ((amounts.Length != 0) ? (num / amounts.Length) : 0);
+ if (strict && num4 > 0)
+ {
+ return -num4;
+ }
int num8 = num6 * 2 + num7;
if (num8 > 1000)
{
@@ -44,5 +48,5 @@
num11 += 10;
}
_total -= num12;
- return num11 + _count * 2;
+ return num11 + _count * 3;
}
C# is compared line by line as printed. In IL, a line is compared without the offsets it
carries or names, since an inserted instruction moves every later offset: a removed line prints
with A's offsets and every other line with B's, and a jump whose target moved to another
instruction shows as a change, as -IL_001f: br.s IL_0026 and +IL_001f: br.s
IL_002c do for BaseShift.Diffs.Ledger.Route. The code the compiler generated
for the method follows its own body, each part under a line // Generated: <name>,
and a hunk that starts inside such a part names it after its @@. There, the numbers
the compiler gives such names by position read #1, #2 in order of first
appearance. A change inside a lambda of InLambda on
BaseShift.Generated.ChangedInside, with format il:
--- A: ...\BaseShift.dll
+++ B: ...\BaseShift2.dll
@@ -41,6 +41,6 @@ // Generated: BaseShift.Generated.ChangedInside.<>c__DisplayClass#1_0.<InLambda>b__0()
IL_0000: ldarg.0
IL_0001: ldfld int32 BaseShift.Generated.ChangedInside/'<>c__DisplayClass#1_0'::seed
-IL_0006: ldc.i4.2
+IL_0006: ldc.i4.3
IL_0007: mul
IL_0008: ret
A whole body is one call away: decompile on either build for its C#, or a
context larger than the method for both bodies in one diff, each change marked.
Two bodies the comparison finds equal get a verdict line alone, in either format, since a copy would show nothing that changed:
// BaseShift.Generated.Renumbered.LocalFunctionCapturing(int) → int — bodies are identical in both assemblies.
When the C# reads alike while the IL differs, the line ends — bodies decompile alike in
both assemblies, but their IL differs; format il shows where. A method that has a body in
neither build is no pair of equal bodies: the line ends — neither assembly has a body for
it: abstract in A, extern in B., with each build's reason, abstract, extern or provided by
the runtime, where its metadata gives one. A method only one build has is the whole difference, so its
code follows a line that says which build has it:
// BaseShift.Generated.Renumbered.Inserted(int) → long — only B has it.
Where the member's declaration changed, its two declarations come first, in a line of the
member list's form, before the verdict, the diff or a constant's values, since none of them shows
the declaration. For an accessor, the line is its property's or event's, which alone can show
init: // public string Name { get; set; } → public string Name { get; init;
} stands above the verdict on get_Name. A line before the diff names the
method only where it says more than the call did:
// public int Total(int a) → internal int Total(int a)
--- A: ...\BaseShift.dll
+++ B: ...\BaseShift2.dll
@@ -1,4 +1,4 @@
-public int Total(int a)
+internal int Total(int a)
{
- return a + 1;
+ return a + 2;
}
Each build looks the name up on its own, so the two can find what the member list counts as two
members, as after a changed return type, a move to a base class, or a method that gave way to an
extension method or to an explicit implementation. A line then names both, each as
decompile's comment does, with [inherited from X] or
[extension method on X] where the type asked about does not declare it, and the
verdict or the diff under it shows whether more than the signature changed:
// BaseShift.Signatures.Returns.Open() → object in A and BaseShift.Signatures.Returns.Open() → string in B are different methods — bodies are identical in both assemblies.
Constants. A constant, an enum member included, is answered with its values in
one line: // BaseShift.Values.Limits.Changed — 5 in A, 6 in B.,
// BaseShift.Values.Limits.Kept — 1 in both assemblies., //
BaseShift.Values.Limits.Added = 7 — only B has it., and for two constants the member list
counts as two members, with their types: // BaseShift.Values.Limits.Wide → int in A and
BaseShift.Values.Limits.Wide → long in B are different constants — 5 in A, 5L in B. An
inherited constant carries its mark, // BaseShift.Values.Derived.Limit [inherited from
BaseShift.Values.Base] — 5 in A, 6 in B. The name means the constant where the nearest
class that has the name declares one, as in C#; a hint asks for a method.
Fields, properties and events. A property or an event has no body of its own.
One that only one build has comes with its code, as decompile shows it, or with
format il with the IL of each accessor under a line that names it, such
as // BaseShift.Sides.Panel.get_Title() → string:
// BaseShift.Sides.Panel.Title — only B has it.
public string Title { get; set; }
A property or event both builds have is answered with an error that names its accessors, which
member_name takes instead, each that only one build has marked. A field is answered
with an error that names the constructors of its class that set it in each build, read from their
code as analyze with assigned_by reads a field's writers, since the
compiler moves a field's initializer into them. Where none does, the error points to
assigned_by; may set it names a constructor that passes the field's
address to code ILens cannot follow, such as a method of another assembly. Both errors name the
two declarations where they differ. Where the member list counts the two as two members, as after
a changed type or a move to a base class, they say A and B have different fields of that
name, or properties or events, and a declaration found off the type asked about is
followed by [inherited from X]:
Error: ArgumentException: BaseShift.Gauge.Level is a property; compare its accessors, get_Level, set_Level.
Error: ArgumentException: BaseShift.Declarations.Accessors.Depth is a property, and its declaration changed: public int Depth { get; } → public int Depth { get; set; }; compare its accessors, get_Depth, and set_Depth, which only B has.
Error: ArgumentException: BaseShift.Fields.Gauge.Size is a field, which has no body; BaseShift.Fields.Gauge..ctor() and BaseShift.Fields.Gauge..ctor(string) set it.
Error: ArgumentException: BaseShift.Fields.Late.Start is a field, which has no body; in A no constructor of BaseShift.Fields.Late sets it, and in B BaseShift.Fields.Late..cctor() sets it.
Error: ArgumentException: BaseShift.Fields.Store.Stock is a field, which has no body; no constructor of BaseShift.Fields.Store sets it, and assigned_by lists what does.
Error: ArgumentException: BaseShift.Fields.Tally.Created is a field, which has no body; BaseShift.Fields.Tally..ctor() may set it.
Error: ArgumentException: BaseShift.Fields.Growing.Fresh is a field, which has no body, and only B has it; BaseShift.Fields.Growing..ctor() sets it.
Error: ArgumentException: BaseShift.Retyped.Till.Delay is a field, which has no body, and A and B have different fields of that name: public int Delay → public int Delay [inherited from BaseShift.Retyped.Store]; in A BaseShift.Retyped.Till..ctor() sets it, and in B BaseShift.Retyped.Store..ctor() sets it.
Error: ArgumentException: BaseShift.Declarations.Kinds.Stride is a field, which has no body, and its declaration changed: public static readonly int Stride → public const int Stride = 4; in A BaseShift.Declarations.Kinds..cctor() sets it, and in B it is a constant.
A name the two builds use for members of different kinds is answered with one line that names
both kinds and both declarations, then the code of each member that has code, in C# or with
format il in IL, under a line that names its build:
// BaseShift.Kinds.Till.Open is a method in A and a property in B: public void Open() → public bool Open { get; }
// A: BaseShift.Kinds.Till.Open() → void
public void Open()
{
}
// B: BaseShift.Kinds.Till.Open
public bool Open => true;
The kinds are field, constant, property, event, method and extension method. A field or a
constant has no code, so its line holds all there is of it: // BaseShift.Kinds.Till.Limit
is a field in A and a property in B: public int Limit → public int Limit { get; set; } is
followed by B's property alone. With format il, the block of a property or
an event holds the IL of each accessor under a line that names it, as
// BaseShift.Kinds.Till.get_Open() → bool under
// B: BaseShift.Kinds.Till.Open. A hint asks for a method, so with one the answer is about
methods.
Extension methods and moved types. Each build finds an extension method
through the type asked about as decompile does. Where one build finds it and the
other declares it as well but its lookup does not take it, the line says why in place of
only A has it: — only A finds it through <type>; in B the type has a
member of that name, which C# takes first., or for a method whose first parameter is a type
parameter:
// BaseShift.Constraints.ShapeExtensions.Corners<T>(T) → int [extension method on T where T : BaseShift.Constraints.IShape] — only A finds it through BaseShift.Constraints.Box; in B BaseShift.Constraints.Box does not meet the method's constraints.
A build that does not define the type asked about but references it, as after the type moved
to a library, is asked through the type it references, so a method one build declares on the type
and an extension method the other declares for the library's type are compared as two different
methods. A line that says only one build has a member then ends with where the other build takes
the type from: — only A has it; in B <type> comes from <assembly>. Where a
build's lookup could not read a class that might declare a member of the name, the answer starts
with the Not checked: line that decompile
writes under its comment.
Reference assemblies. A build marked as a reference assembly holds a library's
declarations without their code (see reference
assemblies), so no body of it is compared or shown. The list of changed types and a type's
list of changes mark no body as changed and end with Not compared: bodies; A is a reference
assembly, which holds no code. With member_name, the line says — A is a
reference assembly, which holds no code to compare with B's. and B's code follows, or
— A and B are reference assemblies, which hold no code to compare.; a member only such
a build has is shown by its declaration and the line // B is a reference assembly, which
holds no code.
For a method Burst compiles, the C# shown is Burst's copy of its code, which the line that
names the method marks with (via X$BurstManaged).
member_name needs type_name.,
parameter_count, parameter_types and return_type select a method overload, so they need
member_name., namespace applies only without type_name.,
include_compiler_generated applies only without member_name., format applies
only with member_name., context applies only with member_name.,
limit applies only without member_name.namespace names a namespace neither assembly has: Namespace not found in
either assembly: ...Type 'Ledger' not found in either assembly. Types named
Ledger: BaseShift.Diffs.Ledger., which names the types the name may have meant, once where
both builds have the same and otherwise for each build, In B, types named .... Where a
build references or forwards the type: Type '...' is not defined in either assembly; it comes
from ..., with each build that forwards it named, A forwards it to ... or
A and B forward it to ..., and beside a forward the build that only references it,
in B it comes from ....Type name 'BaseShift.Arity.Crate' matches BaseShift.Arity.Crate<> in A and
BaseShift.Arity.Crate<,> in B. Name one as listed.member_name: byte[] is an array type, which only
analyze with kind 'extension_methods' and decompile, analyze and compare with member_name take,
...member_name names a field, or a property or event both builds have:
... is a field, which has no body; ... or ... is a property; compare its
accessors, ..., described above under fields, properties and events.decompile, from A unless only B has the type or a member
of the name; with a hint, from A whenever A has the type.find_harmony_dependencies for the members a mod depends on, to check against a new
build; decompile to read a changed type or member in full; list_members
for a type's declarations in one build.
Every tool error reaches the caller as
Error: <ExceptionType>: <message>. The messages below are shared by
several tools; the ones particular to a tool are listed under that tool.
No allowed roots configured. The server cannot load any assemblies. Launch with one or more --allow-root <directory> flags.The server was launched without --allow-root. Add one or more
--allow-root <directory> arguments to the args array in
.mcp.json and start a new session. list_allowed_roots reports the same
condition without failing.
Path is not under any allowed root: ... Allowed roots: ...The requested assembly lies outside every configured root, as the path is written. The
message lists the roots that are configured; a root given through a symbolic link or junction is
listed with (resolved: <path>), the folder the link pointed to when the server
started, where that is another path. Either point the call at a file inside a root, through the
root's link or by its real path, or add the containing directory as another
--allow-root. A path written through a link that lies outside every root is refused
here even where the link leads into a root.
Path resolves (via a symbolic link or junction) to a location outside any allowed root: ...The path passed the check as written but resolves to a real target outside the roots. This is
the link check doing its job: a link planted inside a root cannot be used to reach outside it.
The message lists the roots as the one above does.
The same message answers a request through a root's link that was moved to another folder while
the server runs, since a root stays the folder its link pointed to at startup; restart the server
to follow the link, or ask by the old folder's real path meanwhile. A chain of links that does not
end, or cannot be followed, is rejected with Path has too many levels of symbolic links or
junctions (possible cycle): ... or Path could not be resolved through its symbolic
links or junctions: ....
Path contains '..' which is not allowed: ...Parent-directory segments are rejected before normalization. Pass an absolute path with no
.. components.
Assembly file does not exist: ... or Assembly path is empty.The path is inside a root but names no file, or no path was given. Check the spelling and that the build actually produced the assembly.
MetadataFileNotSupportedException: PE file does not contain any managed metadata., BadImageFormatException: Unknown file format. or InvalidOperationException: Failed to load type system from ...The file is not a .NET assembly: the first is a native DLL or executable, the second a file
that is no Windows executable at all, and the third a file that holds .NET metadata but no
assembly, such as a program database (.pdb). ILens reads compiled .NET assemblies
only.
Assembly is N MB, which exceeds the M MB total memory budget on its own: ... Raise the budget with --max-total-size <MB>.ILens holds an assembly's whole image in memory while it is open, so a file larger than the
whole memory budget is refused before it is loaded. Raise the budget with
--max-total-size <MB>.
Unknown argument '...' for tool '...'. Valid arguments: ...An argument name the tool does not declare, often a parameter name from an older ILens release. Most filters are optional, so a silently dropped filter would return an unfiltered result that looks like an answer; the call is rejected instead. The message lists the valid names.
Invalid value ... for argument '...'. ... or Missing required argument '...'.A value the parameter does not take, such as a kind that analyze
does not have, a number or several comma-separated names for a parameter with listed values, or
text that is no whole number for limit; or a required parameter left out or given as
null. The message names each such parameter of the call and what it takes: Valid values:
... with the listed names, or Expected an integer., Expected an integer
of at most 2147483647. or Expected an integer of at least -2147483648. for a
whole number beyond that range, Expected a string., Expected true or
false., Expected a list of strings. or Expected a list of: .... A
value with exactly one reading is taken without an error, see
Tool reference.
This assembly is a reference assembly, which holds a library's declarations without their code, so ...A question that reads code was asked of a reference assembly, whose method bodies are
placeholders. Ask the build the library runs as, or for find_harmony_dependencies the
build the mod runs as; the messages of analyze, decompile and
search_code also name the questions that read declarations alone and answer on this
assembly. See
Reference assemblies.
Type not found: ...No type the assembly defines, references or forwards has that name. The message ends with the
types the name may have meant: Types of that name: ... where types of that full name
exist with another count of type parameters, and otherwise the types of the name's last segment
in any namespace or enclosing class, those that differ from the name only in the namespace where
any does. Asked of the test assembly CallShapes.dll:
Error: ArgumentException: Type not found: Shop.Vault. Types named Vault: CallShapes.BaseCalls.Vault, CallShapes.Vault.
Types the assembly references or forwards are named in groups with their library,
Types named Marker: this assembly defines ...; it references ExternalLib.Namesakes.Marker
from ExternalLib, Version=1.0.0.0., and at most 40 types are named, the rest counted.
analyze gives such a name an empty answer instead, whose last line says that the
assembly neither defines nor references a type of that name and names the same types.
find_types with name_contains finds the exact name.
... is not defined in this assembly; it comes from ... or ..., which forwards it to ...The assembly uses the type, or forwards it, but another assembly defines it. Point the call at
that assembly to read the type itself. analyze can still answer who in this assembly
uses it, and decompile, analyze and compare still find an
extension method this assembly declares for the type, see
Types from other assemblies. A forwarded type is named with
the assembly it is forwarded to, also where the assembly references it too; a reference that
names no assembly reads ... is not defined in this assembly, which only
references it. Where the assembly gives the name only to generic types of its own, the
message names them, since you may have meant one, as in the test assembly
ExternalUser.dll:
Error: ArgumentException: ExternalLib.Namesakes.Marker is not defined in this assembly; it comes from ExternalLib, Version=1.0.0.0. This assembly defines ExternalLib.Namesakes.Marker<>, ExternalLib.Namesakes.Marker<,>.
Type name '...' matches N types: ... Name one as listed.A name written without arity matches several generic types and no type without type
parameters, such as System.Func. Where the types are of more than one kind, defined,
referenced or forwarded, they are listed in a group for each kind, as in
ExternalUser.dll:
Error: ArgumentException: Type name 'ExternalLib.Namesakes.Crate' matches 2 types: this assembly defines ExternalLib.Namesakes.Crate<,>; it references ExternalLib.Namesakes.Crate<> from ExternalLib, Version=1.0.0.0. Name one as listed.
Types of one kind read matches 17 types this assembly forwards: System.Func<>,
... (the .NET runtime's System.Runtime.dll) or matches N types this
assembly references but does not define: .... Pass one of the listed names, which carry
their arity.
... is an array type, which only analyze with kind 'extension_methods' and decompile, analyze and compare with member_name take, to find the extension methods code can call on it.An array type has no members of its own in an assembly. Ask about its element type, or ask one of the named questions for the extension methods that apply to such an array.
An array type writes its element with its type arguments, as C# does: ... names ..., which takes N.The element of an array type was written without its type arguments, as in
System.Collections.Generic.List[]. Write it as C# does, such as
List<int>[].
Namespace not found: ...No type of the assembly lies in that namespace or below it. Namespaces are matched exactly and case-sensitively.
Member '...' not found on ..., its base types, or as an extension method.The name is not on the type, its base classes or among the extension methods that apply.
list_members lists what the type has; an accessor is named with its prefix,
get_Count, and a constructor .ctor or by the type's name. With an
overload hint, and for analyze's call_sites, which looks for a method,
the message begins Method '...'. Where a usual name does not apply, the message
ends with what does:
; its indexer is named Chars: System.String.get_Chars(int) for
get_Item, set_Item or Item on a type whose indexer has
another name (asked of the .NET runtime's System.Private.CoreLib.dll); ; a
struct that declares no constructor has none in the assembly, and instantiated_by lists where it
is created, ; an enum has no constructor or ; its static constructor is
named '.cctor' for .ctor or the type's name; and ; the methods named Run
are CallShapes.Namesakes.Store.Run(), CallShapes.Namesakes.Store.Run<T>() for
brackets whose count of type parameters fits no method of the name. For the name of a property of
a C# 14 extension block, which the class holds only as the methods the compiler made of its
accessors, the message ends with those methods, the ones to ask instead: ; Doubled is a
property of an extension block: ...get_Doubled(Gauge). When the search stopped at a
base class ILens could not read, the message says the base types ILens could load
and ends with the class and the reason, as in ExternalUser.dll without the folder of
ExternalLib.dll among the allow-roots:
Error: ArgumentException: Member 'Level' not found on ExternalUser.Inherited.Probe, the base types ILens could load, or as an extension method; ExternalLib.Inherited.Sensor and its base types were not searched, since ILens could not load ExternalLib, Version=1.0.0.0.
The three reasons and their remedies are under
Types from other assemblies; once the class can be read, the
search goes on into it. Through an array type the message reads Member '...' not found on
System.Byte[]: no public instance member of System.Array or System.Object and no extension method
of this assembly that applies to it has that name., with ; what ... derives from was
not read, since ... where the element's class could not be read.
... inherits ... from a base class this assembly does not define; ... comes from ...The member is declared on a base class another assembly defines, so the inspected assembly
holds no code for it. The message names the file that does: call decompile,
analyze or compare on that file to read the member. A member the type's
code cannot reach, a private one or an internal one its library does not grant this assembly, is
passed over as C# passes over it, so an extension method of the name answers in its place; for
such a member the error stands only where nothing else answers to the name.
'...' on ... has N matching overloads; ...: ...The name, or the name with the hint given, fits several methods. The message lists them and
says what tells them apart: specify parameter_count or parameter_types to
disambiguate; they are same-arity overloads — specify parameter_types to
disambiguate; write the type parameters of the one meant after its name, as
listed; they differ in their return type alone, which return_type selects;
these parameter types fit more than one; write those of the one meant as listed, as
where a short type name fits classes of that name in two namespaces, and likewise this
return type fits more than one; write that of the one meant as listed; or the
candidates share their parameter types and cannot be narrowed further. Asked of
CallShapes.dll:
Error: ArgumentException: 'op_Explicit' on CallShapes.Namesakes.Amount has 3 matching overloads; they differ in their return type alone, which return_type selects: CallShapes.Namesakes.Amount.op_Explicit(Amount) → int, CallShapes.Namesakes.Amount.op_Explicit(Amount) → long, CallShapes.Namesakes.Amount.op_Explicit(Amount) → decimal.
No overload of '...' on ... matches ...A parameter_count, parameter_types or return_type hint
was given and nothing satisfies it. The message lists every method of that name found across the
inheritance chain, so the right hint is usually visible in the error itself, and a confirmed "no
such overload" is often the answer that was being sought.
Property '...' on ... has N overloads — ...The name is shared by several indexers, which C# names alike. Name the accessor of the one
meant instead, get_Item or set_Item, with parameter_types;
the message lists the candidates.
'...' on ... names N explicit interface implementations: ... Pass the full name of one instead.The type implements several interface members of that name explicitly, such as two
GetEnumerator methods. Pass the full name of the one meant, as listed, which
includes its interface.
Property '...' on ... has no getter.An accessor's name was asked for a property that lacks that accessor; the message reads
has no setter likewise, and Event '...' on ... has no add accessor. or
has no remove accessor. for an event.
Member '...' on ... is ambiguous — it exists as more than one member kind ...One type declares the name as more than one kind of member, which only obfuscated or non-C# metadata produces. Names that classes at different levels of the inheritance chain use for different kinds do not trigger it: the member of the most derived class that declares the name answers. Pass an overload hint if the method is meant. An ordinary event does not trigger this either: the field the compiler keeps its subscribers in is recognized as part of the event.
parameter_count (N) contradicts parameter_types.Length (M).Both overload hints were given and disagree on the number of parameters. Pass one of them, or make them agree.
parameter_count, parameter_types and return_type select a method overload, so they need member_name.An overload hint was given to decompile, analyze or
compare without a member to select. Add member_name, or leave the hint
out to ask about the type.
Unknown result type commentsA referenced assembly is not in an allowed folder. ILens finds an assembly's references beside
it, at the top of each allow-root, and in the installed .NET; a reference it cannot find leaves
the decompiler without its types, so enum values come out as numbers, such as
(Glaze)1 for a value of another library's enum, and expressions of those types are
marked as unknown. Add the folder that holds the missing assemblies, such as a game's own
assemblies for a mod, as another --allow-root. The same missing library makes other
answers end with a line that names it, see
Types from other assemblies.
Almost always the two gates described under
Integrating with Claude Code: an already-running Claude app holding a
stale PATH, or a session that started before .mcp.json existed. Close Claude app
entirely, relaunch it, and start a new session. The server also refuses to start when its own
arguments are wrong. It then writes the reason to stderr as a line starting with
[ILens] and exits, which the MCP client typically reports as a server that failed to
connect:
[ILens] --allow-root path does not exist: ..., or is a file, not a
directory, for a root that is no directory.[ILens] Path could not be resolved through its symbolic links or junctions: ...
for a root whose links cannot be followed, such as one below a folder the server may not list,
and [ILens] Path has too many levels of symbolic links or junctions (possible cycle):
... for a chain of links that does not end.[ILens] unknown argument: ..., followed by a usage line, for any other
argument.[ILens] --max-total-size must be a positive integer (MB): ... and
[ILens] --max-search-seconds must be a non-negative integer (seconds): ..., and a
line that names the value a flag is missing.For machines where winget is unavailable or locked down by policy, or when you would rather inspect the binary before granting it execution.
irm https://raw.githubusercontent.com/tadis174/ILens/main/install.ps1 | iex
Installs to %LOCALAPPDATA%\Programs\ILens\ and adds that directory to your user
PATH. No admin elevation is needed.
Download ILens-windows-x64.zip from the
Releases page and extract it
anywhere. Then either add the directory to PATH yourself, or give the binary's full path in
.mcp.json's command field.
winget. winget upgrade Tadis.ILens to update;
winget uninstall Tadis.ILens to remove.
PowerShell one-liner. Re-run
irm https://raw.githubusercontent.com/tadis174/ILens/main/install.ps1 | iex to
update — the script overwrites the install directory in place and stops any running ILens
processes first. Uninstall with
irm https://raw.githubusercontent.com/tadis174/ILens/main/uninstall.ps1 | iex.
Manual ZIP. No automatic update mechanism — re-download
ILens-windows-x64.zip and re-extract on each new release. To uninstall, delete the
extracted folder and remove the PATH entry if you added one.
ILens.exeSelf-contained .NET binaries are sometimes false-positive-flagged on first sight. Check the
quarantine and restore the file, or add an exception for the install directory —
%LOCALAPPDATA%\Microsoft\WinGet\Packages\Tadis.ILens_*\ for winget,
%LOCALAPPDATA%\Programs\ILens\ for the PowerShell script — before re-running the
installer.
This affects browser-downloaded ZIP installs, which carry Mark-of-the-Web. The winget and PowerShell paths do not. For a manual browser download, click "More info → Run anyway".
winget upgrade Tadis.ILens fails with remove: Access is denied: "...\ILens.exe"A running ILens MCP server — a Claude Code session, Claude Desktop, or another AI client that
registered ILens — holds an exclusive lock on the binary winget is trying to replace. Close every
AI client with ILens registered and re-run the upgrade. The PowerShell install.ps1
path handles this automatically by stopping running ILens processes before extraction, so the
same upgrade via irm ... | iex needs no manual cleanup. winget's
portable installer type has no pre-install hook where an equivalent step could
run.
claude mcp add CLIA programmatic alternative for anyone who would rather not edit .mcp.json by
hand:
claude mcp add ilens ilens -- --allow-root "C:\path\to\dlls"
The doubled ilens is intentional: the first is the MCP server alias — the same
string that becomes the JSON key in .mcp.json — and the second is the binary on
PATH after install. The -- separator before --allow-root is required so
claude mcp add does not consume it as one of its own options.
Claude Desktop reads %APPDATA%\Claude\claude_desktop_config.json. The entry is
equivalent to the Claude Code one:
{
"mcpServers": {
"ilens": {
"command": "ilens",
"args": [
"--allow-root", "C:\\path\\to\\dlls"
]
}
}
}
Claude Desktop's chat does not read server instructions and has no per-project
CLAUDE.md, so paste the paragraph from
Project-level guidance into your Claude Desktop Project's custom
instructions.
This guide documents ILens 12. Source and issue tracker: github.com/tadis174/ILens.
ILens is released under the MIT License; the full text ships as LICENSE beside
this guide in the release ZIP. Every third-party dependency and bundled runtime component is
mapped to its upstream license in third-party-licenses/INDEX.md, included in the
same ZIP alongside the per-source license texts.