C# İle Özel Bir Sürüm Geçmişi Karşılaştırma Aracı Nasıl Yapılır?

C# İle Özel Bir Sürüm Geçmişi Karşılaştırma Aracı Nasıl Yapılır?
C# İle Özel Bir Sürüm Geçmişi Karşılaştırma Aracı Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu projeyi hayata geçirmek için modern bir geliştirme ortamına ihtiyacınız vardır. .NET 9 SDK'nın yüklü olması, C# 13 özelliklerini kullanabilmemiz adına önemlidir. Geliştirme ortamı olarak Visual Studio 2026 veya VS Code kullanabilirsiniz.

  • .NET 9.0 SDK veya daha güncel sürümü.
  • Temel düzeyde LINQ (Language Integrated Query) bilgisi.
  • Dosya I/O (Giriş/Çıkış) işlemleri hakkında temel tecrübe.
  • Koleksiyon yapıları (List, Dictionary, HashSet) hakkında bilgi.

Adım 1: Karşılaştırma Mantığını Belirleme (Algoritmik Yaklaşım)

İki sürüm arasındaki farkı bulmanın en verimli yolu, metinleri satırlara bölmek ve bu satırları birer "hash" değerine dönüştürerek karşılaştırmaktır. Wagner-Fischer algoritması veya daha yaygın kullanılan "Myers Diff" algoritması bu konuda standarttır. Biz, anlaşılabilir olması adına satır bazlı karşılaştırma yapan bir yapı kuracağız.

public record DiffResult(int LineNumber, string Content, ChangeType Type);

public enum ChangeType { Unchanged, Added, Removed }

Yukarıdaki kod bloğunda, karşılaştırma sonucunu temsil eden veri yapımızı tanımladık. record kullanımı, verinin değiştirilemez (immutable) olmasını sağlar ve performans açısından avantajlıdır.

Adım 2: Metinleri Satırlara Ayırma ve Temizleme

Karşılaştırma yapmadan önce veriyi normalize etmeliyiz. Boşluklar veya satır sonu karakterleri (CRLF vs LF) sonuçları yanıltabilir. Aşağıdaki kod, bir metni satırlara ayırarak temiz bir liste döndürür.

public List PrepareLines(string input)
{
    return input.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)
                .Select(line => line.TrimEnd())
                .ToList();
}

Bu metot, her türlü satır sonu karakterini standartlaştırır. TrimEnd() kullanımı, satır sonundaki görünmez boşlukların fark olarak algılanmasını engeller.

Adım 3: İki Sürüm Arasındaki Farkı Hesaplama

Şimdi temel karşılaştırma motorumuzu yazalım. Bu metot, eski ve yeni liste arasındaki farkları tespit eder.

public List CompareVersions(List oldVersion, List newVersion)
{
    var results = new List();
    int maxLines = Math.Max(oldVersion.Count, newVersion.Count);

    for (int i = 0; i < maxLines; i++)
    {
        string oldLine = i < oldVersion.Count ? oldVersion[i] : null;
        string newLine = i < newVersion.Count ? newVersion[i] : null;

        if (oldLine == newLine)
            results.Add(new DiffResult(i + 1, oldLine, ChangeType.Unchanged));
        else if (oldLine != null && newLine == null)
            results.Add(new DiffResult(i + 1, oldLine, ChangeType.Removed));
        else
            results.Add(new DiffResult(i + 1, newLine, ChangeType.Added));
    }
    return results;
}

Bu basit döngü, satırları indeks bazlı karşılaştırır. Daha karmaşık senaryolarda (satır ekleme/silme durumunda kaymalar) bu algoritmanın "Myers Diff" ile geliştirilmesi gerektiğini unutmayın.

Karşılaştırma Yöntemleri ve Özellikleri

Yöntem Avantaj Dezavantaj
Satır Bazlı (Line-by-line) Hızlı ve basit uygulama Blok kaymalarında hatalı sonuç verir
Kelime Bazlı (Word-level) Detaylı değişim gösterir İşlemci yükü yüksektir
Hash Tabanlı (Checksum) Büyük dosyalarda çok hızlı Satır içeriğini göstermez

Adım 4: Sonuçları Raporlama

