---
title: SAIF.Framework.Common operations and processes
summary: Business and technical operations supported by this library.
status: draft
owner: saif-docgen
last_reviewed: 2026-09-15
library: SAIF.Framework.Common
reference_type: package
---

# SAIF.Framework.Common operations and processes

This page is the operation catalog and cross-operation hub for this library. Each operation's detailed, step-by-step logic lives in its own page under `operations/`.

## Operation catalog
| Operation | Intent | Trigger | Inputs | Outputs | Rules |
| --- | --- | --- | --- | --- | --- |
| [Title-case a string](operations/title-case-a-string.md) | Convert a string to title case using acronym/word-list rules | Caller invokes `someString.ToTitleCase()` | `string inString` | Title-cased `string` | [BR-014](business-rules.md#br-014-title-casing-of-a-null-or-empty-string-is-a-no-op), [BR-015](business-rules.md#br-015-a-word-that-already-contains-both-uppercase-and-lowercase-characters-is-assumed-intentionally-cased), [BR-016](business-rules.md#br-016-words-in-the-approved-acronym-list), [BR-017](<business-rules.md#br-017-minor-words-(is%2C-in%2C-the%2C-an%2C-of%2C-on%2C-and%2C-de)-are-rendered-fully-lowercase>), [BR-018](<business-rules.md#br-018-known-mixed-case-words-(phd%2C-lacombe)-keep-their-exact-registered-casing>), [BR-019](<business-rules.md#br-019-a-numeric-word-with-an-ordinal-suffix-(1st%2C-2nd%2C-3rd%2C-4th)-keeps-the-suffix-lowercase>), [BR-020](business-rules.md#br-020-a-trailing-possessive-'s-is-normalized-to-lowercase-after-casing), [BR-021](business-rules.md#br-021-the-first-character-of-any-title-cased-result-is-always-uppercase%2C-regardless-of-every-other-rule) |
| [Title-case a single word](operations/title-case-a-single-word.md) | Apply acronym/lowercase/mixed-case/ordinal rules to one word | Called once per split word by OP-001 step 4 | `string inWord` | Cased `string` | [BR-015](business-rules.md#br-015-a-word-that-already-contains-both-uppercase-and-lowercase-characters-is-assumed-intentionally-cased), [BR-016](business-rules.md#br-016-words-in-the-approved-acronym-list), [BR-017](<business-rules.md#br-017-minor-words-(is%2C-in%2C-the%2C-an%2C-of%2C-on%2C-and%2C-de)-are-rendered-fully-lowercase>), [BR-018](<business-rules.md#br-018-known-mixed-case-words-(phd%2C-lacombe)-keep-their-exact-registered-casing>), [BR-019](<business-rules.md#br-019-a-numeric-word-with-an-ordinal-suffix-(1st%2C-2nd%2C-3rd%2C-4th)-keeps-the-suffix-lowercase>) |
| [Determine whether two date ranges overlap](operations/determine-whether-two-date-ranges-overlap.md) | Test whether two date ranges intersect | Caller compares two `IDateRange` instances | `range1`, `range2`, `inAllowSameDateOverlap` | `Boolean` | [BR-004](business-rules.md#br-004-whether-shared-endpoints-count-as-an-overlap-is-caller-selectable), [BR-001](business-rules.md#br-001-a-date-range-is-valid-only-when-begindate-is-less-than-or-equal-to-enddate), [BR-002](business-rules.md#br-002-a-range-is-%22open-ended%22-when-begindate-is-datetime.minvalue-or-enddate-is-datetime.maxvalue), [BR-003](business-rules.md#br-003-two-ranges-with-identical-begin-and-end-dates-overlap-only-when-they-are-not-a-single-day) |
| [Determine whether a sequence of date ranges is contiguous](operations/determine-whether-a-sequence-of-date-ranges-is-contiguous.md) | Validate that consecutive date ranges connect with no gaps or overlaps | Caller validates a set of consecutive coverage/effective periods | `ranges`, `option` | `Boolean` | [BR-005](business-rules.md#br-005-a-set-of-fewer-than-two-date-ranges-is-always-contiguous), [BR-006](business-rules.md#br-006-contiguity-is-evaluated-on-ranges-sorted-ascending-by-begindate%2C-and-fails-on-the-first-violating-pair), [BR-007](business-rules.md#br-007-the-default-contiguity-mode-is-startisnextday) |
| [Determine whether a sequence of date ranges overlaps itself](operations/determine-whether-a-sequence-of-date-ranges-overlaps-itself.md) | Detect mutual overlap within a collection of date ranges | Caller validates a set of periods for mutual exclusivity | `ranges`, `option` | `Boolean` | [BR-008](business-rules.md#br-008-a-set-of-fewer-than-two-date-ranges-never-self-overlaps), [BR-002](business-rules.md#br-002-a-range-is-%22open-ended%22-when-begindate-is-datetime.minvalue-or-enddate-is-datetime.maxvalue), [BR-009](business-rules.md#br-009-a-range-whose-begin-and-end-are-both-sentinel-values-is-treated-as-a-null-date-and-ignored-during-collection-overlap-evaluation), [BR-010](business-rules.md#br-010-an-open-ended-predecessor-followed-by-any-range-with-a-real-begin-date-is-always-an-overlap), [BR-011](business-rules.md#br-011-the-default-collection-overlap-mode-is-datesmaynotmatch) |
| [Classify the overlap state of two nullable date ranges](operations/classify-the-overlap-state-of-two-nullable-date-ranges.md) | Classify the directional overlap state between two nullable-end ranges | Caller needs a categorical (not boolean) overlap classification where the end date may be open | `rangeA`, `rangeB` | `SAIF.Common.Enums.DateRangeOverlapResult` | [BR-012](business-rules.md#br-012-the-overlap-of-two-nullable-end-ranges-is-classified-into-exactly-one-of-ten-enumerated-states) |
| [Convert a Julian date string to a DateTime](operations/convert-a-julian-date-string-to-a-datetime.md) | Decode a Julian-format date string into a DateTime | Caller decodes a Julian-formatted date from a fixed-format interchange file* | `julian` (e.g. `25001`, `2025001`), optional `millenium` | `DateTime` | [BR-022](business-rules.md#br-022-a-julian-date-must-contain-at-least-4-digits), [BR-023](business-rules.md#br-023-a-julian-string-that-cannot-be-converted-to-int32-is-invalid), [BR-024](<business-rules.md#br-024-when-no-millennium-is-supplied-(or-it-is-below-1000)%2C-the-millennium-is-derived-from-the-current-system-year's-first-digit-x-1000%2C-and-a-1--or-2-digit-julian-year-is-offset-by-it>), [BR-025](<business-rules.md#br-025-leap-year-status-is-determined-using-the-julian-calendar-(system.globalization.juliancalendar)%2C-not-the-gregorian-calendar>), [BR-026](business-rules.md#br-026-the-day-of-year-portion-of-a-julian-date-must-not-exceed-365%2C-or-366-in-a-leap-year) |
| [Resolve the year and leap-year flag from a Julian string](operations/resolve-the-year-and-leap-year-flag-from-a-julian-string.md) | Resolve a 4-digit calendar year and leap-year flag from a Julian string | Called by OP-007 step 3, or directly by callers | `julian`, `millenium` | `int` year, `out bool isLeapYear` | [BR-024](<business-rules.md#br-024-when-no-millennium-is-supplied-(or-it-is-below-1000)%2C-the-millennium-is-derived-from-the-current-system-year's-first-digit-x-1000%2C-and-a-1--or-2-digit-julian-year-is-offset-by-it>), [BR-025](<business-rules.md#br-025-leap-year-status-is-determined-using-the-julian-calendar-(system.globalization.juliancalendar)%2C-not-the-gregorian-calendar>) |
| [Encode a DateTime as a Julian string](operations/encode-a-datetime-as-a-julian-string.md) | Encode a DateTime as a Julian-format string | Caller produces a Julian-formatted date for file interchange* | `date`, `yearLength` | `string` | [BR-027](<business-rules.md#br-027-a-julian-string-is-encoded-as-the-year-(2-digit-by-default%2C-4-digit-on-request)-followed-by-the-zero-padded-3-digit-day-of-year>) |
| [Build a DateTime from EDI date/time strings](operations/build-a-datetime-from-edi-date-time-strings.md) | Parse a DateTime from EDI-style date and time strings | Caller parses a date/time from a fixed-format interchange file* | `date` (`yyyymmdd` or `yymmdd`), `time` (`hh24miss`) | `DateTime` | [BR-028](business-rules.md#br-028-in-edi-date-parsing%2C-a-6-character-date-is-a-2-digit-year-and-is-always-treated-as-21st-century), [BR-029](business-rules.md#br-029-a-short-edi-time-string-is-right-padded-with-zeros-to-hh24miss) |
| [Split an EDI date-range string](operations/split-an-edi-date-range-string.md) | Split a delimited EDI date-range token into start and end DateTimes | Caller parses a `yyyymmdd-yyyymmdd` token | `dateString` | `out startDate`, `out endDate` | [BR-030](business-rules.md#br-030-edi-file-date-strings-use-the-yyyymmdd-format-and-the---separator-between-a-start-and-an-end-date), [BR-028](business-rules.md#br-028-in-edi-date-parsing%2C-a-6-character-date-is-a-2-digit-year-and-is-always-treated-as-21st-century), [BR-029](business-rules.md#br-029-a-short-edi-time-string-is-right-padded-with-zeros-to-hh24miss) |
| [Format one or two dates as an EDI file date string](operations/format-one-or-two-dates-as-an-edi-file-date-string.md) | Format a start date, and optional end date, into an EDI file date string | Caller names or stamps an interchange file* | `startDate`, optional `endDate` | `String` (`yyyymmdd` or `yyyymmdd-yyyymmdd`) | [BR-030](business-rules.md#br-030-edi-file-date-strings-use-the-yyyymmdd-format-and-the---separator-between-a-start-and-an-end-date) |
| [Parse a delimited date-range string into a DateRange](operations/parse-a-delimited-date-range-string-into-a-daterange.md) | Parse a delimited string back into a DateRange, defaulting on any failure | Caller round-trips a `DateRange.ToString()` value | `date`, `delimiter` | `DateRange` | [BR-031](business-rules.md#br-031-parsing-a-malformed-date-range-string-never-throws-%E2%80%94-it-silently-yields-the-unbounded-default-range), [BR-032](business-rules.md#br-032-a-daterange-constructed-with-no-end-date-is-unbounded-forward) |
| [Construct a DateRange](operations/construct-a-daterange.md) | Construct a DateRange from various inputs with sentinel defaulting | Caller creates a range | none / `IDateRange` / `DateTime` / `DateTime, DateTime` | `DateRange` instance | [BR-002](business-rules.md#br-002-a-range-is-%22open-ended%22-when-begindate-is-datetime.minvalue-or-enddate-is-datetime.maxvalue), [BR-032](business-rules.md#br-032-a-daterange-constructed-with-no-end-date-is-unbounded-forward), [BR-033](business-rules.md#br-033-an-end-date-supplied-as-datetime.minvalue-is-reinterpreted-as-no-end-date-and-coerced-to-datetime.maxvalue) |
| [Compare two date ranges for equality](operations/compare-two-date-ranges-for-equality.md) | Compare two date ranges for structural equality | Caller compares ranges | Two ranges | `Boolean` / `int` | [BR-034](business-rules.md#br-034-two-date-ranges-are-equal-if-and-only-if-both-begindate-and-enddate-match) |
| [Filter a collection of ranges as of a date](operations/filter-a-collection-of-ranges-as-of-a-date.md) | Filter a collection of ranged records to those effective on a date | Caller selects the version(s) of a record effective on a given date | `ranges`, `date`, `rangeOption` | `IEnumerable<T>` (deferred) | [BR-035](business-rules.md#br-035-as-of-filtering-of-a-range-collection-selects-ranges-containing-the-supplied-date-under-the-chosen-inclusivity-mode), [BR-036](business-rules.md#br-036-the-default-date-in-range-inclusivity-is-firstonly-%E2%80%94-begin-date-inclusive%2C-end-date-exclusive), [BR-037](business-rules.md#br-037-a-null-date-is-never-in-range) |
| [Enforce a method precondition](operations/enforce-a-method-precondition.md) | Fail fast on invalid arguments or state | Called at the top of a public method by consumers and by intra-library callers (`IEnumerableExtensions`, `DictionaryExtensions`, `UriExtensions`) | The parameter value, the parameter name, or a boolean / `Func<bool>` condition | The validated value (fluent) or `void`; throws on violation | [BR-038](business-rules.md#br-038-a-guard-call-must-itself-supply-a-non-blank-parameter-name), [BR-039](business-rules.md#br-039-a-guarded-argument-must-not-be-null), [BR-040](business-rules.md#br-040-a-guarded-string-argument-must-not-be-empty), [BR-041](business-rules.md#br-041-a-guarded-string-argument-must-not-be-all-white-space), [BR-042](<business-rules.md#br-042-a-guarded-state-value-(as-opposed-to-an-argument)-must-not-be-null>), [BR-043](business-rules.md#br-043-a-caller-supplied-boolean-or-func%3Cbool%3E-condition-must-evaluate-true%2C-or-the-caller-specified-exception-type%2Finstance-is-thrown) |
| [Throw-if-null with a custom exception type](operations/throw-if-null-with-a-custom-exception-type.md) | Throw a caller-chosen exception type when a value is null | Called by `Enforce.NotNull<T>` and by consumers directly | `instance`, `message` | `instance` (non-null) or throws `TException` | [BR-042](<business-rules.md#br-042-a-guarded-state-value-(as-opposed-to-an-argument)-must-not-be-null>) |
| [Raise a validation failure](operations/raise-a-validation-failure.md) | Build and throw a validation exception carrying structured failure detail | Caller detects invalid input or state and needs to surface property-level errors | none / `IValidationResult` / `errorMessage` / `propertyName + errorMessage` | A throwable exception carrying an `IValidationResult` | [BR-044](business-rules.md#br-044-a-saifvalidationexception-always-exposes-a-non-null-ivalidationresult), [BR-045](business-rules.md#br-045-a-validation-exception-built-from-a-bare-message-carries-a-single-failure-whose-propertyname-is-the-empty-string%2C-and-the-two-argument-overload-binds-the-failure-to-a-named-property), [BR-046](business-rules.md#br-046-a-validationresult-always-exposes-non-null-errors-and-messages-collections%2C-even-when-constructed-from-a-null-failure-sequence), [BR-047](business-rules.md#br-047-a-validation-result-is-valid-if-and-only-if-it-holds-no-errors) |
| [Signal a view/context failure](operations/signal-a-view-context-failure.md) | Signal a presentation-layer routing failure with a target view and optional model | A consuming presentation layer detects a missing, invalid, unauthorized-for-user, or disabled context* | none / `targetView` / `model` | An exception carrying `TargetView` and optionally `Model` | [BR-048](business-rules.md#br-048-each-context-failure-condition-maps-to-a-fixed-default-target-view-name) |
| [Format an exception chain for display](operations/format-an-exception-chain-for-display.md) | Format an exception and its inner-exception chain for display | Caller logs or displays a caught exception | `instance`, `innerToOuter` | Multi-section `string` | [BR-049](business-rules.md#br-049-formatting-a-null-exception-yields-an-empty-string-rather-than-throwing), [BR-050](<business-rules.md#br-050-exception-chains-are-reported-innermost-(root-cause)-first-by-default>), [BR-051](<business-rules.md#br-051-the-formatted-exception-report-renders-a-summary-block-(type%2C-message%2C-source)-for-every-exception>) |
| [Format an exception for the error log](operations/format-an-exception-for-the-error-log.md) | Format an exception into a sentinel-delimited error-log entry | Caller writes a caught exception to an error-log store | `instance` | `string` — short message, sentinel, then full detail | [BR-049](business-rules.md#br-049-formatting-a-null-exception-yields-an-empty-string-rather-than-throwing), [BR-050](<business-rules.md#br-050-exception-chains-are-reported-innermost-(root-cause)-first-by-default>), [BR-052](business-rules.md#br-052-an-error-log-entry-begins-with-the-innermost-exception's-message%2C-followed-by-the-sentinel-%40%23elog%23%40%2C-followed-by-the-full-report%2C-so-a-log-reader-can-split-summary-from-detail), [BR-051](<business-rules.md#br-051-the-formatted-exception-report-renders-a-summary-block-(type%2C-message%2C-source)-for-every-exception>) |
| [Locate a byte signature within a buffer](operations/locate-a-byte-signature-within-a-buffer.md) | Locate the first occurrence of a byte signature within a buffer | Caller looks for a file-format signature (e.g. the `PDF_FILE_HEADER` constant `"%PDF-"`) inside a binary payload | `input`, `searchFor` (or an ASCII `string`), `start`, `maxBytesToSearch` | `int` index of the first match, or `-1` | [BR-053](business-rules.md#br-053-a-string-search-pattern-over-a-binary-payload-is-encoded-as-ascii%2C-not-unicode), [BR-054](business-rules.md#br-054-a-byte-search-returns--1-when-the-haystack-is-empty%2C-the-needle-is-empty%2C-the-needle-is-longer-than-the-haystack%2C-or-the-start-offset-leaves-too-few-bytes), [BR-055](business-rules.md#br-055-a-byte-search-may-be-capped) |
| [Strip a leading header from a byte payload](operations/strip-a-leading-header-from-a-byte-payload.md) | Strip a leading transport header so a payload starts at its own signature | Caller has a document payload prefixed with transport bytes and needs it to start at its own signature (e.g. "%PDF-") | `input`, `searchFor`, `maxBytesToSearch` | Trimmed `Byte[]`, `out bool found` | [BR-053](business-rules.md#br-053-a-string-search-pattern-over-a-binary-payload-is-encoded-as-ascii%2C-not-unicode), [BR-055](business-rules.md#br-055-a-byte-search-may-be-capped), [BR-056](business-rules.md#br-056-a-header-is-stripped-only-when-the-signature-is-found-at-a-position-greater-than-zero) |
| [Hash a byte payload](operations/hash-a-byte-payload.md) | Compute an MD5 hash of a byte payload | Caller needs a content fingerprint | `input` | `Byte[]` MD5 digest | [BR-057](business-rules.md#br-057-disposable-cryptographic-and-stream-resources-are-created-inside-using-blocks-so-they-are-released-deterministically) |
| [Serialize / deserialize an object](operations/serialize-deserialize-an-object.md) | Round-trip an object graph to/from JSON or XML, files or strings | Caller persists or rehydrates an object graph | A file path, a serialized string, or the instance to serialize | `T`, `string`, or a written file | [BR-058](<business-rules.md#br-058-json-serialization-uses-javascriptserializer-(system.web.extensions)-and-xml-serialization-uses-xmlserializer%3B-serializable-targets-must-be-reference-types-(where-t-%3A-class)-except-for-savetojson%3Ct%3E%2C-which-is-unconstrained>), [BR-059](business-rules.md#br-059-xml-deserialization-that-produces-an-incompatible-type-yields-null-rather-than-throwing%2C-because-the-result-is-cast-with-as-t), [BR-060](business-rules.md#br-060-serialized-output-written-to-a-file-is-explicitly-flushed-before-the-writer-is-disposed) |
| [Set a property value by strongly-typed expression](operations/set-a-property-value-by-strongly-typed-expression.md) | Resolve and set a property from a strongly-typed lambda expression | Caller assigns a property — including one with a non-public setter — selected by lambda rather than by name string | `Expression<Func<TTarget,TProperty>>` or a resolved `PropertyInfo`, plus the value | `PropertyInfo`, or `void` (property mutated) | [BR-061](business-rules.md#br-061-a-property-selector-lambda-must-select-a-member%2C-not-invoke-a-method), [BR-062](business-rules.md#br-062-a-property-selector-lambda-must-select-a-property%2C-not-a-field), [BR-063](business-rules.md#br-063-the-selected-property-must-be-declared-on-the-target-type-or-one-of-its-base-types), [BR-064](business-rules.md#br-064-a-property-may-be-set-only-when-it-has-a-setter%2C-but-that-setter-may-be-non-public---reflective-assignment-deliberately-bypasses-accessibility) |
| [Generate the next sequence value](operations/generate-the-next-sequence-value.md) | Generate and manage an in-memory monotonic sequence counter | Caller needs a monotonically increasing in-memory counter (e.g. record numbering in a generated file)* | Optional `startValue` (default 0) | `int` / `long` | [BR-065](business-rules.md#br-065-a-sequence-generator-starts-at-the-caller-supplied-start-value%2C-defaulting-to-0%2C-and-may-be-repositioned-at-any-time-via-setvalue), [BR-066](business-rules.md#br-066-nextvalue-is-pre-increment), [BR-067](<business-rules.md#br-067-reset()-returns-the-counter-to-literal-zero%2C-not-to-the-original-start-value>) |
| [Derive MVC route parts from a request URI](operations/derive-mvc-route-parts-from-a-request-uri.md) | Derive ASP.NET MVC-style route segments from a request URI | A consuming web layer needs the application, page, or controller/action name from the current request URL* | `Uri` | `string` | [BR-068](business-rules.md#br-068-the-application-name-is-url-segment-1), [BR-069](business-rules.md#br-069-the-page-name-is-url-segment-2%2C-defaulting-to-%22index%22-when-absent), [BR-039](business-rules.md#br-039-a-guarded-argument-must-not-be-null), [BR-070](business-rules.md#br-070-the-default-controller%2Faction-route-is-home%2Findex) |
| [Split a Windows identity name](operations/split-a-windows-identity-name.md) | Split a DOMAIN\\user identity name into domain and account parts | Caller has an authenticated `IIdentity` and needs the bare account name or the domain | `IIdentity` | `string` | [BR-071](business-rules.md#br-071-a-windows-identity-name-is-interpreted-as-domain%5Cuser) |
| [Interpret a string as a boolean and render booleans as flags](operations/interpret-a-string-as-a-boolean-and-render-booleans-as-flags.md) | Convert between truthy flag strings and boolean values | Caller converts between flag strings (from files, code tables, or UI) and `bool` | `string` / `bool` | `bool` / `string` | [BR-072](business-rules.md#br-072-a-string-is-truthy-only-when-it-uppercases-to-one-of-y%2C-yes%2C-true%2C-1%2C-or-on), [BR-073](business-rules.md#br-073-booleans-render-to-flags-as-y%2Fn%2C-yes%2Fno%2C-or-on%2Foff-depending-on-the-method-chosen) |
| [Normalize text for downstream systems](operations/normalize-text-for-downstream-systems.md) | Fold Unicode punctuation to ASCII and filter text by character class | Caller prepares text for a fixed-format file, a legacy system, or display* | `string` | `string` | [BR-074](business-rules.md#br-074-non-ascii-typographic-characters-are-folded-to-ascii-equivalents-before-text-leaves-the-system), [BR-075](<business-rules.md#br-075-character-class-filters-retain-(or%2C-for-the-remove-variants%2C-exclude)-exactly-the-characters-matching-the-corresponding-char.is-predicate%2C-and-treat-a-null-input-as-an-empty-string>) |
| [Slice, truncate, and convert scalar values](operations/slice-truncate-and-convert-scalar-values.md) | Slice, truncate, and convert scalar string/decimal values | Caller formats a value for a fixed-width field or performs a numeric test* | `string` / `decimal` | `string` / `bool` / `decimal` / `int` | [BR-076](<business-rules.md#br-076-truncate-is-length-safe-(clamps-and-tolerates-null)>), [BR-077](business-rules.md#br-077-a-string-contains-only-numeric-characters-only-when-it-is-non-empty-and-contains-no-non-digit-character-at-all), [BR-078](business-rules.md#br-078-a-null-or-whitespace-decimal-string-converts-to-zero-rather-than-raising-an-error), [BR-079](business-rules.md#br-079-the-isnumeric-test-is-32-bit-integer-bounded), [BR-080](business-rules.md#br-080-a-decimal's-precision-is-its-declared-scale%2C-read-from-the-scale-byte-extracted-via-bitconverter-and-decimal.getbits%2C-with-trailing-zeros-counted), [BR-081](business-rules.md#br-081-reducing-a-decimal's-precision-truncates) |
| [Merge or upsert dictionary entries](operations/merge-or-upsert-dictionary-entries.md) | Merge, upsert, or safely read dictionary entries | Caller combines configuration/lookup dictionaries or reads with a fallback | Target dictionary, plus source dictionary / key / value / default | `void` / `TValue` | [BR-082](business-rules.md#br-082-merging-dictionaries-overwrites-existing-keys-by-default), [BR-039](business-rules.md#br-039-a-guarded-argument-must-not-be-null), [BR-083](business-rules.md#br-083-a-dictionary-read-with-a-supplied-default-never-throws-on-a-missing-key), [BR-084](business-rules.md#br-084-an-upsert-overwrites-an-existing-key-or-adds-a-new-one-and-always-returns-the-supplied-value) |
| [Join or reduce sequences](operations/join-or-reduce-sequences.md) | Fill LINQ gaps: iteration, joining, string-joining, and keyed distinct | Caller performs a collection operation LINQ does not provide natively | One or two sequences plus key selectors/projections | `void` / `string` / `bool` / `IEnumerable<T>` | [BR-039](business-rules.md#br-039-a-guarded-argument-must-not-be-null), [BR-085](business-rules.md#br-085-joining-a-sequence-to-a-string-requires-a-non-null-separator), [BR-086](business-rules.md#br-086-taking-n-items-from-the-end-of-a-sequence-preserves-their-original-relative-order), [BR-087](business-rules.md#br-087-key-based-distinct-retains-the-first-occurrence-of-each-key), [BR-088](business-rules.md#br-088-outer-joins-compare-keys-with-the-caller's-iequalitycomparer%3Ctkey%3E-when-supplied%2C-otherwise-equalitycomparer%3Ctkey%3E.default%2C-and-substitute-caller-supplied-defaults-for-unmatched-sides) |
| [Convert a stream to a byte array](operations/convert-a-stream-to-a-byte-array.md) | Materialize a stream into an in-memory byte array | Caller materialises a read/uploaded stream for hashing, storage, or header stripping | `Stream` | `byte[]` | [BR-057](business-rules.md#br-057-disposable-cryptographic-and-stream-resources-are-created-inside-using-blocks-so-they-are-released-deterministically), [BR-089](business-rules.md#br-089-converting-a-stream-to-a-byte-array-copies-from-the-stream's-current-position---the-caller-owns-rewinding---and-does-not-dispose-the-source-stream) |
| [Enumerate the properties of a type](operations/enumerate-the-properties-of-a-type.md) | Enumerate the public properties of a type via reflection | Caller needs the public property set of a type for mapping or binding* | Generic type parameter `TTarget` | `PropertyInfo[]` | [BR-090](business-rules.md#br-090-property-reflection-returns-only-the-type's-public-properties) |
| [Write to the console/application log (contract only)](operations/write-to-the-console-application-log-contract-only.md) | Define (without implementing) a console/file logging contract | A consuming console/batch application writes progress, errors, or exception detail to one or more log files* | `message`, `logId` or `LogType`, `filePath`, `Exception` | Log output (implementation-defined) | [BR-091](business-rules.md#br-091-a-log-file-must-be-registered-against-a-log-id-before-it-can-be-written-to%2C-and-log-writers-are-retrieved-by-that-id), [BR-092](<business-rules.md#br-092-a-console-application-may-address-at-most-five-conventional-log-files-(file0-file4)-plus-a-custom-category>), [BR-093](business-rules.md#br-093-writetoapplog-appends-a-line-break-by-default-when-addressed-by-string-log-id%2C-but-not-by-default-when-addressed-by-logtype), [BR-094](business-rules.md#br-094-log-services-and-log-writers-are-disposable-resources-and-must-be-released-by-the-consuming-application) |

<!--
  STRICT: The Operation name in each catalog row MUST link to that operation's
  detail page at operations/<operation-slug>.md (never an on-page anchor). The
  step-by-step logic, per-step rule citations, and embedded dependency logic
  live in the per-operation document (see templates/operation-document.md), not
  on this hub page. The Rules column lists the primary business rules the
  operation enforces, each as a link to business-rules.md.
-->

## Process flow diagram
The following diagram visualizes the primary operational flow across this library's functional groups.

```mermaid
flowchart TD
	Start([Caller invokes a static/extension method]) --> Guard{Guard/precondition check?}
	Guard -->|Fails| Throw(["Throws ArgumentException /<br>ArgumentNullException /<br>custom exception"])
	Guard -->|Passes| Domain{Which functional group?}
	Domain -->|Date/date-range| DateOps["Compute validity, overlap, contiguity,<br>or EDI/Julian conversion"]
	Domain -->|Text| TextOps["Normalize, case, filter,<br>or convert scalar value"]
	Domain -->|Validation| ValidationOps["Accumulate failures into<br>ValidationResult<br>or throw SaifValidationException"]
	Domain -->|Sequence/collection| CollectionOps["Advance sequence, merge dictionary,<br>or join/filter sequence"]
	Domain -->|Serialization/bytes| SerializationOps["Serialize, deserialize, hash,<br>or search byte payload"]
	Domain -->|Logging contract| LoggingOps["Write through IConsoleLogService<br>/ ILogWriter contract"]
	DateOps --> Return([Return computed result])
	TextOps --> Return
	ValidationOps --> Return
	CollectionOps --> Return
	SerializationOps --> Return
	LoggingOps --> Return
```

## Failure and edge-case behavior
- Guard failures (`Enforce`, `GenericExtensions.IfNullThrow`) throw immediately rather than returning a sentinel value, so callers must catch or pre-validate.
- Several operations are explicitly designed to be non-throwing on bad input (`DateRange.Parse`, `StringExtensions.Truncate`, `StringExtensions.ConvertToDecimalOrZero` for whitespace), trading strictness for caller convenience — see each operation's Steps for the exact behavior.
- `ILogWriter`/`IConsoleLogService` are contracts only; this library defines no logging implementation, so failure behavior for logging operations is determined entirely by the consuming implementation.*

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Business rules](business-rules.md)
- [API reference](api/index.md)

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

## Sources
- `Common/SAIF.Common/Utility/TitleCase.cs`
- `Common/SAIF.Common/Classes/Julian.cs`
- `Common/SAIF.Common/Classes/DateMaker.cs`
- `Common/SAIF.Common/Classes/DateRange.cs`
- `Common/SAIF.Common/Classes/NullableDateRange.cs`
- `Common/SAIF.Common/Extensions/DateRangeExtensions.cs`
- `Common/SAIF.Common/Extensions/IComparableExtensions.cs`
- `Common/SAIF.Common/Classes/Enforce.cs`
- `Common/SAIF.Common/Extensions/GenericExtensions.cs`
- `Common/SAIF.Common/Classes/SaifValidationException.cs`
- `Common/SAIF.Common/Classes/ValidationResult.cs`
- `Common/SAIF.Common/Classes/ContextException.cs`
- `Common/SAIF.Common/Extensions/ExceptionExtensions.cs`
- `Common/SAIF.Common/Extensions/ByteExtensions.cs`
- `Common/SAIF.Common/Extensions/FileExtensions.cs`
- `Common/SAIF.Common/Classes/Serializer.cs`
- `Common/SAIF.Common/Extensions/DotNet40Extensions.cs`
- `Common/SAIF.Common/Extensions/ExpressionExtensions.cs`
- `Common/SAIF.Common/Classes/SequenceGenerator.cs`
- `Common/SAIF.Common/Classes/Sequence.cs`
- `Common/SAIF.Common/Classes/LongSequence.cs`
- `Common/SAIF.Common/Extensions/UriExtensions.cs`
- `Common/SAIF.Common/Extensions/SecurityPrincipalExtensions.cs`
- `Common/SAIF.Common/Extensions/StringExtensions.cs`
- `Common/SAIF.Common/Extensions/BooleanExtensions.cs`
- `Common/SAIF.Common/Utility/StringUtilities.cs`
- `Common/SAIF.Common/Extensions/NumericExtensions.cs`
- `Common/SAIF.Common/Extensions/DictionaryExtensions.cs`
- `Common/SAIF.Common/Extensions/IEnumerableExtensions.cs`
- `Common/SAIF.Common/Classes/Reflector.cs`
- `Common/SAIF.Common/Interfaces/IConsoleLogService.cs`
- `Common/SAIF.Common/Interfaces/ILogWriter.cs`
- `Common/SAIF.Common/Enums/LogType.cs`
