Der "Selector" ist der erste Teil einer Regel, die from-Klausel oder
der initiale Collection-Zugriff. Er bestimmt auf welcher Sorte
Code-Element die Regel arbeitet.
Top-Level-Collections
Diese Collections können Sie direkt als Startpunkt nutzen:
| Collection | Element-Typ | Beschreibung |
|---|---|---|
Types |
TypeModel |
Alle Klassen, Structs, Interfaces, Records, Enums |
Methods |
MethodModel |
Alle Methoden; Konstruktoren sind nicht enthalten, diese erreichen Sie über t.Constructors am Typ |
Properties |
PropertyModel |
Alle Properties |
Fields |
FieldModel |
Alle Felder |
Events |
EventModel |
Alle Events |
Namespaces |
NamespaceModel |
Alle Namespaces |
Assemblies |
AssemblyModel |
Alle analysierten Assemblies |
TypeDependencies |
TypeDependency |
Coupling-Beziehungen zwischen Typen |
Files |
FileModel |
Eine pro Quelldatei: using-Direktiven und Kommentar-Trivia |
Zwei Schreibweisen
Die DSL unterstützt sowohl from x in Y (Query-Syntax) als auch
Y.Where(x => ...) (Method-Syntax). Beide produzieren das gleiche
Ergebnis, mit einem Unterschied im Fehlerverhalten: Schlägt die Auswertung
eines Elements fehl (etwa durch eine falsch geschriebene Property), wird in
der Query-Syntax nur dieses Element übersprungen, in der Method-Syntax bricht
die gesamte Regel ab und meldet keine Verstöße.
Query-Syntax:
from t in Types
where t.Kind == "Class"
where t.IsAbstract
select t
Method-Syntax:
Types.Where(t => t.Kind == "Class" && t.IsAbstract)
Wann welche? Bei mehreren where-Klauseln ist Query-Syntax lesbarer.
Bei einer Bedingung oder einer Aggregation (.Count > 10, wobei Count eine
Property ist) ist Method-Syntax kürzer.
Per-Element-Properties
Die Element-Typen haben jeweils Properties, die Sie in where-Klauseln
und Sub-Queries nutzen können.
TypeModel (Auszug)
t.Name // einfacher Name
t.FullName // mit Namespace
t.Namespace // Namespace-Objekt; für den Namen t.Namespace.FullName nutzen
t.Kind // "Class", "Interface", "Struct", "Enum", "Record"
t.IsAbstract // bool
t.IsSealed // bool
t.IsStatic // bool
t.IsRecord // bool
t.LinesOfCode // int
t.NumberOfMethods // int
t.Methods // Sammlung von MethodModel
t.Constructors // Sammlung von MethodModel
t.NestedTypes // Sammlung von TypeModel
t.DerivedTypes // Sammlung von TypeModel
t.UsedTypes // welche Typen referenziert dieser Typ
t.UsedByTypes // welche Typen referenzieren diesen
t.BaseType.FullName // Vater-Klasse
MethodModel (Auszug)
m.Name // Methodenname
m.AccessModifier // "Public", "Private", "Protected", "Internal"
m.IsAsync // bool
m.IsStatic // bool
m.IsOverride // bool
m.IsInterfaceImplementation // bool
m.Parameters // Sammlung von ParameterModel
m.CalledMethods // welche Methoden ruft diese auf
m.CalledByMethods // welche Methoden rufen diese
m.LinesOfCode // int
m.DeclaringType // TypeModel
Die vollständige Referenz für Properties und Kind-Werte finden Sie im Predicate-Katalog; wenn Sie einen KI-Assistenten nutzen, liefert das get_authoring_docs-MCP-Tool (Topic predicates) dieselbe Referenz.
Composition mit Any / All / Count
Sub-Queries auf Collection-Properties. Für Any / All geben Sie meist einen
Lambda-Ausdruck an (Any(m => ...)), ein Aufruf ohne Argumente wie
t.Methods.Any() ist aber ebenfalls gültig und prüft nur, ob die Collection
Elemente enthält. Count steht sowohl als Property (t.Methods.Count) als
auch als Aufruf (t.Methods.Count()) zur Verfügung.
// Klassen mit mindestens einer async Methode ohne CancellationToken
from t in Types
where t.Methods.Any(m =>
m.IsAsync &&
!m.Parameters.Any(p => p.TypeShortName == "CancellationToken"))
select t
// Klassen mit mehr als 10 public Methoden
from t in Types
where t.Methods.Where(m => m.AccessModifier == "Public").Count > 10
select t
Statement-Level-Facts als Selector
Manche Pattern liegen innerhalb eines Methoden- oder Konstruktor-Bodys:
eine catch-Klausel, ein Member-Zugriff, ein Literal, ein binärer Ausdruck.
Diese stehen als typisierte Facts an jedem Body zur Verfügung
(m.Catches, m.MemberAccesses, m.Literals, m.BinaryExpressions,
m.Invocations, m.LocalDeclarations), erreichbar über die abgeflachte
AllBodies-Collection, sodass eine Regel jede Methode, jeden Konstruktor
und jeden Property-Accessor über alle Typen hinweg ohne Nesting-Grenze sieht:
@name "Direct DateTime usage"
@severity warn
@category "Testability"
AllBodies.SelectMany(m => m.MemberAccesses.Where(a => (a.Name == "Now" || a.Name == "UtcNow")
&& (a.ResolvedType.FullName == "System.DateTime" || a.ResolvedType.FullName == "System.DateTimeOffset")))
Der vollständige Katalog pro Fact-Typ steht im Predicate-Katalog.
Was als nächstes
- Suppressions, wenn eine Stelle bewusst ausgenommen werden soll
- Best Practices, gute vs. schlechte Selector-Strategien