Tespit edilen farkları kullanıcıya anlamlı bir şekilde sunmak önemlidir. Aşağıdaki örnek, konsol ekranında farkları renklendirerek gösterir.

public void PrintDiff(List diffs)
{
    foreach (var diff in diffs)
    {
        Console.ForegroundColor = diff.Type switch
        {
            ChangeType.Added => ConsoleColor.Green,
            ChangeType.Removed => ConsoleColor.Red,
            _ => ConsoleColor.Gray
        };
        Console.WriteLine($"{diff.Type}: {diff.Content}");
    }
    Console.ResetColor();
}

Bu metot, C# 13'ün sunduğu switch ifadesini kullanarak kodun okunabilirliğini artırır. Yeşil renk eklenen satırları, kırmızı ise silinenleri temsil eder.

Adım 5: Güvenlik ve Hata Yönetimi

Dosya okuma işlemleri sırasında oluşabilecek hataları yönetmek, uygulamanızın kararlılığı için kritiktir. Özellikle dışarıdan alınan dosya yollarında "Path Traversal" saldırılarına karşı dikkatli olmalısınız.

Kritik Güvenlik Uyarısı: Dosya sistemine erişirken kullanıcı tarafından sağlanan dosya yollarını mutlaka doğrulayın. Path.GetFileName() veya izin verilen dizin kontrolü yaparak dizin dışına çıkılmasını engelleyin. SQL veya veritabanı ile çalışıyorsanız, parametreli sorgular kullanarak SQL Injection riskini bertaraf edin.

public string ReadSafeFile(string filePath)
{
    string baseDirectory = @"C:\App\Data\";
    string fullPath = Path.GetFullPath(Path.Combine(baseDirectory, filePath));

    if (!fullPath.StartsWith(baseDirectory))
        throw new UnauthorizedAccessException("Geçersiz dosya yolu!");

    return File.ReadAllText(fullPath);
}

Bu kod, uygulamanın sadece belirlenen dizin içindeki dosyalara erişmesini garanti altına alır.

Sıkça Sorulan Sorular

Bu araç büyük boyutlu dosyaları karşılaştırabilir mi?

Evet, ancak çok büyük dosyalarda (yüzlerce megabayt) tüm içeriği belleğe (RAM) yüklemek yerine, dosyaları satır bazlı stream (akış) yöntemiyle okumanız performansı ciddi oranda artıracaktır.

Myers Diff algoritmasını neden kullanmalıyım?

Basit satır karşılaştırması, dosyanın başına yeni bir satır eklendiğinde tüm dosyayı "değişmiş" olarak algılar. Myers Diff, bu tür kaymaları akıllıca tespit ederek sadece eklenen satırı gösterir.

Performansı artırmak için ne yapabilirim?

Satırları karşılaştırırken string değerlerini doğrudan kıyaslamak yerine, her satırın bir hash değerini (örneğin SHA256) hesaplayıp bu hash değerlerini kıyaslamak çok daha hızlı sonuç verir.

Bu araç veritabanı tablolarını karşılaştırabilir mi?

Evet, veritabanından çektiğiniz verileri bir liste veya koleksiyon yapısına dönüştürerek aynı algoritmaları uygulayabilirsiniz.

Kodun üretim ortamında (production) çalışması için nelere dikkat etmeliyim?

Hata loglama mekanizmaları (Serilog gibi) eklemeli, bellek kullanımını takip etmeli ve büyük dosyalar için asenkron (async/await) programlama yapısını mutlaka kullanmalısınız.

İleri Seviye Optimizasyon: Büyük Ölçekli Dosyalar İçin Bellek Yönetimi

Geliştirdiğiniz karşılaştırma aracı, küçük metin dosyalarında kusursuz çalışsa da, binlerce satırdan oluşan log dosyaları veya büyük kaynak kod dosyalarıyla karşılaştığında OutOfMemoryException hatasıyla karşılaşabilir. Bellek kullanımını optimize etmek için tüm dosya içeriğini belleğe (RAM) yüklemek yerine, StreamReader kullanarak dosyayı satır satır okumalı ve karşılaştırma işlemini "chunk" (parça) mantığıyla gerçekleştirmelisiniz.

