C # 8 null olmayan referanslar ve Dene deseni


23

İle örneklenen, C # sınıflarında bir model vardır Dictionary.TryGetValueve int.TryParse: bir yöntem olup döner bir işlem ve gerçek sonucu içeren bir takım parametre başarısını belirten bir Boole; işlem başarısız olursa, out parametresi null değerine ayarlanır.

C # 8'e bağlanamaz referansları kullandığımı ve kendi sınıfım için bir TryParse yöntemi yazmak istediğimi varsayalım. Doğru imza şudur:

public static bool TryParse(string s, out MyClass? result);

Sonuç yanlış durumda boş olduğundan, çıkış değişkeninin boş olarak işaretlenmesi gerekir.

Ancak, Try paterni genellikle şöyle kullanılır:

if (MyClass.TryParse(s, out var result))
{
  // use result here
}

Sadece işlem başarılı olduğunda şubeye girdiğim için, sonuç o dalda asla boş olmamalıdır. Fakat onu null olarak işaretleyebildiğim için şimdi kontrol etmem ya da !geçersiz kılmak için kullanmam gerekiyor:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // compiler warning, could be null
  Console.WriteLine("Look: {0}", result!.SomeProperty); // need override
}

Bu çirkin ve biraz unergonomic.

Tipik kullanım şekli nedeniyle, başka bir seçeneğim var: sonuç türü hakkında yalan:

public static bool TryParse(string s, out MyClass result) // not nullable
{
   // Happy path sets result to non-null and returns true.
   // Error path does this:
   result = null!; // override compiler complaint
   return false;
}

Şimdi tipik kullanım daha da güzelleşiyor:

if (MyClass.TryParse(s, out var result))
{
  Console.WriteLine("Look: {0}", result.SomeProperty); // no warning
}

ancak atipik kullanım aşağıdakileri yapması gerektiği uyarısını almaz:

else
{
  Console.WriteLine("Fail: {0}", result.SomeProperty);
  // Yes, result is in scope here. No, it will never be non-null.
  // Yes, it will throw. No, the compiler won't warn about it.
}

Şimdi hangi yoldan gideceğimi bilmiyorum. C # dil ekibinden resmi bir öneri var mı? Bana bunun nasıl yapılacağını gösterebilecek null edilemeyen referanslara dönüştürülmüş herhangi bir CoreFX kodu var mı? ( TryParseYöntemleri aramaya başladım . IPAddressBiri olan bir sınıftır, fakat corefx'in ana dalına dönüştürülmedi.)

Peki genel kod Dictionary.TryGetValuebununla nasıl başa çıkıyor? (Muhtemelen MaybeNullbulduğum şeyden özel bir nitelik ile.) DictionaryNULL olmayan bir değer türünü başlattığımda ne olur ?


Denemedim (bu yüzden bunu bir cevap olarak yazmıyorum), ancak switch deyimlerinin yeni desen eşleştirme özelliğiyle, bir seçeneğin basitçe geri döndürülebilir referansı (Try-modeli yok) döndürmek olduğunu tahmin ediyorum. return MyClass?), ve üzerinde bir case MyClass myObj:(ve isteğe bağlı) ile bir anahtar yapın case null:.
Filip Milovanović

+1 Bu soruyu gerçekten beğendim ve bununla çalışmak zorunda kaldığımda her zaman geçersiz kılma yerine fazladan boş bir denetim kullandım - bu da her zaman biraz gereksiz ve inelegant hissettiriyordu, ancak hiçbir zaman performans kritik kodunda değildi. bu yüzden sadece gitmesine izin verdim. Kullanmanın daha temiz bir yolu olup olmadığını görmek güzel olurdu!
BrianH

Yanıtlar:


10

Bool / out-var deseni, tanımladığınız gibi null olan referans türleri ile iyi çalışmaz. Derleyici ile mücadele etmek yerine, özellikleri basitleştirmek için özelliğini kullanın. Gelişmiş kalıp eşleştirme özelliklerini C # 8 ile atın ve null olan bir referansı "fakir bir adamın belki de türü" olarak kabul edebilirsiniz:

public static MyClass? TryParse(string s) => 



if (TryParse(someString) is {} myClass)
{
    // myClass wasn't null, we are good to use it
}

Bu şekilde outparametrelerle uğraşmaktan kaçınırsınız ve derleyici nullile null edilemeyen referanslarla karıştırmak için mücadele etmek zorunda kalmazsınız .

Peki genel kod Dictionary.TryGetValuebununla nasıl başa çıkıyor?

