C# 8.0 Yenilikleri

Oktay Kır
Bilişim Hareketi
7 min readJun 8, 2020

--

Merhaba dostlar,

C# 8.0 ile birlikte gelen heyecan verici tüm yenilikleri alttaki başlıklar ile inceleyeceğiz. C# 8.0 versiyon desteği .Net Core 3.x ve .Net Standard 2.1 ile birlikte geliyor. Detaylı bilgi için burayı inceleyebilirsiniz.

  • Readonly Members
  • Default Interface Methods
  • Pattern Matching
  • Using Declarations
  • Static Local Functions
  • Disposable ref Structs
  • Nullable Reference Types
  • Asynchronous Streams
  • Asynchronous Disposable
  • Indices and Ranges
  • Null-coalescing Assignment
  • Unmanaged Constructed Types
  • Stackalloc In Nested Expressions
  • Interpolated Verbatim String İyileştirmesi

Readonly Members

public struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y);
public override string ToString() =>
$"({X}, {Y}) is {Distance} from the origin";
}

Yukarıdaki örneğimizi inceleyelim. ToString metodu beklendiği gibi bulunduğu struct içerisinde bir değişikliğe neden olmuyor, o zaman bu metoda readonly modifier ekleyebiliriz.

public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin";

Bu değişiklikten sonra ToString metodu içerisinde çağrılan Distance property’si readonly olmadığı ve dolayısıyla state değişikliğine yol açabileceği için derleyici warning verir.

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

Distance property’si herhangi bir state değişikliğine yol açmadığını söylemek için readonly modifier’ı ekleyerek uyarıyı çözmüş oluruz.

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

Daha fazla bilgi için buraya bakabilirsiniz.

Default Interface Methods

En çok beğendim yeni gelen özellik bu olabilir. Hani Interface’in en belirgin özelliklerinden biri olan içerisinde sadece metod tanımları bulunur ifadesi artık geçerli değil, yeni gelen bu özellik ile birlikte artık metod implementasyonuda yapabiliyoruz. Bu bize yeni eklenen metodlara sahip sonraki interface versiyonu için bu interface’i implemente eden mevcut kodlar için breaking change’i engelleyerek metodlara varsayılan davranış tanımlamaları yapmamızı sağlıyor.

Daha detaylı bilgi için buraya bakabilirsiniz.

Pattern Matching

Pattern matching desteği C# 7 ile tanıtılmıştı. Yeni versiyon ile birlikte kullanım alanları alttaki başlıklar ile genişletildi.

Pattern matching teknikleri ile ilgili detaylı bilgi için buraya bakabilirsiniz.

Switch Expressions

Switch ifade kullanımında case, break ve default keyword’lerine ihtiyaç azaltıldı ve daha az süslü parentez kullanımı sağlandı. default keyword’ü _ ile, case ve : ifadeleri => ile değiştirildi.

public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00),
Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF),
Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
_ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
};

Property Patterns

Objenin property değerine göre eşleştirme özelliği geldi.

public decimal ComputeSalesTax(Address location, decimal salePrice) =>
location switch
{
{ State: "WA" } => salePrice * 0.06M,
{ State: "MN" } => salePrice * 0.075M,
{ State: "MI" } => salePrice * 0.05M,
_ => 0M
};

Yukarıdaki örnekte Address tipindeki paremetrenin State propertysinin değerine göre vergiyi hesaplıyoruz.

Tuple Patterns

Switch içerisinde çoklu değer ifadelerini tuple olarak yazmamızı sağlıyor.

public static string RockPaperScissors(string first, string second)
=> (first, second) switch
{
("rock", "paper") => "rock is covered by paper.",
("rock", "scissors") => "rock breaks scissors.",
("paper", "rock") => "paper covers rock.",
("paper", "scissors") => "paper is cut by scissors.",
("scissors", "rock") => "scissors is broken by rock.",
("scissors", "paper") => "scissors cuts paper.",
(_, _) => "tie"
};

Positional Patterns

Switch ifadeleri içerisinde yapılan pattern matching işlemi için objenin Deconstruct metodunu kullanarak tuple üzerinden karşılaştırma yapmasını sağlıyoruz.