Aşağıdaki örnek, büyük dosyaları belleği yormadan satır satır okuyarak karşılaştırma ön hazırlığı yapan bir yapıyı göstermektedir:

public IEnumerable ReadFileStream(string filePath)
{
    using (var reader = new StreamReader(filePath))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line.Trim();
        }
    }
}

Bu yaklaşım, IEnumerable yapısını kullanarak veriyi "lazy loading" (tembel yükleme) yöntemiyle işler. Yani dosyanın tamamı değil, sadece o an karşılaştırılan satır bellekte tutulur.

Alternatif Kütüphaneler: DiffPlex ile Hızlı Entegrasyon

Kendi algoritmanızı yazmak öğretici olsa da, profesyonel projelerde DiffPlex gibi endüstri standardı kütüphaneleri kullanmak, kenar durumları (edge cases) ve performans optimizasyonlarını hazır olarak sunar. DiffPlex, özellikle görselleştirme katmanları için gerekli olan "karakter bazlı" farkları da destekler.

DiffPlex entegrasyonu için NuGet üzerinden paketi yükledikten sonra şu şekilde hızlıca sonuç alabilirsiniz:

using DiffPlex;
using DiffPlex.DiffBuilder;
using DiffPlex.DiffBuilder.Model;

public void CompareWithDiffPlex(string oldText, string newText)
{
    var diffBuilder = new SideBySideDiffBuilder(new Differ());
    var diff = diffBuilder.BuildDiffModel(oldText, newText);

    foreach (var line in diff.OldText.Lines)
    {
        Console.WriteLine($"Eski: {line.Type} - {line.Text}");
    }
    foreach (var line in diff.NewText.Lines)
    {
        Console.WriteLine($"Yeni: {line.Type} - {line.Text}");
    }
}

Neden Hazır Kütüphane Tercih Etmelisiniz?

  • Performans: Karmaşık Myers Diff algoritmaları kütüphanelerde optimize edilmiş C++ veya yüksek performanslı C# kodlarıyla yazılmıştır.
  • Görselleştirme: HTML veya konsol çıktısı için hazır "diff" formatlarını (Side-by-Side veya Inline) destekler.
  • Bakım: Algoritmik hatalarla uğraşmak yerine iş mantığınıza odaklanırsınız.

Test Odaklı Geliştirme (TDD) ile Karşılaştırma Doğruluğu

Karşılaştırma mantığınızın doğru çalıştığından emin olmak için birim testleri (Unit Tests) yazmak kritiktir. Özellikle boş satırlar, dosya sonu karakterleri (CRLF vs LF) ve kod girintileri gibi durumlar için test senaryoları oluşturmalısınız.

Senaryo Beklenen Sonuç
İki boş dosya Fark yok
Sadece satır sırası değişmiş içerik Değişiklik olarak işaretlenmeli
Büyük/Küçük harf duyarlılığı Ayarlanabilir parametre olmalı

Testlerinizi yazarken xUnit veya NUnit kullanarak, farklı dosya içerikleriyle Assert.Equal kontrolleri yapmanız, sürüm geçmişi aracınızın güvenilirliğini artıracaktır.

Sonuç

C# ile özel bir sürüm geçmişi karşılaştırma aracı geliştirmek, hem algoritmik düşünme becerinizi geliştirir hem de projelerinizde otomasyon seviyesini artırır. Bu makalede temel bir satır bazlı karşılaştırma motorunun nasıl kurulacağını, verilerin nasıl temizleneceğini ve sonuçların nasıl raporlanacağını öğrendiniz. Bir sonraki adım olarak, bu aracı bir web API'sine dönüştürerek veya dosya sistemini izleyen bir arka plan servisi (Background Service) haline getirerek projenizi bir üst seviyeye taşıyabilirsiniz.

Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında kullanmadan önce güvenlik açıklarını (dosya izinleri, bellek yönetimi vb.) gözden geçirmeli ve gerekli birim testlerini (Unit Tests) yazmalısınız.

Bu yazıya tepkinizi paylaşın:
Burak Şahin

Verimlilik ve organizasyon üzerine uzmanlaşmış bir yazarım. Karmaşık görevleri yönetilebilir parçalara bölerek okuyucunun zamanını doğru kullanmasını sağlıyorum.

Yorumlar (0)

Yorum Yaz