---
title: Compile a rule into a delegate
summary: Build a LINQ expression tree from rule metadata and compile it into a runnable delegate.
status: draft
owner: SAIF.Core Documentation Team
last_reviewed: 2026-09-24
library: SAIF.Services.ClaimInfoApi.Core
reference_type: package
operation_id: OP-CIA-03
trigger: A caller request, or step 2/3/4 of OP-CIA-02
---

# Compile a rule into a delegate

This operation turns a `NotificationRule`'s string metadata (member name, operator name, target value) into a LINQ expression tree over the model type `T`, then compiles it into a runnable delegate. It is the reflection-and-expression core of the engine and the location of the library's principal performance finding: nothing here is cached.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CIA-03 |
| **Trigger** | A caller request (public static method), or [Evaluate one rule](op-cia-02-evaluate-one-rule.md). |
| **Inputs** | `r` (`NotificationRule`). |
| **Outputs** | A compiled `Func<T,TResult>`. |
| **Primary components** | `NotificationRuleEngine.CompileRule<T,TResult>`, `NotificationRuleEngine.BuildExpr<T>` |
| **Entry point** | `Services/SAIF.Services.ClaimInfoApi.Core/NotificationRuleEngine.cs` (lines 73–119) |

## Steps

1. Create a parameter expression of type `T`.
2. Build the expression body with `BuildExpr<T>`, choosing the first matching branch:
   1. Method branch: if `typeof(T).GetMethod(r.MemberName)` finds a public method, convert `TargetValue` to the method's first parameter type with `Convert.ChangeType`, and emit `model.Method(value)`. The literal string `"null"` is treated as a null value ([CIA-BR-008](../business-rules.md#cia-br-008-the-literal-target-value-null-means-a-null-comparison-value)). ([CIA-BR-009](../business-rules.md#cia-br-009-a-matching-public-method-name-invokes-that-method))
   2. Property-read branch: if `OperatorName` is null, emit `model.Property` ([CIA-BR-010](../business-rules.md#cia-br-010-no-operator-means-a-plain-property-read)).
   3. Binary-operator branch: if `OperatorName` parses as a `System.Linq.Expressions.ExpressionType` name (for example `Equal`, `GreaterThan`), convert `TargetValue` to the property type and emit `Expression.MakeBinary(op, model.Property, value)` ([CIA-BR-011](../business-rules.md#cia-br-011-an-operator-that-parses-as-an-expressiontype-builds-a-binary-comparison)).
   4. Member-method branch: otherwise, treat `OperatorName` as a public method on the property's type (for example `Contains`); convert `TargetValue` to that method's first parameter type and emit `model.Property.Method(value)` ([CIA-BR-012](../business-rules.md#cia-br-012-an-unrecognized-operator-is-treated-as-a-method-on-the-property's-type)).
3. Wrap the body in `Expression.Lambda<Func<T,TResult>>` and call `.Compile()`.
   - No caching is performed: the call `Expression.Lambda<Func<T, TResult>>(body, parameter).Compile()` runs once per rule, per model, per call to this operation — see [Architecture: Design constraints](../architecture.md#design-constraints).
4. Failure behavior: there is no validation or `try`/`catch`.
   - An unknown member name produces a `NullReferenceException`, `ArgumentNullException`, or `ArgumentException` from the `Expression` factories*.
   - A non-convertible `TargetValue` produces `InvalidCastException` or `FormatException` from `Convert.ChangeType`.
   - A delegate whose type does not match `TResult` produces `ArgumentException`.
   - All of these propagate to the caller.

## Process flow

The following diagram visualizes the expression-building and compilation flow.

```mermaid
flowchart TD
	Start([Trigger]) --> Param["Create parameter<br>expression of type T"]
	Param --> Branch{"BuildExpr branch"}
	Branch -->|Method match| Method["model.Method(value)"]
	Branch -->|No operator| PropRead["model.Property"]
	Branch -->|Operator is ExpressionType| Binary["Expression.MakeBinary(op,<br>model.Property, value)"]
	Branch -->|Other operator| MemberMethod["model.Property.Method(value)"]
	Method --> Lambda["Expression.Lambda&lt;Func&lt;T,TResult&gt;&gt;<br>.Compile() — not cached"]
	PropRead --> Lambda
	Binary --> Lambda
	MemberMethod --> Lambda
	Lambda --> Done([Return compiled Func delegate])
```

## Failure and edge-case behavior
- No delegate cache exists: every call recompiles the expression tree from scratch, even for the same rule/model-type pair evaluated repeatedly — a tech-debt/performance constraint, not a mitigated design choice*.
- No validation of `MemberName`, `OperatorName`, or `TargetValue` occurs before expression construction; malformed metadata surfaces as a runtime exception at compile or invoke time rather than a checked error.

> **Note:** Statements marked with `*` were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `Services/SAIF.Services.ClaimInfoApi.Core/NotificationRuleEngine.cs`