Bu noktada, o "zavallı adam belki de" düştü. Karşılaşacağınız zorluk, nULL olabilecek referans türleri (NRT) kullanıldığında, derleyicinin Foo<T>null olmayan gibi davranmasıdır . Ama denemek ve onu değiştirmek Foo<T?>ve Tbir sınıf veya yapıya sınırlandırılabilir değer türleri olarak sınırlandırılmasının CLR'nin bakış açısından çok farklı bir şey olmasını isteyecektir . Bunun için çok çeşitli çalışma var:

  1. NRT özelliğini etkinleştirmeyin,
  2. Kodunuz boş değerlere imza atmasa da parametreler için default(birlikte !) kullanmaya başlayın out,
  3. Maybe<T>Döndürülen değer olarak gerçek bir tür kullanın ; bu değer asla o zaman olmaz nullve onu bool, out Tiçine HasValueveya Valueözelliklerini veya
  4. Bir tuple kullanın:
public static (bool success, T result) TryParse<T>(string s) => 


if (TryParse<MyClass>(someString) is (true, var result))
{
    // result is valid here, as success is true
}

Ben şahsen kullanmayı tercih ediyorum Maybe<T>ancak bir yapıyı desteklemesini sağlıyorum, böylece yukarıda 4'te gösterildiği gibi bir desen olarak eşleştirilebiliyor.


2
TryParse(someString) is {} myClass- Bu sözdizimine alışmak biraz zaman alacak ama ben bu fikri sevdim.
Sebastian Redl

TryParse(someString) is var myClassbana daha kolay görünüyor.
Olivier Jacot-Descombes

2
@ OlivierJacot-Descombes daha kolay görünebilir ... ama işe yaramaz. Araba düzeni her zaman eşleşir, bu yüzden boş x is var yolsun xveya olmasın her zaman doğru olacaktır .
David Arno

15

Eğer biraz geç, benim gibi bu gidiyorlar, bu .NET ekibi gibi parametre ayrıntıların bir demet aracılığıyla ele çıkıyor MaybeNullWhen(returnValue: true)içinde System.Diagnostics.CodeAnalysistry modeli için kullanabileceğiniz alanı.

Örneğin:

Dictionary.TryGetValue gibi genel kod bununla nasıl başa çıkar?

bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value);

kontrol edemezseniz, o da bağırdı demektir. true

// This is okay:
if(myDictionary.TryGetValue("cheese", out var result))
{
  var more = result * 42;
}

// But this is not:
_ = myDictionary.TryGetValue("cheese", out var result);
var more = result * 42;
// "CS8602: Dereference of a potentially null reference"

Daha fazla ayrıntı:


3

Burada bir çatışma olduğunu sanmıyorum.

itirazın

public static bool TryParse(string s, out MyClass? result);

olduğu

Sadece işlem başarılı olduğunda şubeye girdiğim için, sonuç hiçbir zaman o dalda boş olmamalıdır.

Ancak, aslında eski stil TryParse işlevlerinde null atamasını out parametresine engelleyen hiçbir şey yoktur.

Örneğin.

MyJsonObject.TryParse("null", out obj) //sets obj to a null MyJsonObject and returns true

Programcıya, out parametresini kontrol etmeden kullandıklarında verilen uyarı doğrudur. Kontrol etmelisin!

Kodun ana dalının silinemez bir tür döndürdüğü null türlerini döndürmek zorunda kalacağınız birçok durum olacak. Uyarı, bu açık yapmanıza yardımcı olmak için sadece orada. yani.

MyClass? x = (new List<MyClass>()).FirstOrDefault(i=>i==1);

Kodlanmanın null olmayan yolu, boş bir yere sahip olacak bir istisna atar. Ayrıştırma, alma veya ilk yapma

MyClass x = (new List<MyClass>()).First(i=>i==1);

Sanmıyorum FirstOrDefaultonun dönüş değerinin nullness çünkü karşılaştırılabilir olduğunu ana sinyali. Olarak TryParseyöntem, dönüş değeri doğruysa boş olmayan üzerinden parametre yöntemi sözleşmesinin bir parçasıdır.
Sebastian Redl

sözleşmenin bir parçası değil. güvence altına alınan tek şey bir şeyin out parametresine atandığıdır
Ewan

Bir TryParseyöntemden beklediğim davranış bu . Eğer IPAddress.TryParsehiç gerçek döndü ama onun dışında parametresine boş olmayan atama vermedi, ben bir hata olarak rapor ederim.
Sebastian Redl

Beklentiniz anlaşılabilir ancak derleyici tarafından zorlanmadı. Öyleyse, IpAddress'in belirtimi asla doğru ve null döndürmeyebilir diyebilir, ancak JsonObject örneğim null döndürmenin doğru olabileceği bir durumu gösteriyor
Ewan

"Beklentiniz anlaşılabilir ancak derleyici tarafından zorlanmadı." - Biliyorum, ancak sorum, aklımdaki sözleşmeyi ifade etmek için kodumu en iyi nasıl yazacağım, başka bir kodun sözleşmesinin ne olduğu değil.
Sebastian Redl
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.