Recursive AI

Compare two strings word by word in C#

OneDiff is a free, open-source diff library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Find added and removed words in two strings.

Terminal
dotnet add package RecursiveAI.OneDiff

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneDiff;
 
var diff = Diff.Words(
    "Pay within 30 days.",
    "Pay within 60 days.");
Console.WriteLine(
    Diff.ToMarkdownInline(diff));
Text
Pay within <del>30</del><ins>60</ins> days.

Unzip the example, then dotnet run.

The full program

The inputs are the two strings shown here; no files or document adapters are needed. The full program also lists the edit runs. Null text arguments throw ArgumentNullException.

C#
using OneDiff;
 
string oldText = "Pay within 30 days.";
string newText = "Pay within 60 days.";
var diff = Diff.Words(oldText, newText);
Console.WriteLine(Diff.ToMarkdownInline(diff));
foreach (var piece in diff.Pieces)
    Console.WriteLine($"{piece.Kind}: [{piece.Text}]");

dotnet run prints:

Text
Pay within <del>30</del><ins>60</ins> days.
Equal: [Pay within ]
Delete: [30]
Insert: [60]
Equal: [ days.]

Variations

Ignore case, but not other changes. This is a complete replacement program:

C#
using OneDiff;
using OneDiff.Tokenization;
 
var diff = Diff.WordsUsing(
    "Pay now", "PAY now", TokenComparer.IgnoreCase);
Console.WriteLine(
    Diff.ToMarkdownInline(diff));
Text
PAY now

Compare characters instead of words.

C#
using OneDiff;
 
var diff = Diff.Characters("cat", "cut");
Console.WriteLine(
    Diff.ToMarkdownInline(diff));
Text
c<del>a</del><ins>u</ins>t

The short answer

Use Diff.Words(oldText, newText) to get a word-level TextDiff, then pass it to Diff.ToMarkdownInline for a marked-up string. Deleted text is inside <del> and inserted text inside <ins>. In the example, only the payment period changes: 30 is removed and 60 is inserted. The core package is sufficient; this does not read Word or PDF documents.

How do I compare two strings and get the difference in C#?

An equality test answers whether two strings match under a chosen rule. A diff answers where they differ and carries the old and new content. Here, the return value is a TextDiff, not a Boolean or a string ordering result.

For a human-readable result, render inline Markdown as in the snippet at the top. For application logic, walk diff.Pieces as the full program does. Each run has a Kind: Equal, Delete or Insert. Its Text is the run’s content. OldLine and NewLine are one-based source line numbers; an insertion has no old line and a deletion has no new line. These are line positions, not character offsets. Keep the brackets in the sample’s output while debugging so spaces remain visible.

One replacement can therefore appear as a deletion followed by an insertion rather than a single “replace” object. Don’t count those runs as two independent business changes unless that is what your application intends. Word-level text edits say what changed in the string, not what the change means in a contract.

The core’s tokenizer treats a whitespace run as a token, keeps an apostrophe between letters within a word, and keeps a decimal separator between digits within a number. It is script-aware but is not Unicode UAX #29 word segmentation. For details and the original API rules, see the text output documentation and public tokenizer source.

How do I compare strings ignoring case in C#?

Use Diff.WordsUsing with TokenComparer.IgnoreCase, as in the variation. The default word diff does not silently ignore case. The comparer changes what counts as equal while the result still refers to the original tokens. The case-insensitive example renders the new text, PAY now, without insertion or deletion tags; it does not lowercase your source string.

Keep normalization explicit. If case matters to a reviewer, use the default comparison. If your application deliberately treats capitalization as irrelevant, choose the comparer and document that decision next to the code. Separate whitespace comparers are available; ignoring case does not automatically ignore spaces, punctuation or all other typography. The comparer API source lists the supported choices.

The inline renderer escapes ampersands and less-than signs, but leaves other Markdown as written. That makes it useful in a Markdown workflow, not a general-purpose HTML sanitization step. Render it with the content policy appropriate to your application, or use the dedicated HTML report recipe for a standalone comparison page.

All onediff recipes