C# 2026: Extension Members ile Daha Temiz API Tasarımı

C# 14 extension bloklarıyla uzantı özellikleri ve statik uzantılar yazın. Derlenip çalıştırılmış .NET 10 örnekleriyle API tasarımını sadeleştirin.

C# 2026: Extension Members ile Daha Temiz API Tasarımı — .NET 10 / C# 14
Sürüm notu: “C# 2026” resmî bir dil sürümü adı değil, bu rehberin çalışma dönemidir. Örnekler kararlı .NET 10 ve C# 14 içindir; önizleme özellikleri kullanılmaz.

Bir müşteri kaydının görünen adını hesaplamak için küçük bir yardımcı metot yazmak kolaydır. Asıl mesele, bu yardımcıların sayısı arttığında çağıran kodun anlaşılır kalmasıdır. CustomerHelpers.GetDisplayName(customer) yerine customer.DisplayName okumak, doğru bağlamda daha doğal olabilir.

C# 14’ün extension members özelliği tam olarak bu noktada işe yarar: mevcut bir tipe uzantı metotlarının yanında uzantı özellikleri de ekleyebiliriz. Üstelik yalnızca nesneyi değil, tipin kendisini hedefleyen statik uzantılar da tanımlayabiliriz.

Bu yazıda küçük bir müşteri modeli üzerinden ilerleyelim. Amaç mümkün olan en kısa kodu yazmak değil; neyin gerçek bir üye, neyin uzantı olduğunu bilerek okunaklı bir API oluşturmaktır.

1. Ortamı doğru ayarlayın

.NET 10 hedefleyen bir konsol projesi oluşturun:

dotnet new console -n ExtensionDemo -f net10.0
cd ExtensionDemo

net10.0 için varsayılan dil sürümü C# 14’tür. Örneklerle aynı ayarı açıkça belirtmek isterseniz proje dosyanız şöyle olabilir:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>14.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

LangVersion=latest kullanmak yerine hedef çerçevenin varsayılanına veya belirli bir sürüme bağlı kalın. Böylece geliştirici bilgisayarı ile CI ortamında farklı dil özelliklerinin devreye girmesini önlersiniz.

2. Klasik extension metodu hâlâ geçerli

Önce modelimiz:

namespace CSharp2026.ExtensionMembers;

public sealed record Customer(string FirstName, string LastName);

C# 14 öncesinde bu modele bir ad biçimlendirme yardımcısı eklemek için ilk parametresinde this bulunan statik bir metot kullanırdık:

namespace CSharp2026.ExtensionMembers;

public static class LegacyCustomerExtensions
{
    public static string GetDisplayName(this Customer customer)
    {
        ArgumentNullException.ThrowIfNull(customer);
        return $"{customer.FirstName.Trim()} {customer.LastName.Trim()}".Trim();
    }
}

Bu kod hâlâ desteklenir. Mevcut extension metotlarınızı sırf yeni sözdizimi geldi diye topluca değiştirmeniz gerekmez. Çağıran taraf customer.GetDisplayName() yazmaya devam edebilir.

Ancak hesaplanan değeri bir özellik olarak sunmak istiyorsanız, yeni extension blokları daha uygun bir ifade biçimi sağlar.

3. Bir extension bloğunda birden fazla üye

Aşağıdaki kodu ayrı bir CustomerExtensions.cs dosyasına koyun:

namespace CSharp2026.ExtensionMembers;

public static class CustomerExtensions
{
    extension(Customer customer)
    {
        public string DisplayName
        {
            get
            {
                ArgumentNullException.ThrowIfNull(customer);
                return $"{customer.FirstName.Trim()} {customer.LastName.Trim()}".Trim();
            }
        }

        public string ToGreeting() => $"Merhaba, {customer.DisplayName}!";
    }

    extension(Customer)
    {
        public static Customer CreateGuest() => new("Misafir", string.Empty);
    }
}

İlk bloktaki customer, uzantının uygulandığı nesnedir. DisplayName özellik gibi görünür; ToGreeting() ise metot gibi çağrılır. İkinci blokta nesne adı bulunmaz: extension(Customer) doğrudan tipe yönelik statik üyeleri tanımlar.

Kullanımı:

using CSharp2026.ExtensionMembers;

var customer = new Customer(" Ada ", " Lovelace ");
Console.WriteLine(customer.DisplayName);
Console.WriteLine(customer.ToGreeting());
Console.WriteLine(Customer.CreateGuest().DisplayName);

