---
title: "Apply a unified diff patch in C#"
url: https://recursiveai.org/onediff/cookbook/apply-unified-patch/
description: "Apply a unified diff patch to a C# string, check rejected hunks, and distinguish conflicts from success. Includes exact-context and reverse examples."
version: "1.0.1"
---

# Apply a unified diff patch in C#

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

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneDiff) · [GitHub](https://github.com/recursive-ai-oss/onediff-csharp) · [Docs](https://recursiveai.org/onediff/docs/) · [Download example (.zip)](https://recursiveai.org/onediff/cookbook/apply-unified-patch/apply-unified-patch.zip)

Apply line changes from a unified patch and check for rejected hunks.

```shell
dotnet add package RecursiveAI.OneDiff
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneDiff;

string oldText = "red\ngreen\n";
string patch = Diff.Unified(
    oldText, "red\nlime\n");
var result = Diff.ApplyUnified(
    oldText, patch);
if (!result.Success)
    throw new Exception("Rejected hunks");
Console.Write(result.Text);
```

```text
red
lime
```

Unzip [the example](https://recursiveai.org/onediff/cookbook/apply-unified-patch/apply-unified-patch.zip), then `dotnet run`.

## The full program

The program generates a patch from two literal strings, prints it, and applies it. Conflicts return rejected hunks rather than throwing; malformed patch text throws `PatchFormatException`.

```csharp
using OneDiff;

string oldText = "red\ngreen\nblue\n";
string newText = "red\nlime\nblue\n";
string patch = Diff.Unified(oldText, newText);
Console.Write(patch);
var result = Diff.ApplyUnified(oldText, patch);
Console.WriteLine($"Success: {result.Success}");
foreach (var hunk in result.Hunks)
    Console.WriteLine($"{hunk.Outcome}: {hunk.Reason}");
if (result.Success)
    Console.Write(result.Text);
else
    Console.Write(result.Rejected);
```

`dotnet run` prints:

```text
--- a
+++ b
@@ -1,3 +1,3 @@
 red
-green
+lime
 blue
Success: True
Applied: None
red
lime
blue
```

`Applied: None` means the hunk’s `Outcome` is `Applied` and its rejection `Reason` is `None`: it applied without a rejection.

## Variations

**Apply a patch file without saving a partial update.** The download includes `target.txt` and `change.patch` with these contents:

`target.txt`:

```text
red
green
blue
```

`change.patch`:

```diff
--- a
+++ b
@@ -1,3 +1,3 @@
 red
-green
+lime
 blue
```

Run this replacement program in the example folder. It overwrites `target.txt` only if every hunk applies.

```csharp
using OneDiff;

string targetPath = "target.txt";
string patch = File.ReadAllText("change.patch");
var result = Diff.ApplyUnified(
    File.ReadAllText(targetPath), patch);
if (result.Success)
{
    File.WriteAllText(targetPath, result.Text);
    Console.WriteLine("Updated target.txt");
}
else
{
    Console.WriteLine("Rejected; target.txt unchanged");
    Console.Write(result.Rejected);
}
```

```text
Updated target.txt
```

The saved file contains `red`, `lime` and `blue`, each on its own line. If any hunk is rejected, the program leaves the target file untouched and prints the rejected patch.

**Require exact context and the original position.**

```csharp
using OneDiff;
using OneDiff.Patching;

string patch = Diff.Unified("a\nb\n", "a\nc\n");
var result = Diff.ApplyUnified("x\nb\n", patch,
    new UnifiedApplyOptions { MaxFuzz = 0, MaxOffset = 0 });
Console.WriteLine(result.Success);
foreach (var hunk in result.Hunks)
    Console.WriteLine(hunk.Reason);
```

```text
False
ContextMismatch
```

**Detect a patch that looks already applied.**

```csharp
using OneDiff;

string patch = Diff.Unified("old\n", "new\n");
var result = Diff.ApplyUnified("new\n", patch);
Console.WriteLine(result.Hunks[0].Reason);
```

```text
AlreadyApplied
```

**Reverse the patch.**

```csharp
using OneDiff;
using OneDiff.Patching;

string patch = Diff.Unified("old\n", "new\n");
var result = Diff.ApplyUnified("new\n", patch,
    new UnifiedApplyOptions { Reverse = true });
Console.WriteLine(result.Success);
Console.Write(result.Text);
```

```text
True
old
```

## Related recipes

- [Compare two strings word by word](https://recursiveai.org/onediff/cookbook/compare-text/)
- [Write a side-by-side HTML diff](https://recursiveai.org/onediff/cookbook/side-by-side-html-diff/)

## The short answer

Call `Diff.ApplyUnified(text, patchText)` and inspect `Success` before using `Text` as the complete updated file. The result includes an outcome for each hunk and a patch containing the rejected hunks. A hunk is a group of changed lines together with context. A parseable patch is not necessarily an applicable patch, so successful parsing alone does not mean the update succeeded.

## How do I apply a unified diff patch in C#?

The example uses `Diff.Unified` only to supply a known patch. In an application, the second argument can instead be unified patch text received from a file or another process. The one-liner accepts a single-file patch; passing a patch containing more than one file throws `ArgumentException`. It operates on the text you supply, not on paths from the patch header. You decide which file to read and where to write the result.

Matching is line-by-line and exact for the lines being matched. The default applier first looks at the position named by the hunk, then searches nearby positions. If that fails, it can ignore outer context lines, up to two levels of fuzz by default. Fuzz is a relaxation of surrounding context, not arbitrary approximate matching of the changed text. The [patch documentation](https://recursiveai.org/onediff/docs/patches/) specifies placement and edge-of-file rules.

For a stricter update, set `MaxFuzz = 0` to require all context lines to match. Set `MaxOffset = 0` as well when the original line position must match. The exact-context variation changes a context line from `a` to `x`, so it returns `ContextMismatch`. Choose this policy deliberately: forbidding offsets also rejects a patch whose otherwise matching block simply moved down the file.

`Hunks` reports whether each hunk was applied normally, with an offset, with fuzz, or rejected. A rejected hunk has no matched line. `ContextMismatch` means its old lines could not be found under the allowed placement rules. `AlreadyApplied` means its new lines were found where its old lines were not; the patch may already have been applied or may look reversed. It is a diagnostic, not permission to mark the whole operation successful.

Partial application is possible. Applied hunks change `Text`; rejected ones leave their locations unchanged. The full program prints the rejected patch on failure instead of printing the partially updated text as though everything worked. For a file-writing workflow, keep the original file until every hunk has succeeded, or explicitly present the partial result for review. `Rejected` is null only when there are no rejects.

Line endings matter. Patches split text at line-feed characters and retain terminators; a final line without a newline differs from one with a newline. Avoid normalizing line endings between generating and applying a patch unless that transformation is part of your input policy. Reversing a patch swaps its insertions and deletions, as the final variation shows; it still needs matching input.

Malformed patch syntax is a separate error path. The parsers expose typed parse errors or throw `PatchFormatException`; ordinary application conflicts are returned in the result. Binary and rename-only patch sections are not supported. This recipe updates text content, not repository metadata or binary files.
