Pernahkah Anda ingin menyingkirkan masalah dereferensi referensi nol? Jika demikian, maka menggunakan jenis Referensi Nullable bukanlah pilihan Anda. Kenapa ya? Inilah yang akan dibahas hari ini.
Kami memperingatkan dan itu terjadi. Sekitar setahun yang lalu, kolega saya menulis artikel di mana mereka memperingatkan bahwa memperkenalkan jenis Referensi Nullable tidak akan melindungi dari dereferensi referensi nol. Kami sekarang memiliki konfirmasi nyata atas kata-kata kami, yang ditemukan di kedalaman Roslyn.
Jenis Referensi Nullable
Ide untuk menambahkan jenis Referensi Nullable (selanjutnya - NR) tampaknya menarik bagi saya, karena masalah yang terkait dengan referensi nol dereferensi relevan hingga hari ini. Penerapan perlindungan terhadap dereferencing sangat tidak dapat diandalkan. Seperti yang direncanakan oleh pembuatnya, asumsikan nilai null hanya dapat berupa variabel yang tipenya ditandai dengan "?". Misalnya, variabel tipe string? mengatakan bahwa itu dapat berisi null , jenis string - sebaliknya.
Namun, tidak ada yang melarang kami meneruskan variabel referensi null ke non-nullable.(selanjutnya - NNR), karena tidak diterapkan pada level kode IL. Penganalisis statis yang dibangun ke dalam kompiler bertanggung jawab atas batasan ini. Oleh karena itu, inovasi ini lebih bersifat penasehat. Berikut contoh sederhana untuk menunjukkan cara kerjanya:
#nullable enable
object? nullable = null;
object nonNullable = nullable;
var deref = nonNullable.ToString();
Seperti yang bisa kita lihat, tipe nonNullable ditetapkan sebagai NNR, tetapi kita bisa meneruskan null dengan aman di sana . Tentu saja, kita akan mendapat peringatan tentang mengonversi "Mengonversi literal nol atau kemungkinan nilai nol ke jenis non-nullable." Namun, ini dapat dielakkan dengan menambahkan sedikit agresi:
#nullable enable
object? nullable = null;
object nonNullable = nullable!; // <=
var deref = nonNullable.ToString();
Satu tanda seru dan tidak ada peringatan. Jika salah satu dari Anda adalah seorang ahli kuliner, maka pilihan lain tersedia:
#nullable enable
object nonNullable = null!;
var deref = nonNullable.ToString();
Nah, satu contoh lagi. Mari buat dua proyek konsol sederhana. Di bagian pertama, kami menulis:
namespace NullableTests
{
public static class Tester
{
public static string RetNull() => null;
}
}
Yang kedua, kami menulis:
#nullable enable
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string? nullOrNotNull = NullableTests.Tester.RetNull();
System.Console.WriteLine(nullOrNotNull.Length);
}
}
}
Arahkan kursor ke nullOrNotNull dan lihat pesan berikut:
Kami diberitahu bahwa string tidak boleh null di sini . Namun, kami memahami bahwa ini akan menjadi nol di sini . Kami memulai proyek dan mendapatkan pengecualian:
Tentu saja, ini hanyalah contoh sintetik, yang tujuannya adalah untuk menunjukkan bahwa pengantar ini tidak menjamin perlindungan Anda terhadap dereferensi referensi nol. Jika Anda berpikir bahwa sintetis itu membosankan, dan di mana ada contoh nyata sama sekali, maka saya meminta Anda untuk tidak khawatir, maka semua ini akan terjadi.
Jenis NR memiliki masalah lain - tidak jelas apakah termasuk atau tidak. Misalnya, solusinya memiliki dua proyek. Satu di-markup dengan sintaks ini, dan yang lainnya tidak. Setelah memasuki proyek dengan tipe NR, Anda dapat memutuskan bahwa setelah satu ditandai, maka semua akan ditandai. Namun, ini tidak akan menjadi masalah. Ternyata Anda perlu melihat setiap kali apakah konteks nullable disertakan dalam proyek atau file. Jika tidak, Anda mungkin salah mengira bahwa tipe referensi normal adalah NNR.
Bagaimana bukti itu ditemukan
Saat mengembangkan diagnostik baru di penganalisis PVS-Studio, kami selalu mengujinya berdasarkan proyek nyata kami. Ini membantu dalam berbagai aspek. Misalnya:
- lihat "hidup" pada kualitas peringatan yang diterima;
- singkirkan beberapa positif palsu;
- temukan poin-poin menarik dalam kode, yang kemudian dapat Anda bicarakan;
- dll.
Salah satu diagnostik V3156 baru telah menemukan tempat di mana pengecualian dapat dilemparkan karena potensi nol . Kata-kata dari aturan diagnostik adalah: "Argumen metode tidak diharapkan menjadi nol". Intinya adalah bahwa metode tersebut tidak mengharapkan null , nilainya dapat dilewatkan sebagai argumen ke null . Hal ini dapat menyebabkan, misalnya, pengecualian atau eksekusi yang salah dari metode yang dipanggil. Anda dapat membaca lebih lanjut tentang aturan diagnostik ini di sini .
Bukti di sini
Jadi kita sampai ke bagian utama artikel ini. Di sini Anda akan melihat fragmen kode nyata dari proyek Roslyn, di mana diagnostik mengeluarkan peringatan. Arti utamanya adalah bahwa tipe NNR diberikan null , atau tidak ada pemeriksaan untuk nilai tipe NR. Semua ini dapat menyebabkan pengecualian dilemparkan.
Contoh 1
private static Dictionary<object, SourceLabelSymbol>
BuildLabelsByValue(ImmutableArray<LabelSymbol> labels)
{
....
object key;
var constantValue = label.SwitchCaseLabelConstant;
if ((object)constantValue != null && !constantValue.IsBad)
{
key = KeyForConstant(constantValue);
}
else if (labelKind == SyntaxKind.DefaultSwitchLabel)
{
key = s_defaultKey;
}
else
{
key = label.IdentifierNodeOrToken.AsNode();
}
if (!map.ContainsKey(key)) // <=
{
map.Add(key, label);
}
....
}
V3156 Argumen pertama metode 'ContainsKey' tidak diharapkan null. Potensi nilai null: key. SwitchBinder.cs 121
Pesan tersebut menyatakan bahwa kunci potensial null . Mari kita lihat di mana variabel ini bisa mendapatkan nilai seperti itu. Mari kita periksa metode KeyForConstant terlebih dahulu :
protected static object KeyForConstant(ConstantValue constantValue)
{
Debug.Assert((object)constantValue != null);
return constantValue.IsNull ? s_nullKey : constantValue.Value;
}
private static readonly object s_nullKey = new object();
Karena s_nullKey bukan null , mari kita lihat apa yang dikembalikan constantValue.Value :
public object? Value
{
get
{
switch (this.Discriminator)
{
case ConstantValueTypeDiscriminator.Bad: return null; // <=
case ConstantValueTypeDiscriminator.Null: return null; // <=
case ConstantValueTypeDiscriminator.SByte: return Boxes.Box(SByteValue);
case ConstantValueTypeDiscriminator.Byte: return Boxes.Box(ByteValue);
case ConstantValueTypeDiscriminator.Int16: return Boxes.Box(Int16Value);
....
default: throw ExceptionUtilities.UnexpectedValue(this.Discriminator);
}
}
}
Ada dua literal nol di sini, tetapi dalam kasus ini kami tidak akan membahas kasus apa pun dengannya. Ini karena pemeriksaan IsBad dan IsNull . Namun, saya ingin menarik perhatian Anda ke jenis pengembalian properti ini. Ini adalah tipe NR, tetapi metode KeyForConstant sudah mengembalikan tipe NNR. Ternyata secara umum, metode KeyForConstant dapat mengembalikan null . Sumber lain yang dapat mengembalikan null adalah metode AsNode :
public SyntaxNode? AsNode()
{
if (_token != null)
{
return null;
}
return _nodeOrParent;
}
Sekali lagi, harap perhatikan jenis kembalian dari metode tersebut - ini adalah jenis NR. Ternyata ketika kami mengatakan bahwa null dapat dikembalikan dari metode ini, ini tidak memengaruhi apa pun. Sangat menarik bahwa kompilator tidak bersumpah untuk mengubah dari NR ke NNR di sini:
Contoh 2
private SyntaxNode CopyAnnotationsTo(SyntaxNode sourceTreeRoot,
SyntaxNode destTreeRoot)
{
var nodeOrTokenMap = new Dictionary<SyntaxNodeOrToken,
SyntaxNodeOrToken>();
....
if (sourceTreeNodeOrTokenEnumerator.Current.IsNode)
{
var oldNode = destTreeNodeOrTokenEnumerator.Current.AsNode();
var newNode = sourceTreeNodeOrTokenEnumerator.Current.AsNode()
.CopyAnnotationsTo(oldNode);
nodeOrTokenMap.Add(oldNode, newNode); // <=
}
....
}
V3156 Argumen pertama dari metode 'Tambah' tidak diharapkan menjadi nol. Nilai potensial null: oldNode. SyntaxAnnotationTests.cs 439
Contoh lain dengan fungsi AsNode dijelaskan di atas. Hanya kali ini oldNode akan berjenis NR. Padahal kunci di atas berjenis NNR.
Ngomong-ngomong, saya tidak bisa tidak berbagi dengan Anda pengamatan yang menarik. Seperti yang saya jelaskan di atas, saat mengembangkan diagnostik, kami mengujinya pada proyek yang berbeda. Saat memeriksa sisi positif dari aturan ini, momen aneh diperhatikan. Sekitar 70% dari semua peringatan dikeluarkan untuk metode kelas Kamus . Selain itu, kebanyakan dari mereka jatuh pada metode TryGetValue... Mungkin hal ini disebabkan fakta bahwa secara tidak sadar kita tidak mengharapkan pengecualian dari metode yang mengandung kata coba . Jadi periksa kode Anda untuk pola ini untuk melihat apakah Anda menemukan yang serupa.
Contoh 3
private static SymbolTreeInfo TryReadSymbolTreeInfo(
ObjectReader reader,
Checksum checksum,
Func<string, ImmutableArray<Node>,
Task<SpellChecker>> createSpellCheckerTask)
{
....
var typeName = reader.ReadString();
var valueCount = reader.ReadInt32();
for (var j = 0; j < valueCount; j++)
{
var containerName = reader.ReadString();
var name = reader.ReadString();
simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
new ExtensionMethodInfo(containerName, name));
}
....
}
V3156 Argumen pertama dari metode 'Tambah' diteruskan sebagai argumen ke metode 'TryGetValue' dan tidak diharapkan menjadi nol. Nilai potensial nol: typeName. SymbolTreeInfo_Serialization.cs 255
Penganalisis mengatakan bahwa masalahnya terletak pada typeName . Pertama-tama, pastikan bahwa argumen ini memang berpotensi null . Kami melihat ReadString :
public string ReadString() => ReadStringValue();
Jadi, lihat ReadStringValue :
private string ReadStringValue()
{
var kind = (EncodingKind)_reader.ReadByte();
return kind == EncodingKind.Null ? null : ReadStringValue(kind);
}
Hebat, sekarang mari kita segarkan ingatan kita dengan melihat ke mana variabel kita diteruskan:
simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
new ExtensionMethodInfo(containerName,
name));
Saya pikir sudah waktunya untuk masuk ke dalam metode Tambah :
public bool Add(K k, V v)
{
ValueSet updated;
if (_dictionary.TryGetValue(k, out ValueSet set)) // <=
{
....
}
....
}
Memang, jika null diteruskan ke metode Add sebagai argumen pertama , maka kita akan mendapatkan ArgumentNullException . Ngomong-ngomong, menarik bahwa jika kita mengarahkan kursor ke typeName di Visual Studio , kita akan melihat bahwa tipenya adalah string? :
Dalam kasus ini, tipe kembalian dari metode ini hanyalah string :
Dalam hal ini, jika Anda selanjutnya membuat variabel tipe NNR dan menetapkannya typeName , maka tidak ada kesalahan yang akan ditampilkan.
Mari kita coba menjatuhkan Roslyn
Bukan untuk niat jahat, tapi untuk kesenangan, saya sarankan mencoba mereproduksi salah satu contoh yang ditunjukkan.
Tes 1
Mari kita ambil contoh yang dijelaskan di bawah nomor 3:
private static SymbolTreeInfo TryReadSymbolTreeInfo(
ObjectReader reader,
Checksum checksum,
Func<string, ImmutableArray<Node>,
Task<SpellChecker>> createSpellCheckerTask)
{
....
var typeName = reader.ReadString();
var valueCount = reader.ReadInt32();
for (var j = 0; j < valueCount; j++)
{
var containerName = reader.ReadString();
var name = reader.ReadString();
simpleTypeNameToExtensionMethodMap.Add(typeName, // <=
new ExtensionMethodInfo(containerName, name));
}
....
}
Untuk mereproduksinya, Anda perlu memanggil metode TryReadSymbolTreeInfo , tetapi ini bersifat pribadi . Ada baiknya kelas dengannya memiliki metode ReadSymbolTreeInfo_ForTestingPurposesOnly , yang sudah internal :
internal static SymbolTreeInfo ReadSymbolTreeInfo_ForTestingPurposesOnly(
ObjectReader reader,
Checksum checksum)
{
return TryReadSymbolTreeInfo(reader, checksum,
(names, nodes) => Task.FromResult(
new SpellChecker(checksum,
nodes.Select(n => new StringSlice(names,
n.NameSpan)))));
}
Sangat menyenangkan bahwa kami ditawari secara langsung untuk menguji metode TryReadSymbolTreeInfo . Oleh karena itu, mari buat kelas kita berdampingan dan tulis kode berikut:
public class CheckNNR
{
public static void Start()
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
writer.Write((byte)170);
writer.Write((byte)9);
writer.Write((byte)0);
writer.Write(0);
writer.Write(0);
writer.Write(1);
writer.Write((byte)0);
writer.Write(1);
writer.Write((byte)0);
writer.Write((byte)0);
stream.Position = 0;
using var reader = ObjectReader.TryGetReader(stream);
var checksum = Checksum.Create("val");
SymbolTreeInfo.ReadSymbolTreeInfo_ForTestingPurposesOnly(reader, checksum);
}
}
Sekarang kami mengumpulkan Roslyn , membuat aplikasi konsol sederhana, menghubungkan semua file dll yang diperlukan dan menulis kode berikut:
static void Main(string[] args)
{
CheckNNR.Start();
}
Kami mulai, kami mencapai tempat yang diperlukan dan melihat:
Selanjutnya, buka metode Add dan dapatkan pengecualian yang diharapkan:
Izinkan saya mengingatkan Anda bahwa metode ReadString mengembalikan tipe NNR, yang, menurut desain, tidak dapat berisi null . Contoh ini sekali lagi menegaskan relevansi aturan diagnostik PVS-Studio untuk mencari dereferensi referensi nol.
Tes 2
Nah, karena kita sudah mulai mereproduksi contoh, mengapa tidak mereproduksi satu lagi. Contoh ini tidak akan terkait dengan jenis NR. Namun, diagnosis V3156 yang sama menemukannya, dan saya ingin memberi tahu Anda tentangnya. Ini kodenya:
public SyntaxToken GenerateUniqueName(SemanticModel semanticModel,
SyntaxNode location,
SyntaxNode containerOpt,
string baseName,
CancellationToken cancellationToken)
{
return GenerateUniqueName(semanticModel,
location,
containerOpt,
baseName,
filter: null,
usedNames: null, // <=
cancellationToken);
}
V3156 Argumen keenam dari metode 'GenerateUniqueName' diteruskan sebagai argumen ke metode 'Concat' dan tidak diharapkan menjadi null. Potensi nilai nol: null. AbstractSemanticFactsService.cs 24
Saya akan jujur: ketika membuat diagnostik ini, saya tidak benar-benar mengharapkan positif pada garis lurus null . Lagi pula, agak aneh mengirim null ke metode yang akan mengeluarkan pengecualian karena ini. Meskipun saya telah melihat tempat-tempat di mana hal ini dibenarkan (misalnya, dengan kelas Expression ), tetapi sekarang bukan tentang itu.
Oleh karena itu, saya sangat tergugah ketika melihat peringatan ini. Mari kita lihat apa yang terjadi dalam metode GenerateUniqueName .
public SyntaxToken GenerateUniqueName(SemanticModel semanticModel,
SyntaxNode location,
SyntaxNode containerOpt,
string baseName,
Func<ISymbol, bool> filter,
IEnumerable<string> usedNames,
CancellationToken cancellationToken)
{
var container = containerOpt ?? location
.AncestorsAndSelf()
.FirstOrDefault(a => SyntaxFacts.IsExecutableBlock(a)
|| SyntaxFacts.IsMethodBody(a));
var candidates = GetCollidableSymbols(semanticModel,
location,
container,
cancellationToken);
var filteredCandidates = filter != null ? candidates.Where(filter)
: candidates;
return GenerateUniqueName(baseName,
filteredCandidates.Select(s => s.Name)
.Concat(usedNames)); // <=
}
Kami melihat bahwa hanya ada satu jalan keluar dari metode ini, tidak ada pengecualian yang dilempar, dan tidak ada goto . Dengan kata lain, tidak ada yang mencegah Anda meneruskan usedNames ke metode Concat dan mendapatkan ArgumentNullException .
Tapi ini semua hanya kata-kata, ayo lakukan. Untuk melakukan ini, lihat di mana Anda dapat memanggil metode ini. Metodenya sendiri ada di kelas AbstractSemanticFactsService . Kelasnya abstrak, jadi untuk kenyamanan, mari kita ambil kelas CSharpSemanticFactsService , yang mewarisi darinya. Di file kelas ini, kita akan membuatnya sendiri, yang akan memanggil metode GenerateUniqueName . Ini terlihat seperti ini:
public class DropRoslyn
{
private const string ProgramText =
@"using System;
using System.Collections.Generic;
using System.Text
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
public void Drop()
{
var tree = CSharpSyntaxTree.ParseText(ProgramText);
var instance = CSharpSemanticFactsService.Instance;
var compilation = CSharpCompilation
.Create("Hello World")
.AddReferences(MetadataReference
.CreateFromFile(typeof(string)
.Assembly
.Location))
.AddSyntaxTrees(tree);
var semanticModel = compilation.GetSemanticModel(tree);
var syntaxNode1 = tree.GetRoot();
var syntaxNode2 = tree.GetRoot();
var baseName = "baseName";
var cancellationToken = new CancellationToken();
instance.GenerateUniqueName(semanticModel,
syntaxNode1,
syntaxNode2,
baseName,
cancellationToken);
}
}
Sekarang kami mengumpulkan Roslyn, membuat aplikasi konsol sederhana, menghubungkan semua file dll yang diperlukan dan menulis kode berikut:
class Program
{
static void Main(string[] args)
{
DropRoslyn dropRoslyn = new DropRoslyn();
dropRoslyn.Drop();
}
}
Kami meluncurkan aplikasi dan mendapatkan yang berikut:
Ini menyesatkan
Katakanlah kita setuju dengan konsep nullable. Ternyata jika kita melihat tipe NR, maka kita yakin bahwa itu bisa mengandung potensi null . Namun, terkadang Anda dapat melihat situasi ketika kompilator memberi tahu kami sebaliknya. Oleh karena itu, di sini akan dipertimbangkan beberapa kasus di mana penggunaan konsep ini tidak intuitif.
Kasus 1
internal override IEnumerable<SyntaxToken>? TryGetActiveTokens(SyntaxNode node)
{
....
var bodyTokens = SyntaxUtilities
.TryGetMethodDeclarationBody(node)
?.DescendantTokens();
if (node.IsKind(SyntaxKind.ConstructorDeclaration,
out ConstructorDeclarationSyntax? ctor))
{
if (ctor.Initializer != null)
{
bodyTokens = ctor.Initializer
.DescendantTokens()
.Concat(bodyTokens); // <=
}
}
return bodyTokens;
}
V3156 Argumen pertama dari metode 'Concat' diharapkan tidak null. Nilai potensial nol: bodyTokens. CSharpEditAndContinueAnalyzer.cs 219 Mari kita
lihat mengapa bodyTokens berpotensi null dan melihat operator bersyarat null :
var bodyTokens = SyntaxUtilities
.TryGetMethodDeclarationBody(node)
?.DescendantTokens(); // <=
Jika kita masuk ke metode TryGetMethodDeclarationBody , kita akan melihat bahwa itu bisa mengembalikan null . Namun, ini relatif besar, jadi saya meninggalkan tautan ke sana jika Anda ingin melihatnya sendiri. Dengan bodyTokens semuanya jelas, tetapi saya ingin menarik perhatian ke argumen ctor :
if (node.IsKind(SyntaxKind.ConstructorDeclaration,
out ConstructorDeclarationSyntax? ctor))
Seperti yang bisa kita lihat, tipenya disetel ke NR. Dalam kasus ini, baris di bawah ini adalah dereferensi:
if (ctor.Initializer != null)
Kombinasi ini agak mengkhawatirkan. Namun, Anda akan mengatakan bahwa, kemungkinan besar, jika IsKind mengembalikan true , maka ctor sudah pasti bukan null . Caranya adalah:
public static bool IsKind<TNode>(
[NotNullWhen(returnValue: true)] this SyntaxNode? node, // <=
SyntaxKind kind,
[NotNullWhen(returnValue: true)] out TNode? result) // <=
where TNode : SyntaxNode
{
if (node.IsKind(kind))
{
result = (TNode)node;
return true;
}
result = null;
return false;
}
Di sini, atribut khusus digunakan yang menunjukkan di nilai keluaran mana parameter tidak akan null . Kita bisa yakin akan hal ini dengan melihat logika metode IsKind . Ternyata di dalam kondisi tersebut, tipe ctor harus NNR. Kompilator memahami ini dan mengatakan bahwa ctor di dalam kondisi tidak akan null . Namun, untuk memahami ini bagi kita, kita harus masuk ke metode IsKind dan memperhatikan atribut di sana. Jika tidak, sepertinya mendereferensi variabel NR tanpa memeriksa null . Anda dapat mencoba menambahkan kejelasan seperti ini:
if (node.IsKind(SyntaxKind.ConstructorDeclaration,
out ConstructorDeclarationSyntax? ctor))
{
if (ctor!.Initializer != null) // <=
{
....
}
}
Kasus 2
public TextSpan GetReferenceEditSpan(InlineRenameLocation location,
string triggerText,
CancellationToken cancellationToken)
{
var searchName = this.RenameSymbol.Name;
if (_isRenamingAttributePrefix)
{
searchName = GetWithoutAttributeSuffix(this.RenameSymbol.Name);
}
var index = triggerText.LastIndexOf(searchName, // <=
StringComparison.Ordinal);
....
}
V3156 Argumen pertama dari metode 'LastIndexOf' diharapkan tidak null. Potensi nilai nol: searchName. AbstractEditorInlineRenameService.SymbolRenameInfo.cs 126
Kami tertarik dengan variabel searchName . null dapat ditulis ke sana setelah memanggil metode GetWithoutAttributeSuffix , tetapi tidak sesederhana itu. Mari kita lihat apa yang terjadi di dalamnya:
private string GetWithoutAttributeSuffix(string value)
=> value.GetWithoutAttributeSuffix(isCaseSensitive:
_document.GetRequiredLanguageService<ISyntaxFactsService>()
.IsCaseSensitive)!;
Mari kita bahas lebih dalam:
internal static string? GetWithoutAttributeSuffix(
this string name,
bool isCaseSensitive)
{
return TryGetWithoutAttributeSuffix(name, isCaseSensitive, out var result)
? result : null;
}
Ternyata metode TryGetWithoutAttributeSuffix akan mengembalikan hasil atau nol . Dan metode tersebut mengembalikan tipe NR. Namun, mundur selangkah, kami melihat bahwa tipe metode tiba-tiba berubah menjadi NNR. Ini terjadi karena tanda tersembunyi "!":
_document.GetRequiredLanguageService<ISyntaxFactsService>()
.IsCaseSensitive)!; // <=
Ngomong-ngomong, agak sulit untuk menyadarinya di Visual Studio:
Dengan menyediakannya, pengembang memberi tahu kita bahwa metode tersebut tidak akan pernah mengembalikan nol . Meskipun, melihat contoh sebelumnya dan masuk ke metode TryGetWithoutAttributeSuffix , secara pribadi saya tidak yakin tentang ini:
internal static bool TryGetWithoutAttributeSuffix(
this string name,
bool isCaseSensitive,
[NotNullWhen(returnValue: true)] out string? result)
{
if (name.HasAttributeSuffix(isCaseSensitive))
{
result = name.Substring(0, name.Length - AttributeSuffix.Length);
return true;
}
result = null;
return false;
}
Keluaran
Akhirnya, saya ingin mengatakan bahwa mencoba menyelamatkan kita dari pemeriksaan nol ekstra adalah ide bagus. Namun, tipe NR lebih bersifat penasehat, karena tidak ada yang secara tegas melarang kami untuk memberikan null ke tipe NNR. Itulah mengapa aturan PVS-Studio yang sesuai tetap relevan. Misalnya seperti V3080 atau V3156 .
Semua yang terbaik dan atas perhatiannya terima kasih.
Jika Anda ingin berbagi artikel ini dengan audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Nikolay Mironov. Referensi Nullable tidak akan melindungi Anda, dan ini buktinya .