Aracılığıyla paylaş


C# 9.0 için desen eşleştirme değişiklikleri

Uyarı

Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.

Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar ilgili dil tasarım toplantısı (LDM) notlarında yakalanır.

Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek içinbelirtimleri makalesinde bulabilirsiniz.

C# 9.0 için desen eşleştirmede doğal sinerjiye sahip olan ve bir dizi yaygın programlama sorununu çözmek için iyi çalışan küçük bir dizi iyileştirmeyi düşünüyoruz:

Ayraçlı Desenler

Ayraçlı desenler, programcının herhangi bir desenin çevresine parantez yerleştirmesine izin verir. Bu, C# 8.0'daki mevcut desenlerde çok kullanışlı değildir, ancak yeni desen birleştiricileri programcının geçersiz kılmak isteyebileceği bir öncelik tanıtır.

primary_pattern
    : parenthesized_pattern
    | // all of the existing forms
    ;
parenthesized_pattern
    : '(' pattern ')'
    ;

Tür Desenleri

Bir türe desen olarak izin veririz:

primary_pattern
    : type-pattern
    | // all of the existing forms
    ;
type_pattern
    : type
    ;

Bu, var olan is-type-ifadesini, desenin tür deseni olduğu bir is-pattern-ifadesi olacak şekilde yineler, ancak derleyici tarafından üretilen söz dizimi ağacını değiştirmeziz.

Hafif bir uygulama sorunu, bu dil bilgisinin belirsiz olmasıdır. gibi a.b bir dize, nitelenmiş ad (tür bağlamında) veya noktalı ifade (ifade bağlamında) olarak ayrıştırılabilir. Derleyici, gibi e is Color.Redbir şeyi işlemek için nitelenmiş bir adı noktalı ifadeyle aynı şekilde ele alma özelliğine zaten sahiptir. Derleyicinin semantik çözümlemesi, bu yapıyı desteklemek üzere bağlı tür deseni olarak ele almak için bir (söz dizimsel) sabit desenini (örneğin noktalı ifade) tür olarak bağlayabilecek şekilde genişletilebilir.

Bu değişiklik sonrasında

void M(object o1, object o2)
{
    var t = (o1, o2);
    if (t is (int, string)) {} // test if o1 is an int and o2 is a string
    switch (o1) {
        case int: break; // test if o1 is an int
        case System.String: break; // test if o1 is a string
    }
}

İlişkisel Desenler

İlişkisel desenler, programcının sabit bir değerle karşılaştırıldığında bir giriş değerinin ilişkisel kısıtlamayı karşılaması gerektiğini ifade etmesi için izin verir:

    public static LifeStage LifeStageAtAge(int age) => age switch
    {
        < 0 =>  LifeStage.Prenatal,
        < 2 =>  LifeStage.Infant,
        < 4 =>  LifeStage.Toddler,
        < 6 =>  LifeStage.EarlyChild,
        < 12 => LifeStage.MiddleChild,
        < 20 => LifeStage.Adolescent,
        < 40 => LifeStage.EarlyAdult,
        < 65 => LifeStage.MiddleAdult,
        _ =>    LifeStage.LateAdult,
    };

İlişkisel desenler, <bir ifadede aynı türde iki işlenene sahip bu tür ikili ilişkisel işleçleri destekleyen tüm yerleşik türlerde , , <=ve > ilişkisel işleçleri >=destekler. Özellikle, , , sbytebyteshort, ushort, , int, uint, long, ulongchar, float, , doubledecimalve nintiçin nuintbu ilişkisel desenlerin tümünü destekliyoruz.

primary_pattern
    : relational_pattern
    ;
relational_pattern
    : '<' relational_expression
    | '<=' relational_expression
    | '>' relational_expression
    | '>=' relational_expression
    ;

İfade, sabit bir değere değerlendirmek için gereklidir. Sabit değer veya double.NaNise bu bir hatadırfloat.NaN. İfade null bir sabitse bu bir hatadır.