public class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) =>
(x, y) = (X, Y);
}
public enum Quadrant
{
Unknown,
Origin,
One,
Two,
Three,
Four,
OnBorder
}
static Quadrant GetQuadrant(Point point) => point switch
{
(0, 0) => Quadrant.Origin,
var (x, y) when x > 0 && y > 0 => Quadrant.One,
var (x, y) when x < 0 && y > 0 => Quadrant.Two,
var (x, y) when x < 0 && y < 0 => Quadrant.Three,
var (x, y) when x > 0 && y < 0 => Quadrant.Four,
var (_, _) => Quadrant.OnBorder,
_ => Quadrant.Unknown
};

Burada switch ifadesi ya bir değer üretmeli ya da exception fırlatmalıdır.

Eğer hiçbir eşleşme olmazsa bir exception fırlatır. Switch ifadeleri içerisindeki tüm olası durumlar kapsanmadı ise derleyici bir warning oluşturur.

Recursive Patterns

Pattern’leri birbirleri içerisinde kullanmamızı sağlayan bir özellik geldi. Örneğimize bakalım:

foreach (var pet in Pets) 
{
if (pet is Dog { Vaccinated: false, Name: string name })
{
// ...
}
}

Eğer pet.Vaccinate değeri false olarak atanmış bir Dog tipinde ise pet.Name değerini alıp name değişkenine atıyoruz.

Daha detaylı bilgi için buraya bakabilirsiniz.

Using Declarations

Daha önceki using kullanıma bakalım. WriteLinesToFile metodu ile using ifadesinin kapsamları farklı olduğu için metodtan return edecek değişkeni using ifadesinden önce tanımlamamız gerekirdi.

static int WriteLinesToFile(IEnumerable<string> lines)
{
int skippedLines = 0;
using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
{
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}
}
return skippedLines;
}

Artık metod ile using kapsamları aynı yapılarak bu kapsam içerisinde herhangi bir yerde tanımlanan değişkeni return edebiliyoruz.

static int WriteLinesToFile(IEnumerable<string> lines)
{
using var file = new System.IO.StreamWriter("WriteLines2.txt");

int skippedLines = 0;
foreach (string line in lines)
{
if (!line.Contains("Second"))
{
file.WriteLine(line);
}
else
{
skippedLines++;
}
}

return skippedLines;
}

Static Local Functions

C# 7 ile gelen local function neydi hatırlayalım, bir metod içerisinde başka bir metod tanımlamamızı sağlayan bir özelliktir.

C# 8 ile artık local functionlar static özelliği kazanmış oldu. Böylece tanımlandığı metod içerisinde static olmayan yerel değişkenlere erişim engellenerek local function çağrılarında performans ve güvenlik kazanımı sağlanmış oldu.

int M()
{
int y = 5;
int x = 7;
return Add(x, y);
static int Add(int left, int right) => left + right;
}

Disposable ref Structs

ref structs interface implemente etme özelliği olmadığı için using içerisinde kullanıp disposable yapamıyorduk. Örneğimize bakalım:

class Program
{
static void Main(string[] args)
{
using (var book = new Book())
{
...
}
}
}
ref struct Book : IDisposable
{
public void Dispose()
{
}
}

Eğer IDisposable interface’ini implemente etmeye çalışırsak derleyici şu mesajı verir.

Error CS8343 'Book': ref structs cannot implement interfaces

Artık, ref struct içerisine public Dispose metodu eklediğimizde sihirli bir şekilde using içerisinde kullanmamıza olanak sağlandı.

class Program
{
static void Main(string[] args)
{
using (var book = new Book())
{
// ...
}
}
}
ref struct Book
{
public void Dispose()
{
}
}

Nullable Reference Types

Bildiğiniz gibi referans tipli değişkenler null değeri alabilirler. Artık referans tipler non-nullable tanımlama özelliği kazandı ve referans tipler tanımları varsayılan olarak non-nullable kabul ediliyor.

Eğer bir değişkeni nullable yapmak istiyorsak ? işareti eklememiz gerekiyor.

Bu arada endişe etmeyin kodlarınızı C# 8 geçişinde referans tipli değişkenler için refactoring yapmamıza gerek yok. Çünkü bu özelliği aktifleştirmek için nullable annotation context’i etkinleştirerek derleyicinin referans tipli değişkenleri nasıl yorumlayacağını belirtmemiz gerekiyor.

