CodeCharter analyses your solution once and hands the DSL a cached code model. This page lists what you can query, sorted by entity type.
When you write a rule you navigate the model starting from a root collection. Property reads, method calls, LINQ chains, and at the end you collect the matches.
Root collections
Every query starts at one of these.
| Root | Element | Contents |
|---|---|---|
Types |
TypeModel |
All classes, structs, interfaces, enums, records |
Methods |
MethodModel |
All methods across all types (constructors are not included; they live in TypeModel.Constructors). Operator overloads and finalizers are ordinary entries here too — m.Name is "op_Addition", "op_Equality", etc. for an overloaded operator and "Finalize" for a ~Type() destructor, not the source-level spelling |
Properties |
PropertyModel |
All properties |
Fields |
FieldModel |
All fields |
Events |
EventModel |
All events |
Namespaces |
NamespaceModel |
All namespaces |
Assemblies |
AssemblyModel |
One per project |
TypeDependencies |
TypeDependency |
Directed type-to-type dependencies |
Files |
FileModel |
One per source file: using directives and comment trivia |
Diagnostics |
DiagnosticModel |
Whole-program analyzer findings — see Security diagnostics |
AllTypesFlattened |
TypeModel |
Every type including nested types at any depth (Types stops at top-level types) |
AllMethodsFlattened |
MethodModel |
Every method across AllTypesFlattened, including nested types |
AllBodies |
see below | Every method and property (including accessors) with a body, across AllTypesFlattened, ready for .Catches/.Invocations/etc. (see Body-level convenience collections) without navigating through Types and Methods yourself |
AllLiteralSources |
see below | Every method, property, and field that can carry literal expressions, across AllTypesFlattened, ready for .Literals |
AllConstructorInjectableCreations |
ObjectCreationModel |
Every new expression in a constructor body or field initializer, across AllTypesFlattened, after the exclusions the new-in-constructor rule applies (value types, records, BCL/framework types, Exception/Args/Options-style suffixes) |
DocumentableDeclarations |
see below | Every type, method, property, field, and event across AllTypesFlattened, as one sequence — for a rule that checks XML-doc coverage uniformly instead of separately per entity kind |
TypeModel
Identity:
Name string # without namespace
FullName string # Namespace.Type
Namespace NamespaceModel # object, not a string: use t.Namespace.FullName
Kind string # "Class" | "Interface" | "Struct" | "Enum" | "Record" | "Delegate"
SourceFile string
LineNumber int
Modifiers:
IsAbstract bool
IsSealed bool
IsStatic bool
IsPartial bool
IsGeneric bool
IsRecord bool
IsValueType bool # true for struct, record struct, enum; false for class, record class/record, interface, delegate — the only way to tell a record struct apart from a record class, since both report Kind == "Record"
AccessModifier string # "Public" | "Internal" | "Private" | "Protected" | "ProtectedInternal" | "PrivateProtected"
Structural metrics:
LinesOfCode int
NumberOfMethods int
NumberOfFields int
NumberOfProperties int
NumberOfDerivedTypes int
WeightedMethodsPerClass int
DepthOfInheritance int
LackOfCohesion double
ResponseForClass int
Instability double
MaintainabilityIndex double
CouplingEfferent int
CouplingAfferent int
Abstractness and DistanceFromMainSequence are namespace-level
metrics; you find them on NamespaceModel, not here.
Relationships:
BaseType TypeModel # only set when the base type is declared in the analysed code; null for framework base types
DeclaredBaseTypeName string # fully qualified name of the DIRECT base type, e.g. "System.Exception" (set even for framework types); null when there is no explicit base
BaseTypeNames collection<string> # fully qualified names of the WHOLE inheritance chain (direct + transitive) up to System.Object; crosses the framework boundary, so use it for transitive base checks
Methods collection<MethodModel> # for a Kind == "Delegate" type, holds exactly one synthesized "Invoke" entry carrying the delegate's real ReturnType/Parameters — the only way to reach a delegate's signature
Constructors collection<MethodModel>
Properties collection<PropertyModel>
Fields collection<FieldModel>
Events collection<EventModel>
NestedTypes collection<TypeModel>
DerivedTypes collection<TypeModel>
UsedTypes collection<TypeModel>
UsedByTypes collection<TypeModel>
ImplementedInterfaces collection<TypeModel>
ImplementedInterfaceNames collection<string> # fully qualified names of every implemented interface (direct or inherited), erased to the interface's generic definition, e.g. "System.IEquatable<T>"
ImplementedInterfaceConstructedNames collection<string> # same set and order as ImplementedInterfaceNames, but keeping each interface's constructed type arguments, e.g. "System.IEquatable<Foo>" — use to tell IEquatable<Bar> apart from IEquatable<Foo>, which ImplementedInterfaceNames cannot
Attributes collection<AttributeModel>
TypeParameters collection<TypeParameterModel> # generic type parameters, e.g. the T in class Box<T>; empty when IsGeneric is false
Reaches(pattern, mode?) bool # see "Reachability" below
ReachedBy(pattern, mode?) bool # see "Reachability" below
InboundReferenceCount() int # see "Reachability" below
Syntax node # the TYPE DECLARATION's own syntax — not its members' bodies, see AllBodies/a member's own Syntax for those. For a partial type, anchors at the same declaration SourceFile/LineNumber already anchor at (the last-processed part), so it never reflects the other parts. Empty for enum and delegate declarations. See "Syntax (statement-level navigation)" below.
Reachability
t.Reaches(pattern, mode?) and t.ReachedBy(pattern, mode?) answer a question the
member-level UsedTypes/UsedByTypes collections above cannot: whether a directed
path of edges connects t to some type matching pattern, however many hops away.
Reaches walks outward from t; ReachedBy is the transpose, true when some type
matching pattern can reach t. A type does not reach a pattern it matches itself
unless it sits on a genuine cycle.
pattern is a glob, not a regex: * matches any run of characters, ? matches
exactly one, matched against the candidate type's full name. Do not paste a regex here —
a pattern containing ^, \, or $ is flagged at lint time, since those are regex
syntax that means nothing in a glob. Use Matches("regex") (see String
helpers) when you need an actual regular expression instead.
The optional mode argument selects the edge set the path is computed over. The
default and most common mode is "declared": exactly the six edge kinds a
compile-time scan of the source can see — inheritance, interface implementation,
member/parameter/return-type usage, a resolved call, an applied attribute, and an
object-creation expression. This is a compile-time-only question, so a declared hit
is always a genuine dependency chain, which makes it the right mode for a
prohibition rule such as "domain must not reach infrastructure":
from t in Types
where t.FullName.Matches("App.Domain\\..*")
where t.Reaches("App.Infra.*", "declared")
select t
If App.Domain.OrderService only holds an IRepository interface and App.Infra.SqlRepository
implements it elsewhere, declared correctly stays silent — that is the DIP-correct shape, and
declared never follows dependency-injection wiring from an interface to its resolved
implementation. This is also its blind spot: reflection, serializers, source generators, and
string-keyed DI resolution are all invisible to it, so declared can under-report a
requirement rule ("X must reach Y") the same way. The mode set is closed: the only
valid names are "declared" and "dispatch", where "dispatch" additionally follows
interface and virtual dispatch at every resolved call site; any other string is an
authoring error flagged at lint time.
t.InboundReferenceCount() is the depth-1 sibling of these predicates: the number of
distinct solution types with at least one direct declared edge into t, over the same
six edge kinds. It takes no arguments and counts direct referrers only, not transitive
reachability — t.AccessModifier != "Public" && t.InboundReferenceCount() == 0 flags an
internal type nothing in the solution references.
MethodModel
Name string
FullName string
AccessModifier string
IsAsync bool
IsStatic bool
IsAbstract bool
IsSealed bool
IsOverride bool
IsInterfaceImplementation bool
IsConstructor bool
IsExtensionMethod bool
IsVirtual bool
IsNew bool # declared with the `new` modifier, hiding a same-signature base member; read from the declaration syntax, since Roslyn has no direct "is new" symbol flag
ReturnType string
ReturnTypeShortName string
IsReturnTypeClass bool
IsReturnTypeInterface bool
IsReturnTypeRecord bool
ResolvedReturnType TypeInfo
Parameters collection<ParameterModel>
LinesOfCode int
CognitiveComplexity int
CyclomaticComplexity int
NestingDepth int
NumberOfLocalVariables int
NumberOfParameters int
NumberOfOverloads int
Overloads collection<MethodModel>
DeclaringType TypeModel
CalledMethods collection<MethodModel>
CalledByMethods collection<MethodModel>
Attributes collection<AttributeModel>
TypeParameters collection<TypeParameterModel> # the method's own generic type parameters, e.g. the T in void Map<T>(); does not include the declaring type's — empty for a non-generic method
SourceFile string
LineNumber int
Syntax node # the method body, for statement-level rules — see below
Syntax (statement-level navigation)
The properties above describe a method from the outside. m.Syntax lets you
query the statements inside the body — loops, try/catch, throw, calls,
and so on. You start at m.Syntax and walk down to the construct you want.
m.Syntax is a node. Reading a member gives you another node, a node set
(a collection of nodes), or a token (a leaf such as an identifier).
Members of a node
Every node — m.Syntax included — exposes exactly these:
Kind string # the construct name, e.g. "CatchClause" (see "Construct kinds")
Text string # the verbatim source text of this construct
Line int # 1-based start line
Column int # 1-based start column
Descendants node set # every construct below this node, at any depth
Children node set # the direct child constructs only (one level down)
ResolvedType TypeInfo # the semantically resolved type of this node, or null
ResolvedSymbol string # the fully qualified name of the symbol it binds to, or null
<slot> node | node set | token # a named part, see "Child slots per construct"
A token (for example the Identifier of a catch declaration) exposes Kind,
Text, Line, and Column only. It is a leaf: it has no Descendants,
Children, or child slots.
Members of a node set
A node set is a collection. It exposes Count and the same closed
collection helpers (.Any, .Where, .Count, .Select,
…) as every other collection, plus:
Count int # number of nodes in the set
Descendants node set # all descendants of every node in the set
Children node set # direct children of every node in the set
<Kind> node set # the nodes of that kind, e.g. .Descendants.CatchClause
.<Kind> filters by construct name (see below). A name that is not a known
construct is reported as an error, not a silent empty result.
Construct kinds
The exact names you use as a kind filter (.Descendants.<Kind>) or compare with
Kind. They are the names of the C# language constructs.
Statements Block ExpressionStatement LocalDeclarationStatement
IfStatement ElseClause SwitchStatement SwitchSection
ForStatement ForEachStatement WhileStatement DoStatement
TryStatement ThrowStatement ReturnStatement YieldStatement
UsingStatement LockStatement
BreakStatement ContinueStatement GotoStatement
Clauses CatchClause CatchDeclaration CatchFilterClause FinallyClause
Expressions InvocationExpression MemberAccessExpression ObjectCreationExpression
BinaryExpression AssignmentExpression ConditionalExpression
CastExpression AwaitExpression IdentifierName
LiteralExpression NumericLiteralExpression StringLiteralExpression
ArgumentList Argument
Declarations VariableDeclaration VariableDeclarator EqualsValueClause
These follow C#'s own grammar; any other construct in the language is reachable by its grammar name, no matter how deeply nested. The grammar is large — the kinds above are the ones rules normally need.
Child slots per construct
A construct's named parts. Each yields a node, a node set, or a token. A slot
that is absent in the source (an if with no else, a bare throw;, a
catch {} with no declaration) yields empty and is safe to read.
TryStatement Block (node) Catches (node set) Finally (node)
CatchClause Declaration (node) Filter (node) Block (node)
CatchDeclaration Type (node) Identifier (token)
CatchFilterClause FilterExpression (node)
FinallyClause Block (node)
IfStatement Condition (node) Statement (node) Else (node)
ElseClause Statement (node)
ForStatement Declaration (node) Condition (node) Incrementors (node set) Statement (node)
ForEachStatement Type (node) Identifier (token) Expression (node) Statement (node)
WhileStatement Condition (node) Statement (node)
DoStatement Statement (node) Condition (node)
SwitchStatement Expression (node) Sections (node set)
SwitchSection Labels (node set) Statements (node set)
ThrowStatement Expression (node)
ReturnStatement Expression (node)
Block Statements (node set)
ExpressionStatement Expression (node)
LocalDeclarationStatement Declaration (node)
UsingStatement Declaration (node) Expression (node) Statement (node)
LockStatement Expression (node) Statement (node)
InvocationExpression Expression (node) ArgumentList (node)
ArgumentList Arguments (node set)
Argument Expression (node)
MemberAccessExpression Expression (node) Name (node)
ObjectCreationExpression Type (node) ArgumentList (node) Initializer (node)
BinaryExpression Left (node) Right (node)
AssignmentExpression Left (node) Right (node)
ConditionalExpression Condition (node) WhenTrue (node) WhenFalse (node)
CastExpression Type (node) Expression (node)
AwaitExpression Expression (node)
IdentifierName Identifier (token)
LiteralExpression Token (token)
VariableDeclaration Type (node) Variables (node set)
VariableDeclarator Identifier (token) Initializer (node)
EqualsValueClause Value (node)
Example
Flag throw ex;, which discards the original error location:
@name "Rethrow loses the original location"
@severity error
@category "ErrorHandling"
@recommendation "Use a bare throw; to keep the original stack trace"
Methods.Where(m =>
m.Syntax.Descendants.CatchClause.Any(c =>
c.Block.Descendants.ThrowStatement.Any(t =>
t.Expression.Text == c.Declaration.Identifier.Text)))
The finding points at the exact throw statement, even when it sits deep inside
if branches or loops, not at the whole method.
Resolving types and symbols
By default m.Syntax matches the written form of the code. When you need to
match the real type behind an expression — regardless of aliased usings or partial
names — read ResolvedType (a TypeInfo) or ResolvedSymbol (the symbol's fully
qualified name). Both are null when resolution is unavailable, so guard accordingly.
# Flag MD5/SHA1 by their real type, however the code spells them:
Methods.Where(m =>
m.Syntax.Descendants.ObjectCreationExpression.Any(n =>
n.ResolvedType.FullName == "System.Security.Cryptography.MD5"))
ResolvedSymbol on a fluent extension-method call renders the reduced
form, qualified by the receiver's static type — never by the declaring static
class. xs.Count() resolves to
"System.Collections.Generic.IEnumerable<int>.Count<int>()", not
"System.Linq.Enumerable.Count<int>(...)"; logger.LogError(...) resolves to
"Microsoft.Extensions.Logging.ILogger.LogError(...)", not a
LoggerExtensions. prefix. Pin the exact rendered string for the overload you
target (parameter lists are part of the display name) rather than guessing a
prefix.
Limits
m.Syntax does not follow a value across statements — var e2 = ex; throw e2;
reads as a different variable — and it is scoped to a single method body. For checks
that span several methods, use the structural properties above. Syntax is empty for
members without a body (abstract or interface methods); navigating it then yields no
matches.
Body-level convenience collections
Walking raw syntax with .Descendants.<Kind> covers everything, but for the
most common patterns MethodModel exposes them pre-extracted and already
typed, saving you the tree walk. The same collections are available on a
property's accessor bodies (PropertyModel.Catches,
PropertyModel.Invocations, and so on — empty for an auto-implemented
property), and Literals/ObjectCreations are also available on
FieldModel for its initializer expression.
Catches collection<CatchClauseModel>
Invocations collection<InvocationModel>
ObjectCreations collection<ObjectCreationModel>
MemberAccesses collection<MemberAccessModel>
Literals collection<LiteralModel>
BinaryExpressions collection<BinaryExpressionModel>
LocalDeclarations collection<LocalDeclarationModel>
UnusedParameters collection<ParameterModel> # this method's parameters never referenced in its body, after the same exclusions the built-in unused-parameter rule applies (underscore-prefixed names, CancellationToken, the (sender, e) event-handler shape); empty for abstract/virtual/override/partial/explicit-interface/empty-bodied methods
Shape MethodShapeModel # coarse control-flow shape, see below
CatchClauseModel — a single catch clause:
IsEmpty bool # no statements in the block: it silently swallows the exception
ExceptionType TypeInfo # resolved caught type, null for a bare `catch { }` or unresolved
RethrowsOriginal bool # a bare `throw;` anywhere in the block (not `throw ex;`)
SourceFile, Line, Column
InvocationModel — a single method call expression:
Target string # verbatim invoked expression, e.g. "_repo.Save"
ResolvedType TypeInfo # resolved return type of the invoked method
Arguments collection<string> # verbatim argument text, in call order
IsUnobservedStatement bool # this call IS the whole statement: not awaited, assigned, returned, or passed as an argument
SourceFile, Line, Column
Fire-and-forget check: i.IsUnobservedStatement && i.ResolvedType.FullName.StartsWith("System.Threading.Tasks.Task").
ObjectCreationModel — a new Foo(...) or target-typed new(...):
ResolvedType TypeInfo # resolved created type, null when unresolved
SourceFile, Line, Column
MemberAccessModel — a member access such as DateTime.Now:
Name string # accessed member's simple name, e.g. "Now"
ResolvedType TypeInfo
SourceFile, Line, Column
LiteralModel — a literal expression:
Value string # verbatim source text of the literal token
IsNumeric bool
IsNamedConstant bool # inside a const/static readonly field, const local, or enum member declaration
IsTrivialMagicExemptValue bool # numeric value is 0, 1, or 2, across suffixed/hex/binary/real forms
IsInMagicNumberExcludedContext bool # any of: named-constant context, attribute argument, array index
SourceFile, Line, Column
BinaryExpressionModel — a == b, x + y, and similar:
Operator string # e.g. "==", "!=", "+", "&&"
Left, Right BinaryOperandModel
IsInNestedScope bool # nested inside a lambda body or LINQ query expression
IsConcatenationRoot bool # this `+` is the topmost node of its string-concatenation chain
ConcatenationOperatorCount int # number of `+` operators in the chain rooted here
SourceFile, Line, Column
BinaryOperandModel (each side of Left/Right):
ResolvedType TypeInfo # null for the `null` literal too
IsNull bool
IsStringLiteral bool # a string literal, not merely a value resolving to System.String
LocalDeclarationModel — a local variable declaration:
IsVar bool
DeclaredTypeName string # verbatim declared type text, e.g. "Foo" or "var"
DeclaredType TypeInfo # resolved declared type; for `var`, the inferred type
InitializerKind string # "ObjectCreation" | "Cast" | "None" | "Other"
InitializerType TypeInfo
SourceFile, Line, Column
MethodShapeModel (m.Shape) — the coarse control-flow shape of a body:
BodyStatementCount int # top-level statements directly in the body block
BodyLinesOfCode int # non-whitespace lines in the body block
FirstStatementIsGuardIf bool # first executable statement (after leading local declarations) is an if with no else
FirstGuardIfBodyLineRatio double # fraction (0.0-1.0) of body lines inside that guard if; 0 when the flag above is false
Missing-guard-clause example: m.Shape.BodyStatementCount >= 3 && m.Shape.FirstStatementIsGuardIf && m.Shape.BodyLinesOfCode > 5 && m.Shape.FirstGuardIfBodyLineRatio > 0.7.
ParameterModel
Name string
Type string # fully qualified type name
TypeShortName string # e.g. "CancellationToken"
HasDefaultValue bool
IsParams bool
IsOut bool
IsRef bool # ref, including ref readonly
IsIn bool # in (read-only by-reference)
IsTypeInterface bool
IsTypeClass bool
IsTypeRecord bool
IsTypeEnum bool
IsTypeStruct bool
IsTypePrimitive bool
IsTypeAbstract bool
ResolvedType TypeInfo
TypeBaseTypeNames collection<string> # base type names of the parameter type
Attributes collection<AttributeModel>
PropertyModel
Name string
Type string # type name
AccessModifier string
IsStatic bool
IsAutoProperty bool
IsAbstract bool
IsVirtual bool
IsOverride bool
HasGetter bool
HasSetter bool # set or init
HasInitOnlySetter bool # true when the setter is init-only
IsInterfaceImplementation bool
IsNew bool # declared with the `new` modifier, hiding a same-name base member; read from the declaration syntax, since Roslyn has no direct "is new" symbol flag
DeclaringType TypeModel
Attributes collection<AttributeModel>
ResolvedType TypeInfo
SourceFile string
LineNumber int
FieldModel
Name string
Type string # type name
AccessModifier string
IsStatic bool
IsReadonly bool # note the lowercase "o": property names are case-sensitive
IsConst bool
IsVolatile bool # declared `volatile`; double-checked-locking rules use this to avoid flagging an already-safely-published field
DeclaringType TypeModel
Attributes collection<AttributeModel>
ResolvedType TypeInfo
SourceFile string
LineNumber int
EventModel
Name string
DelegateType string # delegate type of the event
IsStatic bool
DeclaringType TypeModel
SourceFile string
LineNumber int
NamespaceModel
Name string # last segment
FullName string
Assembly AssemblyModel
Types collection<TypeModel> # directly contained types
NumberOfTypes int
Abstractness double
Instability double
DistanceFromMainSequence double
AssemblyModel
Name string
Version string
TargetFramework string
IsTestProject bool # resolved via build property, test-framework reference, or name suffix
Namespaces collection<NamespaceModel>
TypeDependency
Source TypeModel
Target TypeModel
Kind string # "Inherits" | "Implements" | "Uses"
AttributeModel
Name string # keeps the compiler-visible "Attribute" suffix, e.g. "ObsoleteAttribute"
FullName string # e.g. "System.ObsoleteAttribute"
ArgumentValues collection<string> # positional/constructor argument values, in order, as TypedConstant.ToCSharpString() display strings
NamedArguments collection<AttributeArgumentModel> # named arguments, in source declaration order
Every argument value is rendered with Roslyn's TypedConstant.ToCSharpString(): a string constant
renders quoted ("reason"), a number or bool as its literal (1, true), an enum member qualified
with its declaring type (N.Color.Green), a typeof(...) argument as typeof(string), and an
array argument as a brace-delimited element list ({1, 2, 3}). Each rendered value is capped at 256
characters (with a trailing "…" marker), and an over-long array argument (more than 32 elements,
or a combined element length past the cap) renders only a bounded prefix of its elements — the
list then ends in , …} — so a hostile multi-megabyte attribute literal cannot balloon the
analyzed model. ArgumentValues and NamedArguments are always empty collections,
never null, when the attribute application supplies no arguments of that kind.
The DSL has no Zip/ElementAt to correlate two parallel collections by index, so
NamedArguments pairs each argument's name with its value in one AttributeArgumentModel entry
rather than exposing separate name/value collections.
AttributeArgumentModel
A single named argument of an attribute application (e.g. the Skip = "reason" in
[Fact(Skip = "reason")]).
Name string # the named argument's name, as written in source
Value string # the named argument's rendered value — same format and 256-character cap as AttributeModel.ArgumentValues
Detect a placeholder skip reason: m.Attributes.Any(a => a.Name == "FactAttribute" && a.NamedArguments.Any(na => na.Name == "Skip" && na.Value == "\"TODO\"")).
TypeParameterModel
Available via TypeParameters on TypeModel and MethodModel — a generic
type parameter such as the T in class Box<T> or void Map<T>().
Name string # e.g. "T"
Ordinal int # zero-based position in the declaring type's/method's type parameter list
Variance string # "None" (invariant) | "In" (contravariant, e.g. IComparer<in T>) | "Out" (covariant, e.g. IEnumerable<out T>)
HasReferenceTypeConstraint bool # class constraint
HasValueTypeConstraint bool # struct constraint
HasNotNullConstraint bool # notnull constraint
HasConstructorConstraint bool # new() constraint
ConstraintTypeNames collection<string> # base class and/or interface constraint names, in declaration order
TypeInfo
Returned by ResolvedType / ResolvedReturnType. Unlike TypeModel
it also works for types outside your solution:
FullName string
ShortName string
IsClass bool # classes excluding records
IsRecord bool
IsInterface bool
IsEnum bool
IsStruct bool
IsPrimitive bool
IsAbstract bool
IsTypeParameter bool # an unresolved generic type parameter such as `T` in `List<T>`
IsSealed bool # also true for structs, enums, and delegates (not static classes)
String helpers
When you read a string property you can chain:
.StartsWith("...")
.EndsWith("...")
.Contains("...")
.Matches("regex")
.ToLower()
.ToUpper()
.Substring(start) # to the end of the string
.Substring(start, length)
.Length # property, not a method
Comparisons are case-sensitive unless the helper documents otherwise.
This list is closed: exactly the methods above are available. Calling
any other string method you may know from C#, such as Trim or Replace,
fails at evaluation time.
+ also works between strings (see Syntax overview for
the full operator list), so you can build a comparison value from parts, e.g.
"Get" + p.Name == m.Name to flag a GetFoo() method sitting beside a Foo
property (CA1721):
from t in Types
from m in t.Methods
where t.Properties.Any(p => m.Name == "Get" + p.Name && m.Parameters.Count == 0)
select m
Collection helpers
All root collections and sub-collections (Methods, Parameters,
Properties, ...) support exactly these methods:
.Where(x => ...)
.Select(x => ...)
.SelectMany(x => ...)
.Any(x => ...)
.All(x => ...)
.Count # property, not a method
.Contains(value)
.First(x => ...)
.Sum(x => ...)
.Min(x => ...)
.Max(x => ...)
.Average(x => ...)
.OrderBy(x => ...)
.OrderByDescending(x => ...)
.Take(n)
.Distinct()
This list is closed as well: calling any method outside it, including
common LINQ operators such as GroupBy, Skip, or FirstOrDefault,
fails at evaluation time. What happens
then depends on the query form: the LINQ form skips just the failing
item, the fluent form aborts the whole rule without findings. See
DSL grammar for details.
Common pitfalls
.Countinstead of.Count(): sub-collections useCountas a property.- Parentheses, not argument count, decide whether a call is a method call:
.Any(),.Count(), and.ToLower()work as method calls both as steps of the fluent chain and inside nested expressions (m.Name.ToLower()); leaving the parentheses off (m.Name.ToLower) is a property read instead and fails, sinceToLoweris not a property. Kind == "Class"is case-sensitive: the capital letter is required.AccessModifier == "Public": we follow the C# Pascal-case convention.Parametersdoes not contain thethisreceiver for instance methods..First()and.First(x => ...)throw when nothing matches, exactly like LINQ'sFirst. Guard with.Any(...), or use.Where(...).Count > 0as the guard condition, rather than assuming an empty result is returned silently.
Requesting a property
If your use case needs a property that this catalog does not list yet, please tell us. The DSL grows along real demand.
Where to go next
- DSL grammar: how the language is structurally put together.
- Rule examples: practical applications of the predicates.
- Syntax overview: more compact variant of this catalog.