Öğretici: C# 11 özelliğini keşfetme - arabirimlerdeki statik sanal üyeler

C# 11 ve .NET 7, arabirimlerde statik sanal üyeler içerir. Bu özellik, aşırı yüklenmiş işleçleri veya diğer statik üyeleri içeren arabirimleri tanımlamanızı sağlar. Statik üyeleri olan arabirimleri tanımladıktan sonra, işleçleri veya diğer statik yöntemleri kullanan genel türler oluşturmak için bu arabirimleri kısıtlamalar olarak kullanabilirsiniz. Aşırı yüklenmiş işleçlere sahip arabirimler oluşturmasanız bile, büyük olasılıkla bu özellikten ve dil güncelleştirmesi tarafından etkinleştirilen genel matematik sınıflarından yararlanacaksınız.

Bu öğreticide aşağıdakilerin nasıl yapılacağını öğreneceksiniz:

  • Statik üyeleri olan arabirimleri tanımlayın.
  • Tanımlanan işleçlerle arabirimler uygulayan sınıfları tanımlamak için arabirimleri kullanın.
  • Statik arabirim yöntemlerini kullanan genel algoritmalar oluşturun.

Önkoşullar

C# 11'i destekleyen .NET 7'yi çalıştırmak için makinenizi ayarlamanız gerekir. C# 11 derleyicisi Visual Studio 2022, sürüm 17.3 veya .NET 7 SDK'dan itibaren kullanılabilir.

Statik soyut arabirim yöntemleri

Bir örnekle başlayalım. Aşağıdaki yöntem iki double sayının orta noktasını döndürür:

public static double MidPoint(double left, double right) =>
    (left + right) / (2.0);

Aynı mantık herhangi bir sayısal türde çalışır: int, short, long, ,decimalfloat veya bir sayıyı temsil eden herhangi bir tür. ve / işleçlerini kullanmak + ve için bir değer tanımlamak için 2bir yolunuz olması gerekir. Yukarıdaki yöntemi aşağıdaki genel yöntem olarak yazmak için arabirimini kullanabilirsiniz System.Numerics.INumber<TSelf> :

public static T MidPoint<T>(T left, T right)
    where T : INumber<T> => (left + right) / T.CreateChecked(2);  // note: the addition of left and right may overflow here; it's just for demonstration purposes

Arabirimi uygulayan INumber<TSelf> her tür için ve için operator +operator /bir tanım içermelidir. Payda, paydayı iki parametreyle aynı türe zorlayan herhangi bir sayısal türün değerini 2 oluşturmak için tarafından T.CreateChecked(2) tanımlanır. INumberBase<TSelf>.CreateChecked<TOther>(TOther) belirtilen değerden türün bir örneğini oluşturur ve değer temsil edilebilir aralığın dışında kalırsa bir OverflowException oluşturur. (Bu uygulama, hem de yeterince büyük değerlerse leftright taşma potansiyeline sahiptir. Bu olası sorunu önleyebilecek alternatif algoritmalar vardır.)

Tanıdık söz dizimini kullanarak bir arabirimde statik soyut üyeler tanımlarsınız: ve abstract değiştiricilerini bir uygulama sağlamayan herhangi bir statik üyeye eklersinizstatic. Aşağıdaki örnek, geçersiz kılan operator ++herhangi bir türe uygulanabilecek bir IGetNext<T> arabirim tanımlar:

public interface IGetNext<T> where T : IGetNext<T>
{
    static abstract T operator ++(T other);
}

tür bağımsız değişkeninin uyguladığı IGetNext<T> kısıtlama, Tişlecin imzasının içeren türü veya tür bağımsız değişkenini içermesini sağlar. Birçok işleç, parametrelerinin türle eşleşmesi veya içeren türü uygulamak için kısıtlanmış tür parametresi olması gerektiğini zorlar. Bu kısıtlama olmadan, ++ işleç arabirimde IGetNext<T> tanımlanamaz.