#nullable enable
string? s = null;
#nullable disable

Nullable context ile ilgili detaylı bilgi için buraya bakabilirsiniz.

Asynchronous Streams

IAsyncEnumerable interface’i sayesinde async bir metod oluşturup await foreach ile kullanabiliyoruz.

public static async IAsyncEnumerable<int> GenerateSequence()
{
for (int i = 0; i < 20; i++)
{
yield return i;
}
}
await foreach (var number in GenerateSequence())
{
Console.WriteLine(number);
}

Daha detaylı bilgi için buraya bakabilirsiniz.

Asynchronous Disposable

Hali hazırda bir objeyi asenkron olarak dispose etmek için alttaki gibi async/await context içerisinde kullanıyorduk fakat önerilmeyen bir yöntemdir.

await Task.Run(() => disposableObj.Dispose());

IAsyncDisposable implementasyonu ile using ifadesi ile bir objeyi asenkron olarak dispose edebilir hale geldik.

class AsyncDisposableObject : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.CompletedTask;
}
}
class Program
{
static async Task Main(string[] args)
{
await using var obj = new AsyncDisposableObject();
}
}

Indices and Ranges

Dile dizi işlemleri için System.Index ve System.Range tipleri eklendi.
Dizinin belirli bir elemanını işaret etmek için Index tipini kullanıyoruz, sonundan bir sıra işaret etmek için ise ^ operatörünü kullanıyoruz.

Belirli bir aralığı işaret etmek için ise .. operatörünü kullanıyoruz.

var words = new string[]
{
// index from start index from end
"The", // 0 ^9
"quick", // 1 ^8
"brown", // 2 ^7
"fox", // 3 ^6
"jumped", // 4 ^5
"over", // 5 ^4
"the", // 6 ^3
"lazy", // 7 ^2
"dog" // 8 ^1
}; // 9 (or words.Length) ^0
var firstWord = words[0] //"The";
var lastWord = words[^1] //"dog";
var quickBrownFox = words[1..4];

Detaylı bilgi için buraya bakabilirsiniz.

Not: Range kullanımında dikkat edilmesi gereken nokta ^0 index’in words.Length değerine eşit olduğu. Bu yüzden words[^0] kullanımı IndexOutOfRangeException hatasına sebep olur.

Null-coalescing Assignment

??= Operatörü ile atama işlemlerinde null kontrolü yapmamıza olanak veriyor. Eğer yalnızca sol tarafta işlenen ifade null ise sağ tarafta işlenen değerin sol tarafa ataması yapılır.

List<int> numbers = null;
int? i = null;
numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);
Console.WriteLine(string.Join(" ", numbers)); //output: 17 17
Console.WriteLine(i); //output: 17

Detaylı bilgi için buraya bakabilirsiniz.

Unmanaged Constructed Types

C# 8 ile birlikte kurucu tipleri unmanaged yapabiliyoruz.

public struct Coords<T> where T : unmanaged
{
public T X;
public T Y;
}
public class UnmanagedTypes
{
public void Foo()
{
DisplaySize<Coords<int>>();
}
private unsafe void DisplaySize<T>() where T : unmanaged{}
}

Detaylı bilgi için buraya bakabilirsiniz.

Stackalloc In Nested Expressions

Stackalloc operatörü stack’te bellek bloğu tahsis eder. Metod return ederken tahsis edilen bellek bloğu otomatik olarak silinir.

C# 8 ile birlikte eğer stackalloc ifadesinin sonucu System.Span<T> veya System.ReadOnlySpan<T> türünde ise stackalloc ifadesini başka ifadeler içerisinde kullanabiliyoruz.

Interpolated Verbatim String İyileştirmesi

Daha önce birlikte kullanımlarda string interpolation($) operatörü verbatim string(@) operatöründen önce yazılması gerekirdi. C# 8 ile operatör kullanım sıralarının yani @$ ile $@ ifadelerinin farkı kalmadı.

--

--

Oktay Kır
Bilişim Hareketi

Father, Software Architect, Self-Motivated, Curiosity Driven, Always Learning and Improving https://github.com/OKTAYKIR kir.oktay@gmail.com