C# 9 Yenilikleri
30 May 2020C# 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!