[EL] DEBUG-4880 Align probe expression compilation with runtime types by dudikeleti · Pull Request #7992 · DataDog/dd-trace-dotnet

@dudikeleti

## Summary of changes
- Reworked `ProbeExpressionEvaluator` to cache compiled expressions
**per runtime type combination** (invocation target, return value, and
scope members), instead of a single `Lazy<>` instance shared across all
invocations.
- Introduced `ExpressionCacheKey` to uniquely identify a compilation
context using **runtime types** (`Value.GetType()` when available) and
member runtime types to support polymorphic calls.
- Replaced single cached fields with
`ConcurrentDictionary<ExpressionCacheKey, …>` caches for:
  - Templates (`CompiledExpression<string>[]?`)
  - Condition (`CompiledExpression<bool>?`)
  - Metric (`CompiledExpression<double>?`)
  - Span decorations (compiled decorations array)
- Preserved evaluation semantics and error aggregation, while ensuring
compiled delegates match the actual runtime types.

## Reason for change
The previous implementation compiled expressions once (on first
invocation) and reused them for all subsequent calls. This fails in
polymorphic scenarios where:
- The declared type differs from the runtime type (e.g., generics/base
class/interface method invoked on a derived instance).
- Parameters/locals are declared as a base type (or `object`) but carry
different concrete runtime types across invocations.

In these cases, delegates compiled against the first observed type can
fail later (typically invalid casts / expression binding mismatches),
causing evaluation errors and incorrect probe results.

## Implementation details
- Compute a **runtime-aware cache key** on each `Evaluate()` call:
- `thisType`: `InvocationTarget.Value?.GetType()` (fallback to declared
type, then `typeof(object)`)
  - `returnType`: `Return.Value?.GetType()` (fallback to declared type)
- `members`: runtime types captured from `ScopeMember.Value?.GetType()`
(fallback to declared `ScopeMember.Type`)
- Store compiled artifacts in thread-safe caches:
  - `ConcurrentDictionary<ExpressionCacheKey, ...>`
  - On cache miss: compile, then `TryAdd`
- Added `ExpressionCacheKey` struct:
- Precomputes hash over `thisType`, `returnType`, and member runtime
types.
- Implements equality by comparing `ThisType`, `ReturnType`, and the
captured member type sequence (hash used as a fast-path).
- Added debug logging for cache misses (type + hash + cache size) to
help diagnose compilation churn and verify caching behavior.

## Test coverage
- Existing `DebuggerExpressionLanguageTests` remain supported via
`CompiledTemplates` / `CompiledCondition` accessors (now returning the
first cached entry).
- Snapshot Exploration Test

## Dependencies
- Depends on: #7991, #7992, #7993
- Merge order: #7991#7992#7993 → (this PR)

## Other details
- Caches may grow with many unique type combinations; however this is
required for correctness under polymorphic workloads. If needed later,
we can add eviction/size caps.
- This PR is part of an effort to make the Snapshot Exploration Test run
successfully end-to-end.