Aşağıdaki kodu kullanarak her artışın dizeye başka bir karakter eklediği bir 'A' karakter dizesi oluşturan bir yapı oluşturabilirsiniz:

public struct RepeatSequence : IGetNext<RepeatSequence>
{
    private const char Ch = 'A';
    public string Text = new string(Ch, 1);

    public RepeatSequence() {}

    public static RepeatSequence operator ++(RepeatSequence other)
        => other with { Text = other.Text + Ch };

    public override string ToString() => Text;
}

Daha genel olarak, "bu türün sonraki değerini üret" anlamına gelecek şekilde tanımlamak ++ isteyebileceğiniz herhangi bir algoritma oluşturabilirsiniz. Bu arabirimin kullanılması net kod ve sonuçlar üretir:

var str = new RepeatSequence();

for (int i = 0; i < 10; i++)
    Console.WriteLine(str++);

Yukarıdaki örnek aşağıdaki çıkışı oluşturur:

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

Bu küçük örnek, bu özelliğin motivasyonunu gösterir. İşleçler, sabit değerler ve diğer statik işlemler için doğal söz dizimi kullanabilirsiniz. Aşırı yüklenmiş işleçler de dahil olmak üzere statik üyeleri kullanan birden çok tür oluşturduğunuzda bu teknikleri keşfedebilirsiniz. Türlerinizin özellikleriyle eşleşen arabirimleri tanımlayın ve ardından bu türlerin yeni arabirim desteğini bildirin.

Genel matematik

Arabirimlerde işleçler de dahil olmak üzere statik yöntemlere izin vermek için motivasyon sağlayan senaryo, genel matematik algoritmalarını desteklemektir. .NET 7 temel sınıf kitaplığı, birçok aritmetik işleç için arabirim tanımları ve bir arabirimde birçok aritmetik işleci birleştiren INumber<T> türetilmiş arabirimler içerir. Bu türleri, için herhangi bir sayısal türü kullanabilen bir Point<T> kayıt oluşturmak için Tuygulayalım. Nokta bazıları XOffset tarafından taşınabilir ve YOffset işleci kullanılarak + kullanılabilir.

veya Visual Studio kullanarak dotnet new yeni bir Konsol uygulaması oluşturarak başlayın.

ve Point<T> için Translation<T> ortak arabirim aşağıdaki kod gibi görünmelidir:

// Note: Not complete. This won't compile yet.
public record Translation<T>(T XOffset, T YOffset);

public record Point<T>(T X, T Y)
{
    public static Point<T> operator +(Point<T> left, Translation<T> right);
}

hem hem de Point<T> türleri için türünü kullanırsınızrecord: Her ikisi de Translation<T> iki değeri depolar ve bunlar karmaşık davranış yerine veri depolamayı temsil eder. uygulaması operator + aşağıdaki koda benzer olacaktır:

public static Point<T> operator +(Point<T> left, Translation<T> right) =>
    left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };

Önceki kodun derlenmiş olması için arabirimini desteklediğini TIAdditionOperators<TSelf, TOther, TResult> bildirmeniz gerekir. Bu arabirim statik yöntemi içerir operator + . Üç tür parametresi bildirir: Biri sol işlenen için, biri sağ işlenen için ve biri sonuç için. Bazı türler farklı işlenen ve sonuç türleri için uygulanır + . tür bağımsız değişkeninin T uygulaydığını IAdditionOperators<T, T, T>belirten bir bildirim ekleyin:

public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>

Bu kısıtlamayı ekledikten sonra sınıfınız Point<T> ekleme işleci için öğesini + kullanabilir. Bildirime aynı kısıtlamayı Translation<T> ekleyin:

public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;

Kısıtlama, IAdditionOperators<T, T, T> sınıfınızı kullanan bir geliştiricinin bir noktaya ekleme kısıtlamasını karşılamayan bir tür kullanarak oluşturmasını Translation engeller. için type parametresine Translation<T> gerekli kısıtlamaları eklediniz ve Point<T> bu nedenle bu kod çalışır. Program.cs dosyanızda ve Point bildirimlerinin Translation üstüne aşağıdaki gibi bir kod ekleyerek test edebilirsiniz:

var pt = new Point<int>(3, 4);

var translate = new Translation<int>(5, 10);

var final = pt + translate;

Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);

Bu türlerin uygun aritmetik arabirimleri uyguladığını bildirerek bu kodu daha yeniden kullanılabilir hale getirebilirsiniz. Yapılacak ilk değişiklik, arabirimi uygulayan bildirimini Point<T, T>IAdditionOperators<Point<T>, Translation<T>, Point<T>> yapmaktır. türü, Point işlenenler ve sonuç için farklı türleri kullanır. Türü Point zaten bu imzaya sahip bir operator + uygular, bu nedenle tek ihtiyacınız olan bildirime arabirim eklemektir:

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>

Son olarak, ekleme gerçekleştirirken, bu tür için ek kimlik değerini tanımlayan bir özelliğe sahip olmak yararlı olur. Bu özellik için yeni bir arabirim vardır: IAdditiveIdentity<TSelf,TResult>. öğesinin çevirisi {0, 0} , eklemeli kimliktir: Sonuçta elde edilen nokta, sol işlenenle aynıdır. IAdditiveIdentity<TSelf, TResult> Arabirim, AdditiveIdentitykimlik değerini döndüren tek bir salt okunur özelliği tanımlar. bu Translation<T> arabirimi uygulamak için birkaç değişikliğe ihtiyaç duyar:

using System.Numerics;

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Translation<T> AdditiveIdentity =>
        new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
}

Burada birkaç değişiklik var, bu nedenle bunları tek tek inceleyelim. İlk olarak, türün Translation arabirimini uyguladığını IAdditiveIdentity bildirirsiniz:

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>

Daha sonra aşağıdaki kodda gösterildiği gibi arabirim üyesini uygulamayı deneyebilirsiniz:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: 0, YOffset: 0);

Türüne bağlı olduğundan 0 yukarıdaki kod derlenmez. Yanıt: için 0kullanınIAdditiveIdentity<T>.AdditiveIdentity. Bu değişiklik, kısıtlamalarınızın artık uygulayanları içermesi TIAdditiveIdentity<T>gerektiği anlamına gelir. Bunun sonucunda aşağıdaki uygulama elde edebilirsiniz:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);

Artık bu kısıtlamayı öğesine Translation<T>eklediğinize göre, öğesine de aynı kısıtlamayı Point<T>eklemeniz gerekir:

using System.Numerics;

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Point<T> operator +(Point<T> left, Translation<T> right) =>
        left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
}

Bu örnek, genel matematik arabirimlerinin nasıl oluştuğuna göz atmıştır. Şunları öğrendiniz:

  • Yöntemin herhangi bir sayısal türle kullanılabilmesi için arabirime dayalı INumber<T> bir yöntem yazın.
  • Yalnızca bir matematiksel işlemi destekleyen bir tür uygulamak için ekleme arabirimlerine dayalı bir tür oluşturun. Bu tür, başka şekillerde oluşturulabilmesi için aynı arabirimler için desteğini bildirir. Algoritmalar, matematik işleçlerinin en doğal söz dizimi kullanılarak yazılır.

Bu özelliklerle denemeler yapın ve geri bildirimleri kaydedin. Visual Studio'da Geri Bildirim Gönder menü öğesini kullanabilir veya GitHub'daki roslyn deposunda yeni bir sorun oluşturabilirsiniz. Herhangi bir sayısal türle çalışan genel algoritmalar oluşturun. Tür bağımsız değişkeninin yalnızca sayı benzeri özelliklerin bir alt kümesini uygulayabileceği bu arabirimleri kullanarak algoritmalar oluşturun. Bu özellikleri kullanan yeni arabirimler oluşturmasanız bile bunları algoritmalarınızda kullanmayı deneyebilirsiniz.

Ayrıca bkz.