Giriş, girişin sol işleneni ve verilen sabiti sağ işlenen olarak geçerli olan uygun bir yerleşik ikili ilişkisel işlecin tanımlandığı bir tür olduğunda, bu işlecin değerlendirmesi ilişkisel desenin anlamı olarak alınır. Aksi takdirde, açık bir null atanabilir veya kutulama kaldırılabilir dönüştürme kullanarak girişi ifadenin türüne dönüştürüriz. Böyle bir dönüştürme yoksa bu bir derleme zamanı hatasıdır. Dönüştürme başarısız olursa desenin eşleşmediği kabul edilir. Dönüştürme başarılı olursa desen eşleştirme işleminin sonucu, dönüştürülen girişin olduğu e OP v ifadenin e değerlendirilmesinin sonucudur, OP ilişkisel işleç ve v sabit ifadedir.

Desen Birleştiricileri

Desen birleştiricileri kullanarak iki farklı desenin and her ikisinin de eşleştirilmesine izin verir (bu, kullanarak andiki farklı desenden or biri (ditto) veya not yoluyla herhangi bir sayıda desene genişletilebilir.

Birleştiricinin yaygın kullanımlarından biri deyimi olacaktır

if (e is not null) ...

Geçerli deyiminden e is objectdaha okunabilir olan bu desen, birinin null olmayan bir değeri denetlediğini açıkça ifade eder.

and ve or birleştiricileri, değer aralıklarını test etmede yararlı olacaktır

bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Bu örnekte, 'den anddaha yüksek bir ayrıştırma önceliğine (yani daha yakından bağlanacaktır) sahip olacağı gösterilmektediror. Programcı, önceliği açık hale getirmek için ayraçlı deseni kullanabilir:

bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Tüm desenlerde olduğu gibi, bu birleştiriciler iç içe desenler, is-pattern-ifadesi, switch-ifadesi ve switch deyiminin durum etiketinin deseni de dahil olmak üzere bir desenin beklendiği herhangi bir bağlamda kullanılabilir.

pattern
    : disjunctive_pattern
    ;
disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;
conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;
negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;
primary_pattern
    : // all of the patterns forms previously defined
    ;

6.2.5 Dil Bilgisi Belirsizliği Olarak Değiştir

Tür deseninin kullanıma sunulması nedeniyle, genel bir türün belirtecinden =>önce görünmesi mümkündür. Bu nedenle, tür bağımsız değişkeni listesinin belirsiz olmasını => sağlamak için §6.2.5 Dil bilgisi belirsizliklerinde listelenen belirteç kümesine ekleriz<. Ayrıca bkz. https://github.com/dotnet/roslyn/issues/47614.

Önerilen Değişikliklerle İlgili Sorunları Açma

İlişkisel işleçler için söz dizimi

, andve or bir tür bağlamsal anahtar sözcük münot? Öyleyse, hataya neden olan bir değişiklik var mı (örneğin, bildirim deseninde bir belirleyici olarak kullanılmasıyla karşılaştırıldığında).

İlişkisel işleçler için semantik (örn. tür)

İlişkisel işleç kullanılarak bir ifadede karşılaştırılabilir tüm temel türleri desteklemeyi bekliyoruz. Basit durumlarda anlamı açıktır

bool IsValidPercentage(int x) => x is >= 0 and <= 100;

Ancak giriş bu kadar temel bir tür olmadığında, bunu hangi türe dönüştürmeyi deneriz?

bool IsValidPercentage(object x) => x is >= 0 and <= 100;

Giriş türü zaten karşılaştırılabilir bir ilkel olduğunda karşılaştırmanın türü olduğunu önerdik. Bununla birlikte, giriş karşılaştırılabilir bir ilkel olmadığında ilişkisel, ilişkiselin sağ tarafındaki sabitin türüne örtük tür testi dahil etmek gibi davranırız. Programcı birden fazla giriş türünü desteklemeyi planlıyorsa, bunun açıkça yapılması gerekir:

bool IsValidPercentage(object x) => x is
    >= 0 and <= 100 or    // integer tests
    >= 0F and <= 100F or  // float tests
    >= 0D and <= 100D;    // double tests

Sonuç: İlişkisel, ilişkiselin sağ tarafındaki sabitin türüne örtük bir tür testi içerir.

Akış türü bilgileri sağdan sola and

Bir and birleştirici yazdığınızda, en üst düzey tür hakkında solda öğrenilen tür bilgilerinin sağa akabileceği önerilmiştir. Örneğin:

bool isSmallByte(object o) => o is byte and < 100;

Burada, ikinci desene giriş türü, öğesinin solundaki and gereksinimlerine göre daraltılır. Tüm desenler için tür daraltma semantiğini aşağıdaki gibi tanımlarız. Bir P aşağıdaki gibi tanımlanır:

  1. Tür deseni ise P , daraltılmış tür , tür deseninin türünün türüdür.
  2. Bir bildirim deseni ise P , daraltılmış tür , bildirim deseninin türünün türüdür.
  3. Açık bir tür veren özyinelemeli bir desense P , daraltılmış tür bu türdür.
  4. için kurallarıyla P eşleştirilirseITuple, daraltılmış tür türüdür.System.Runtime.CompilerServices.ITuple
  5. Sabitin null sabit olmadığı ve ifadenin giriş türüneP sabit ifade dönüştürmesinin olmadığı sabit bir desense, daraltılmış tür sabitin türüdür.
  6. Sabit ifadenin giriş türüne Psabit ifade dönüştürmesinin olmadığı ilişkisel bir desense, daraltılmış tür sabitin türüdür.
  7. Bir desen iseP, daraltılmış türorböyle bir ortak tür varsa, daraltılmış alt ekran türlerinin ortak türüdür. Bu amaçla, ortak tür algoritması yalnızca kimlik, kutulama ve örtük başvuru dönüştürmelerini dikkate alır ve bir desen dizisinin or tüm alt desenlerini (parantezli desenleri yoksayarak) dikkate alır.
  8. Bir desenseP, and doğru desenin daraltılmış türüdür. Ayrıca, sol desenin dar türü , sağ desenin giriş türüdür .
  9. Aksi takdirde daraltılmış türüPP' nin giriş türüdür.

Sonuç: Yukarıdaki daraltma semantiği uygulanmıştır.

Değişken tanımları ve kesin atama

ve or desenlerinin not eklenmesi, desen değişkenleri ve kesin atama ile ilgili bazı ilginç yeni sorunlar oluşturur. Değişkenler normalde en fazla bir kez bildirilebildiği için, desen eşleştiğinde desenin bir tarafında bildirilen herhangi bir or desen değişkeni kesinlikle atanmayacak gibi görünür. Benzer şekilde, bir desen içinde bildirilen bir not değişkenin, desen eşleştiğinde kesinlikle atanması beklenemez. Bu sorunu çözmenin en basit yolu, bu bağlamlarda desen değişkenlerini bildirmeyi yasaklamamaktır. Ancak, bu çok kısıtlayıcı olabilir. Dikkate alınması gereken başka yaklaşımlar da vardır.

Dikkate alınması gereken senaryolardan biri şudur:

if (e is not int i) return;
M(i); // is i definitely assigned here?

Bir is-pattern-ifadesi için desen değişkenleri yalnızca is-pattern-ifadesi true olduğunda ("kesinlikle true olduğunda atanır") kesinlikle atanmış olarak kabul edildiğinden, bu işlem bugün çalışmaz.

Bunu desteklemek, bir olumsuz koşul deyimi için if destek eklemekten daha basit olacaktır (programcı açısından bakıldığında). Böyle bir destek eklesek bile programcılar yukarıdaki kod parçacığının neden çalışmadığını merak eder. Öte yandan, programda switchfalse olduğunda kesinlikle atanmış olan karşılık gelen bir nokta olmadığından, aynı senaryo daha az anlamlıdır. Desenlere izin verilen diğer bağlamlarda değil de bir is-pattern-ifadesinde buna izin verir miyiz? Bu düzensiz görünüyor.

Bu durumla ilgili olarak, ayrık desende kesin atama sorunu vardır.

if (e is 0 or int i)
{
    M(i); // is i definitely assigned here?
}

Yalnızca giriş sıfır olmadığında kesinlikle atanmasını bekleriz i . Ancak girişin bloğun i içinde sıfır olup olmadığını bilmediğimiz için kesinlikle atanmamış. Ancak, karşılıklı olarak dışlayan farklı desenlerde bildirilmesine izin i verirsek ne olur?

if ((e1, e2) is (0, int i) or (int i, 0))
{
    M(i);
}

Burada değişken i kesinlikle bloğun içinde atanır ve sıfır öğe bulunduğunda tanımlama grubunun diğer öğesinden değerini alır.

Ayrıca, bir büyük/küçük harf bloğunun her örneğinde değişkenlerin tanımlanmasına (çarpma) izin vermek de önerilmiştir:

    case (0, int x):
    case (int x, 0):
        Console.WriteLine(x);

Bu çalışmalardan herhangi birini yapmak için, bu tür birden çok tanımın nerede ve hangi koşullarda bu tür bir değişkenin kesinlikle atanmış olarak kabul edildiğini dikkatlice tanımlamamız gerekir.

Bu çalışmayı daha sonraya ertelemeyi seçersek (tavsiye ederim), C# 9'da söyleyebiliriz

  • not veya oraltında desen değişkenleri bildirilmeyebilir.

Ardından, daha sonra rahatlamanın olası değeri hakkında içgörü sağlayacak bazı deneyimler geliştirmek için zamanımız olur.

Sonuç: Desen değişkenleri veya notor deseni altında bildirilemiyor.

Tanılama, alt yordam ve kapsamlılık

Bu yeni desen formları, tanınabilir programcı hatası için birçok yeni fırsat sağlar. Hangi tür hataları tanılayacağımıza ve bunu nasıl yapacağımıza karar vermemiz gerekir. Aşağıda bazı örnekler verilmiştir:

case >= 0 and <= 100D:

Bu durum hiçbir zaman eşleşmez (çünkü giriş hem hem intdoublede bir olamaz). Hiçbir zaman eşleşmeyen bir servis talebi algıladığımızda zaten bir hatayla karşılaşıyoruz, ancak sözcüğü ("Anahtar büyük/küçük harf zaten önceki bir durum tarafından işlendi" ve "Desen, anahtar ifadesinin önceki bir kolu tarafından işlendi") yeni senaryolarda yanıltıcı olabilir. Desenin hiçbir zaman girişle eşleşmediğini söylemek için sözcüğü değiştirmemiz gerekebilir.

case 1 and 2:

Benzer şekilde, bu bir hata olabilir çünkü bir değer hem hem de 12olamaz.

case 1 or 2 or 3 or 1:

Bu durum eşleşmesi mümkündür, ancak or 1 sonundaki desene hiçbir anlam eklemez. Bir bileşik desenin herhangi bir konjonktürü veya ayrıştırması bir desen değişkeni tanımlamadığı veya eşleşen değer kümesini etkilemediği her durumda hata üretmeyi hedeflememiz gerektiğini düşünüyorum.

case < 2: break;
case 0 or 1 or 2 or 3 or 4 or 5: break;

Burada, 0 or 1 or bu değerler ilk servis talebi tarafından işlendiği için ikinci büyük/küçük harfe hiçbir şey eklenmez. Bu da bir hatayı hak ediyor.

byte b = ...;
int x = b switch { <100 => 0, 100 => 1, 101 => 2, >101 => 3 };

Bunun gibi bir anahtar ifadesi kapsamlı olarak değerlendirilmelidir (tüm olası giriş değerlerini işler).

C# 8.0'da, türü byte girişi olan bir switch ifadesi yalnızca deseni her şeyle eşleşen son bir kol içeriyorsa (atma deseni veya var-desen) kapsamlı olarak kabul edilir. Her ayrı byte değer için bir kolu olan bir switch ifadesi bile C# 8'de kapsamlı olarak kabul edilmez. İlişkisel desenlerin kapsamlılığını düzgün bir şekilde ele almak için bu olayı da ele almak zorundayız. Bu teknik olarak hataya neden olacak, ancak hiçbir kullanıcının fark etme olasılığı yoktur.