Beklenen çıktı:

Ada Lovelace
Merhaba, Ada Lovelace!
Misafir

CreateGuest() mevcut Customer sınıfının kaynak kodunda tanımlı değildir. İlgili namespace kapsamdayken derleyici statik uzantıyı bulur. Her çağrı yeni bir kayıt oluşturur; burada paylaşılan, değiştirilebilir bir global nesne yoktur.

DisplayName de saklanan bir alan değildir. Her erişimde adları kırpıp sonucu hesaplar. Bu ayrım, özellikle özellik içine pahalı bir veritabanı sorgusu koymayı düşündüğünüzde önemlidir: basit görünen bir özellik, beklenmedik I/O işlemi saklamamalıdır.

4. Koleksiyonlara anlamlı bir özellik eklemek

Uzantı blokları generic tiplerle de kullanılabilir:

namespace CSharp2026.ExtensionMembers;

public static class CollectionExtensions
{
    extension<T>(IReadOnlyCollection<T> source)
    {
        public bool HasItems
        {
            get
            {
                ArgumentNullException.ThrowIfNull(source);
                return source.Count > 0;
            }
        }
    }
}

Örneğin:

var customers = new List<Customer> { new("Ada", "Lovelace") };
Console.WriteLine(customers.HasItems); // True

Burada özellikle IReadOnlyCollection<T> seçtik. Count üzerinden kontrol yapabiliyoruz; diziyi baştan dolaşmamız gerekmiyor. Sadece IEnumerable<T> kabul edip her erişimde Any() çağıran bir özellik, kaynağa bağlı olarak yeniden numaralandırma yapabilir. Üyenin adı kadar kabul ettiği arayüz de davranışı belirler.

Bununla birlikte bu örnek, projedeki her koleksiyona HasItems eklenmesi gerektiği anlamına gelmez. Count > 0 zaten yeterince açıksa ek bir soyutlama gereksiz olabilir.

5. Tasarımda dikkat edilecek sınırlar

Extension üyeleri tipe gerçekten yeni alanlar eklemez ve kapsüllemeyi aşmaz. Private üyelere erişim yetkisi vermez; virtual davranışın yerini de almaz. İsim ve imza uyuştuğunda tipin kendi üyesi uzantıdan önceliklidir.

Bir başka önemli nokta namespace kapsamıdır. Gerekli using yoksa uzantı görünmez. Çok genel bir Extensions namespace’i, uygulamanın her yerine beklenmedik üyeler yayabilir. Katmana özgü ve açık isimler kullanmak daha sağlıklı bir tercihtir.

Şu ayrımı koruyun:

  • Kural modelin her kullanımında geçerliyse, kaynak kod sizin kontrolünüzdeyse doğrudan model üyesi düşünün.
  • Davranış sadece sunum, raporlama veya belirli bir uygulama katmanına aitse uzantı uygun olabilir.
  • Zaten açık olan bir ifadeyi tek satır kısaltmak için yeni bir API oluşturmayın.

Örnekteki ThrowIfNull, alıcı nesnenin null olmasını yakalar. Bu, FirstName ve LastName için kapsamlı veri doğrulaması değildir. Dışarıdan gelen veriler model oluşturulurken ayrıca doğrulanmalıdır; nullable anotasyonları tek başına çalışma zamanı garantisi sağlamaz.

Sonuç: yenilikten çok niyeti görünür kılın

C# 14 extension blokları; ilişkili yardımcı üyeleri bir arada toplamak, hesaplanan değerleri özellik olarak ifade etmek ve statik yardımcıları tip üzerinden çağırmak için kullanışlıdır. Kazanç, yalnızca daha az karakter yazmak değil, çağıran kodda niyetin daha kolay okunmasıdır.

Üç makalenin çalıştırılabilir kod örneklerini indir — ZIP. Bu makalenin ExtensionMembers projesinde normal kullanım, boş koleksiyon, klasik sözdizimi ve null alıcı davranışı için yedi doğrulama bulunur.

Kaynaklar ve devamı

Bu yazı yapay zekâ desteğiyle hazırlanmıştır. İndirilebilir örnekler .NET SDK 10.0.401 üzerinde, C# 14 ayarı ve uyarıları hata kabul eden yapılandırmayla derlenip çalıştırılmıştır.