Umut Özel    About    Archive    Feed

C# 9 Yenilikleri

C# 9.0 şekilleniyor ve bir sonraki sürümüne eklenen bazı önemli özellikler hakkındaki bir ön gösterim yapmak istedim.

C#’ın her yeni sürümünde, ortak kodlama senaryolarında daha fazla netlik ve basitlik için ciddi geliştirmeler yaşanıyor ve C# 9.0 bir istisna değil. Bu kez ağırlıklı odaklanılan nokta verilerin kısa ve değişmez (immutable) temsilleriyle ilgili.


Init-only properties

Object-Initializer’ları hepimiz çok sevdik. Esnek ve okunabilir bir şekilde obje oluşturmamızı sağlarken, gerektiğinde iç-içe objeleri de bir çırpıda oluşturabilmemizi sağlıyor. Aşağıda basit bir örnekte görebiliriz.

new Person
{
    FirstName = "Alan",
    LastName = "Turing"
}

Bizi sıkıcı .ctor yazılımlarından da kurtarabiliyor, sadece property yazmamız yeterli.

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Ancak önemli bir gereksinimleri var, property’ler değiştirilebilir olmak zorunda. Arka planda .ctor çağrısı yaptıktan sonra (verdiğimiz örnek için varsayılan parametresiz .ctor çağırılıyor) property set işlemleri bizim için yapılıyor.

Burada init-only property’ler imdadımıza yetişiyor. Yeni init belirteci ile set kullanmadan sadece init için kullanılabilir property’ler oluşturabiliyoruz!

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Kodumuzu böyle değiştirdiğimizde object-initialize kodumuz halen geçerli olacak.


Init ve Readonly

init tanımlaması yapılan readonly alanlar da kullanılabiliyor.

public class Person
{
    private readonly string firstName;
    private readonly string lastName;

    public string FirstName
    {
        get => firstName;
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }

    public string LastName
    {
        get => lastName;
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

Records

init tek tek alanları değiştirilemez yapmak için çok güzel. Ancak ya tüm sınıfa bu işlemi yapmak ve sadece değer taşıyan değiştirilemez objeler elde etmek istiyorsak?

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

Yukarıda gördüğünüz data sınıfı değiştirilemez (immutable) yaptı, çalışması artık value-type (struct) gibi olacak. Heap içinde tutmak istediğiniz değer tipleri gibi görebilirsiniz.


With ifadesi

Değiştirilemez objeler ile çalışırken sık kullanılan bir yaklaşım (fonksiyonel diller ile uğraşanlar bilirler) eski objeler yerine yeni durumu temsil eden yeni objeler oluşturup eskileri göz ardı etmektir. Bir Person objemizin soyadı değiştiğinde farklı soyada sahip yeni bir örnek oluşturmak gibi. Bu tekniğe genel olarak non-destructive mutation denir. Bu yaklaşımı kolaylaştırmak için aşağıdaki gibi with ifadesini kullanabiliyoruz. Bu arada dil gittikçe fonksiyonel özellikleri arttırıyor, bakalım nereye varacağız :)

var otherPerson = person with { LastName = "Kay" };

Tahmin ettiğiniz gibi yeni objemizin soyadı “Kay” oldu.

Arka planda Record olarak tanımladığımız sınıflar bizim için tüm property’leri kopyalayan protected bir .ctor oluşturuyor.

protected Person(Person original) { /* her şeyi kopyala */ } // otomatik üretildi

with çağrımız bu .ctor’un çağırılmasına sebep oluyor, üstüne de object-initialize kodumuzu çağırıyor.


Top-level Programlar

En basitinden bir C# kodumuzu aşağıdaki gibi görünüyordu.

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

Python gibi diller kullanan arkadaşların bel altı vurduğu bu seremoni kodlarına artık gerek kalmayacak, nasıl mı?

using System;

Console.WriteLine("Hello World!");

Pattern-Matching daha da gelişti

Gerçekten fonksiyonel özellikler objeye dayalı olanları geçecek yakında. Ben de bu makaleyi yazarken esinlendiğim :) kaynak gibi aşağıdaki kod parçası üzerinden yenilikleri anlatayım.

public static decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
       ...

        DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
        DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m,
        DeliveryTruck _ => 10.00m,

        _ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
    };

Basit Tip Şablonları

Her eşleşen şablon için bir değişken veriyorduk (ilk iki t), kenara atacak olsak bile (_). Artık tip eşleşmesinde kullanmayacaksak değişken geçmemiz gerekmiyor.

DeliveryTruck => 10.00m,

Karşılaştırma Şablonları

Artık C# 9 ile karşılaştırma şablonlarımızı aşağıdaki gibi yazabiliyoruz.

DeliveryTruck t when t.GrossWeightClass switch
{
    > 5000 => 10.00m + 5.00m,
    < 3000 => 10.00m - 2.00m,
    _ => 10.00m,
},

Burada gördüğünüz > 5000 ve < 3000 karşılaştırma şablonları. Çok acayip!

Mantıksal Şablonlar

Eh, yukarıdaki gibi kalsa ayıp olurdu tabii, mantıksal ifadelerle birleştirebiliyoruz da.

DeliveryTruck t when t.GrossWeightClass switch
{
    < 3000 => 10.00m - 2.00m,
    >= 3000 and <= 5000 => 10.00m,
    > 5000 => 10.00m + 5.00m,
},

İlk örnedğimizde hiçbir şartımıza uymayan parametre için ArgumentException fırlatıyoruz. O satırı aşağıdaki ikili ile değiştirerek kontrolümüzü sıkılaştırabiliriz. Burada da not kullanımını görebilirsiniz.

not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))

Ayrıca not artık if yazarken de aşağıdaki gibi kullanılabiliyor.

if (e is not Customer) { ... }

Gelişmiş Hedef-Tip Tahmini

Hedef-Tip Tahmini (Target-Typing) dilin bir ifadeye bakıp bizim açıkça belirtmediğimiz tipi anlamaya çalışması.

new ile

Artık new ile yeni bir instance oluştururken ne oluşturmak istediğimiz belli ise tipi yazmak zorunda değiliz.

Point p = new (3, 5);

?? ve ?: ile

Bazen ?? ve ?: kullandığımızda ortak tipi belirlemek mümkün olmayabiliyor. Eğer ortak bir hedef tipi sol tarafa yazdıysak C# 9 bizim için otomatik doğru seçimi yapabilecek.

Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type

Covariant Dönüşler

Biliyorsunuz bir metodu ezerken alt sınıfta belirtilen tipi kullanmak zorundayız, her ne kadar bu tipten türemiş (ya da implemente eden) başka bir tip dönüyor olsak da. Artık bu yaptığımız özelleştirmeyi kodumuza yansıtabiliyoruz.

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

Yukarıda Food sınıfından türeyen bir Meat dönüyoruz, ve bu yaptığımızı açık açık söyleyebiliyoruz (tanıdık gelmiştir, polymorphism).


Ve Çok Daha Fazlası

Bu özellikler henüz geliştirme aşamasında olduğundan değişebilirler hatta sonraya ertelenebilirler. En iyisi siz de Dil Özellikleri Takip Sayfasını kontrol edin.

Mutlu kodlamalar!