Buggy C# Kodu: C# Programlamada En Yaygın 10 Hata

Yayınlanan: 2022-03-11

C Sharp Hakkında

C#, Microsoft Ortak Dil Çalışma Zamanı'nı (CLR) hedefleyen birkaç dilden biridir. CLR'yi hedefleyen diller, diller arası entegrasyon ve istisna işleme, gelişmiş güvenlik, bileşen etkileşimi için basitleştirilmiş bir model ve hata ayıklama ve profil oluşturma hizmetleri gibi özelliklerden yararlanır. Günümüzün CLR dillerinden C#, Windows masaüstü, mobil veya sunucu ortamlarını hedefleyen karmaşık, profesyonel geliştirme projeleri için en yaygın kullanılanıdır.

C#, nesne yönelimli, kesin olarak yazılmış bir dildir. C#'ta hem derleme hem de çalıştırma zamanlarında katı tip denetimi, tipik C# programlama hatalarının çoğunun mümkün olduğunca erken rapor edilmesine ve konumlarının oldukça doğru bir şekilde belirlenmesine neden olur. Bu, C Sharp programlamasında, tür güvenliği uygulamaları açısından daha serbest olan dillerde rahatsız edici işlem gerçekleştikten çok sonra meydana gelebilecek şaşırtıcı hataların nedenini bulmaya kıyasla çok zaman kazandırabilir. Ancak, birçok C# kodlayıcısı farkında olmadan (veya dikkatsizce) bu algılamanın faydalarını bir kenara atıyor ve bu da bu C# eğitiminde tartışılan bazı sorunlara yol açıyor.

Bu C Sharp Programlama Eğitimi Hakkında

Bu öğretici, C# programcıları tarafından yapılan en yaygın 10 C# programlama hatasını veya kaçınılması gereken sorunları açıklar ve onlara yardım sağlar.

Bu makalede tartışılan hataların çoğu C#'a özgü olmakla birlikte, bazıları CLR'yi hedefleyen veya Çerçeve Sınıf Kitaplığı'nı (FCL) kullanan diğer dillerle de ilgilidir.

Yaygın C# Programlama Hatası #1: Değer gibi bir referans kullanmak veya tam tersi

C++ programcıları ve diğer birçok dil, değişkenlere atadıkları değerlerin basitçe değerler mi yoksa mevcut nesnelere referanslar mı olduğunu kontrol etmeye alışkındır. Ancak C Sharp programlamasında bu karar, nesneyi başlatan ve onu bir değişkene atayan programcı tarafından değil, nesneyi yazan programcı tarafından verilir. Bu, C# programlamayı öğrenmeye çalışanlar için yaygın bir "yakalama"dır.

Kullandığınız nesnenin bir değer türü mü yoksa bir başvuru türü mü olduğunu bilmiyorsanız, bazı sürprizlerle karşılaşabilirsiniz. Örneğin:

 Point point1 = new Point(20, 30); Point point2 = point1; point2.X = 50; Console.WriteLine(point1.X); // 20 (does this surprise you?) Console.WriteLine(point2.X); // 50 Pen pen1 = new Pen(Color.Black); Pen pen2 = pen1; pen2.Color = Color.Blue; Console.WriteLine(pen1.Color); // Blue (or does this surprise you?) Console.WriteLine(pen2.Color); // Blue

Gördüğünüz gibi, hem Point hem de Pen nesneleri aynı şekilde oluşturuldu, ancak pen1 point1 X point2 bir renk atandığında değiştirildi. pen2 . Bu nedenle, point1 ve point1 her birinin bir Point nesnesinin kendi kopyasını içerdiğini, oysa pen1 ve point2 aynı Pen nesnesine referanslar içerdiğini pen2 . Ama bu deneyi yapmadan bunu nasıl bilebiliriz?

Cevap, nesne türlerinin tanımlarına bakmaktır (bunu Visual Studio'da imlecinizi nesne türünün adının üzerine getirerek ve F12 tuşuna basarak kolayca yapabilirsiniz):

 public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type

Yukarıda gösterildiği gibi, C# programlamasında, bir değer tipini tanımlamak için struct anahtar kelimesi kullanılırken, bir referans tipini tanımlamak için class anahtar kelimesi kullanılır. C++ ve C# anahtar sözcükleri arasındaki birçok benzerlik nedeniyle yanlış bir güvenlik duygusuna kapılan C++ geçmişine sahip olanlar için, bu davranış büyük olasılıkla bir C# öğreticisinden yardım istemenize neden olabilecek bir sürpriz olarak gelebilir.

Değer ve referans türleri arasında farklılık gösteren bazı davranışlara – örneğin bir nesneyi bir yöntem parametresi olarak iletme ve bu yöntemin nesnenin durumunu değiştirmesini sağlama gibi – bağımlı olacaksanız, aşağıdakilerle uğraştığınızdan emin olun. C# programlama sorunlarını önlemek için doğru nesne türü.

Yaygın C# Programlama Hatası #2: Başlatılmamış değişkenler için varsayılan değerlerin yanlış anlaşılması

C#'da değer türleri boş olamaz. Tanım olarak, değer türlerinin bir değeri vardır ve değer türlerinin başlatılmamış değişkenlerinin bile bir değeri olmalıdır. Bu, o tür için varsayılan değer olarak adlandırılır. Bu, bir değişkenin başlatılmamış olup olmadığını kontrol ederken genellikle beklenmeyen aşağıdaki sonuca yol açar:

 class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }

1. nokta point1 boş değil? Yanıt, Point bir değer türü olduğu ve bir Point için varsayılan değerin (0,0) olduğu, null olmadığıdır. Bunu fark edememek, C#'ta yapılması çok kolay (ve yaygın) bir hatadır.

Çoğu (ancak tümü değil) değer türünün, varsayılan değerine eşit olup olmadığını kontrol edebileceğiniz bir IsEmpty özelliği vardır:

 Console.WriteLine(point1.IsEmpty); // True

Bir değişkenin başlatılıp başlatılmadığını kontrol ederken, bu türden başlatılmamış bir değişkenin varsayılan olarak hangi değere sahip olacağını bildiğinizden emin olun ve bunun boş olmasına güvenmeyin.

Yaygın C# Programlama Hatası #3: Uygunsuz veya belirtilmemiş dizi karşılaştırma yöntemleri kullanma

C#'ta dizeleri karşılaştırmanın birçok farklı yolu vardır.

Çoğu programcı, dizi karşılaştırması için == operatörünü kullansa da, esasen kodda hangi tür karşılaştırmanın istendiğini açıkça belirtmediği için, aslında kullanılması en az arzu edilen yöntemlerden biridir.

Bunun yerine, C# programlamasında dize eşitliğini test etmenin tercih edilen yolu Equals yöntemidir:

 public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);

İlk yöntem imzası (yani, comparisonType parametresi olmadan), aslında == operatörünü kullanmakla aynıdır, ancak açıkça dizelere uygulanma avantajına sahiptir. Temelde bayt bayt karşılaştırma olan dizelerin sıralı karşılaştırmasını gerçekleştirir. Çoğu durumda bu tam olarak istediğiniz karşılaştırma türüdür, özellikle de değerleri dosya adları, ortam değişkenleri, öznitelikler vb. gibi programlı olarak ayarlanmış dizeleri karşılaştırırken. Bu durumlarda, sıralı karşılaştırma gerçekten doğru tür olduğu sürece Bu durum için karşılaştırma yapıldığında, Equals yöntemini bir comparisonType Türü olmadan kullanmanın tek dezavantajı, kodu okuyan birinin ne tür bir karşılaştırma yaptığınızı bilmemesidir.

Yine de, dizeleri her karşılaştırdığınızda bir comparisonType Türü içeren Equals yöntem imzasını kullanmak, yalnızca kodunuzu daha net hale getirmekle kalmaz, aynı zamanda hangi tür karşılaştırma yapmanız gerektiğini açıkça düşünmenizi sağlar. Bu, yapmaya değer bir şeydir, çünkü İngilizce, sıralı ve kültüre duyarlı karşılaştırmalar arasında çok fazla fark sağlamasa bile, diğer diller çok şey sağlar ve diğer dillerin olasılığını göz ardı etmek, kendinizi çok fazla potansiyele açar. yolda hatalar. Örneğin:

 string s = "strasse"; // outputs False: Console.WriteLine(s == "straße"); Console.WriteLine(s.Equals("straße")); Console.WriteLine(s.Equals("straße", StringComparison.Ordinal)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("straße", StringComparison.OrdinalIgnoreCase)); // outputs True: Console.WriteLine(s.Equals("straße", StringComparison.CurrentCulture)); Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCultureIgnoreCase));

En güvenli uygulama, Equals yöntemine her zaman bir comparisonType Türü parametresi sağlamaktır. İşte bazı temel yönergeler:

  • Kullanıcı tarafından girilen veya kullanıcıya görüntülenecek dizeleri karşılaştırırken, kültüre duyarlı bir karşılaştırma kullanın ( CurrentCulture veya CurrentCultureIgnoreCase ).
  • Programatik dizeleri karşılaştırırken sıralı karşılaştırmayı kullanın ( Ordinal veya OrdinalIgnoreCase ).
  • Sıralı karşılaştırmalar daha verimli olduğundan, InvariantCulture ve InvariantCultureIgnoreCase genellikle çok sınırlı durumlar dışında kullanılmaz. Kültüre duyarlı bir karşılaştırma gerekliyse, genellikle mevcut kültüre veya başka bir belirli kültüre karşı yapılmalıdır.

Equals yöntemine ek olarak, dizeler ayrıca, yalnızca bir eşitlik testi yerine dizelerin göreli sırası hakkında bilgi veren Compare yöntemini de sağlar. Bu yöntem, C# sorunlarından kaçınmak için yukarıda tartışılanla aynı nedenlerle < , <= , > ve >= operatörlerine tercih edilir.

İlgili: 12 Temel .NET Mülakat Sorusu

Yaygın C# Programlama Hatası #4: Koleksiyonları işlemek için yinelemeli (bildirimsel yerine) ifadeler kullanma

C# 3.0'da, dile Dille Tümleşik Sorgu'nun (LINQ) eklenmesi, koleksiyonların sorgulanma ve manipüle edilme şeklini sonsuza dek değiştirdi. O zamandan beri, koleksiyonları işlemek için yinelemeli ifadeler kullanıyorsanız, muhtemelen kullanmanız gerektiği halde LINQ kullanmadınız.

Bazı C# programcıları LINQ'nun varlığından bile haberdar değiller ama neyse ki bu sayı giderek azalıyor. Yine de çoğu kişi, LINQ anahtar sözcükleri ile SQL ifadeleri arasındaki benzerlik nedeniyle, bunun tek kullanımının veritabanlarını sorgulayan kodda olduğunu düşünüyor.

Veritabanı sorgulama, LINQ deyimlerinin çok yaygın bir kullanımı olsa da, aslında numaralandırılabilir herhangi bir koleksiyon (yani, IEnumerable arabirimini uygulayan herhangi bir nesne) üzerinde çalışırlar. Örneğin, bir C# Listesi foreach yazmak yerine, bir Hesap diziniz varsa:

 decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == "active") { total += account.Balance; } }

sadece şunu yazabilirsin:

 decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();

Bu, bu yaygın C# programlama sorununun nasıl önleneceğine dair oldukça basit bir örnek olsa da, tek bir LINQ ifadesinin kodunuzdaki yinelemeli bir döngüde (veya iç içe döngülerde) düzinelerce ifadeyi kolayca değiştirebildiği durumlar vardır. Ve daha az genel kod, hataların tanıtılması için daha az fırsat anlamına gelir. Ancak, performans açısından bir değiş tokuş olabileceğini unutmayın. Performans açısından kritik senaryolarda, özellikle yinelemeli kodunuzun koleksiyonunuz hakkında LINQ'nun yapamayacağı varsayımlarda bulunabildiği durumlarda, iki yöntem arasında bir performans karşılaştırması yaptığınızdan emin olun.

Yaygın C# Programlama Hatası #5: Bir LINQ deyiminde temel alınan nesneleri dikkate almamak

LINQ, ister bellek içi nesneler, ister veritabanı tabloları veya XML belgeleri olsun, koleksiyonları değiştirme görevini soyutlamak için mükemmeldir. Mükemmel bir dünyada, temeldeki nesnelerin ne olduğunu bilmenize gerek yoktur. Ancak buradaki hata, mükemmel bir dünyada yaşadığımızı varsaymaktır. Aslında, aynı LINQ ifadeleri, tam olarak aynı veriler üzerinde yürütüldüğünde, bu veriler farklı bir biçimde olursa, farklı sonuçlar döndürebilir.

Örneğin, aşağıdaki ifadeyi göz önünde bulundurun:

 decimal total = (from account in myAccounts where account.Status == "active" select account.Balance).Sum();

Nesnenin account.Status birinin durumu “Etkin” ise (büyük A'yı not edin) ne olur? Pekala, myAccounts bir DbSet nesnesiyse (varsayılan büyük/küçük harfe duyarsız yapılandırmayla ayarlanmışsa), where ifadesi yine de o öğeyle eşleşirdi. Ancak, myAccounts bir bellek içi dizideyse eşleşmez ve bu nedenle toplam için farklı bir sonuç verir.

Ama bir dakika bekle. Daha önce dizi karşılaştırmasından bahsettiğimizde, == operatörünün dizilerin sıralı karşılaştırmasını yaptığını gördük. Öyleyse neden bu durumda == operatörü büyük/küçük harfe duyarsız bir karşılaştırma yapıyor?

Cevap, bir LINQ deyimindeki temel nesneler SQL tablo verilerine referans olduğunda (bu örnekte Entity Framework DbSet nesnesinde olduğu gibi), deyim bir T-SQL deyimine dönüştürülür. Operatörler daha sonra C# programlama kurallarını değil T-SQL programlama kurallarını takip eder, bu nedenle yukarıdaki durumda karşılaştırma büyük/küçük harfe duyarlı değildir.

Genel olarak, LINQ, nesne koleksiyonlarını sorgulamak için yararlı ve tutarlı bir yol olsa da, gerçekte, kodunuzun davranışının değişeceğinden emin olmak için ifadenizin başlık altında C# dışında bir şeye çevrilip çevrilmeyeceğini bilmeniz gerekir. çalışma zamanında beklendiği gibi olun.

Yaygın C# Programlama Hatası #6: Uzantı yöntemleriyle kafa karıştırmak veya sahtekarlık yapmak

Daha önce belirtildiği gibi, LINQ deyimleri IEnumerable uygulayan herhangi bir nesne üzerinde çalışır. Örneğin, aşağıdaki basit işlev, herhangi bir hesap koleksiyonundaki bakiyeleri toplayacaktır:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) { return myAccounts.Sum(a => a.Balance); }

Yukarıdaki kodda, myAccounts parametresinin türü IEnumerable<Account> olarak bildirilir. myAccounts bir Sum yöntemine başvurduğundan (C#, bir sınıf veya arabirimdeki bir yönteme başvurmak için tanıdık "nokta gösterimini" kullandığından), IEnumerable<T> arabiriminin tanımında Sum() adlı bir yöntem görmeyi bekleriz. Ancak, IEnumerable<T> tanımı, herhangi bir Sum yöntemine atıfta bulunmaz ve basitçe şöyle görünür:

 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }

Peki Sum() yöntemi nerede tanımlanır? C# kesinlikle yazılmıştır, bu nedenle Sum yöntemine yapılan başvuru geçersizse, C# derleyicisi bunu kesinlikle bir hata olarak işaretleyecektir. Bu nedenle var olması gerektiğini biliyoruz, ama nerede? Ayrıca, LINQ'nun bu koleksiyonları sorgulamak veya toplamak için sağladığı diğer tüm yöntemlerin tanımları nerede?

Cevap, Sum() 'un IEnumerable arabiriminde tanımlanmış bir yöntem olmadığıdır. Bunun yerine, System.Linq.Enumerable sınıfında tanımlanan statik bir yöntemdir ("uzantı yöntemi" olarak adlandırılır):

 namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable<TSource> source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal> selector); ... } }

Peki, bir uzatma yöntemini diğer herhangi bir statik yöntemden farklı kılan nedir ve ona diğer sınıflarda erişmemizi sağlayan nedir?

Bir uzatma yönteminin ayırt edici özelliği, ilk parametresindeki this değiştiricidir. Bu, onu derleyiciye bir uzatma yöntemi olarak tanımlayan “sihirdir”. Değiştirdiği parametrenin türü (bu durumda IEnumerable<TSource> ), daha sonra bu yöntemi uygulamak için görünecek olan sınıfı veya arabirimi belirtir.

(Bir yan nokta olarak, IEnumerable arabiriminin adı ile uzantı yönteminin tanımlandığı Enumerable sınıfının adı arasındaki benzerliğin sihirli bir yanı yoktur. Bu benzerlik yalnızca keyfi bir stilistik seçimdir.)

Bu anlayışla, yukarıda tanıttığımız sumAccounts fonksiyonunun bunun yerine aşağıdaki gibi uygulanabileceğini de görebiliriz:

 public decimal SumAccounts(IEnumerable<Account> myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }

Bunu bu şekilde uygulayabilmemiz gerçeği, neden uzatma yöntemlerine sahip olduğumuz sorusunu gündeme getiriyor? Uzantı yöntemleri, esasen, yeni bir türetilmiş tür oluşturmadan, yeniden derlemeden veya orijinal türü değiştirmeden mevcut türlere yöntemler "eklemenizi" sağlayan C# programlama dilinin bir kolaylığıdır.

Uzantı yöntemleri, bir using [namespace]; Dosyanın üst kısmındaki ifade. Hangi C# ad alanının aradığınız uzantı yöntemlerini içerdiğini bilmeniz gerekir, ancak ne aradığınızı öğrendikten sonra bunu belirlemek oldukça kolaydır.

C# derleyicisi bir nesnenin bir örneğinde bir yöntem çağrısıyla karşılaştığında ve başvurulan nesne sınıfında tanımlanan yöntemi bulamadığında, gerekli yöntemle eşleşen birini bulmaya çalışmak için kapsam içindeki tüm uzantı yöntemlerine bakar. imza ve sınıf. Bir tane bulursa, örnek referansını bu uzatma yöntemine ilk argüman olarak iletir, daha sonra varsa argümanların geri kalanı, uzatma yöntemine sonraki argümanlar olarak iletilir. (C# derleyicisi kapsam içinde karşılık gelen herhangi bir uzantı yöntemi bulamazsa bir hata verir.)

Uzatma yöntemleri, (genellikle) daha net ve daha sürdürülebilir kod yazmamıza izin veren C# derleyicisinin bir parçası olan "sözdizimsel şeker" örneğidir. Daha net, yani, kullanımlarının farkındaysanız. Aksi takdirde, özellikle ilk başta biraz kafa karıştırıcı olabilir.

Uzantı yöntemlerini kullanmanın kesinlikle avantajları olsa da, sorunlara neden olabilir ve bunların farkında olmayan veya bunları tam olarak anlamayan geliştiriciler için C# programlama yardımı için bir çığlık. Bu, özellikle çevrimiçi kod örneklerine veya önceden yazılmış herhangi bir koda bakıldığında geçerlidir. Bu tür kod derleyici hataları ürettiğinde (çünkü çağrıldıkları sınıflarda açıkça tanımlanmayan yöntemleri çağırır), eğilim kodun kitaplığın farklı bir sürümüne veya tamamen farklı bir kitaplığa uygulandığını düşünmektir. Yeni bir sürüm veya var olmayan hayali “eksik kitaplık” aramak için çok zaman harcanabilir.

Uzantı yöntemlerine aşina olan geliştiriciler bile, nesne üzerinde aynı ada sahip bir yöntem olduğunda, ancak yöntem imzası, uzantı yöntemininkinden ince bir şekilde farklı olduğunda, hala ara sıra yakalanır. Orada olmayan bir yazım hatası veya hatayı aramak için çok zaman harcanabilir.

C# kitaplıklarında uzantı yöntemlerinin kullanımı giderek yaygınlaşmaktadır. LINQ'ya ek olarak, Unity Application Block ve Web API çerçevesi, Microsoft tarafından yaygın olarak kullanılan ve genişletme yöntemlerini de kullanan iki modern kitaplığa örnektir ve daha birçokları vardır. Çerçeve ne kadar modern olursa, genişletme yöntemlerini içerme olasılığı o kadar yüksek olur.

Elbette kendi uzantı yöntemlerinizi de yazabilirsiniz. Bununla birlikte, uzatma yöntemlerinin tıpkı normal örnek yöntemleri gibi çağrıldığı görünse de, bunun gerçekten sadece bir yanılsama olduğunu fark edin. Özellikle, genişletme yöntemleriniz, genişlettikleri sınıfın özel veya korumalı üyelerine başvuramaz ve bu nedenle daha geleneksel sınıf mirasının tam yerini alamaz.

Yaygın C# Programlama Hatası #7: Eldeki görev için yanlış türde koleksiyon kullanma

C#, aşağıdakiler yalnızca kısmi bir liste olmak üzere çok çeşitli koleksiyon nesneleri sağlar:
Array , ArrayList , BitArray , BitVector32 , Dictionary<K,V> , HashTable , HybridDictionary , List<T> , NameValueCollection , OrderedDictionary , Queue, Queue<T> , SortedList , StringDictionary Stack, Stack<T> , StringCollection

Çok fazla seçeneğin yetersiz seçim kadar kötü olduğu durumlar olsa da, koleksiyon nesnelerinde durum böyle değildir. Mevcut seçeneklerin sayısı kesinlikle sizin avantajınıza olabilir. Araştırmak için biraz fazladan zaman ayırın ve amacınız için en uygun koleksiyon türünü seçin. Muhtemelen daha iyi performans ve daha az hata payı ile sonuçlanacaktır.

Sahip olduğunuz öğenin türünü (dize veya bit gibi) özellikle hedefleyen bir koleksiyon türü varsa, önce onu kullanmaya yönelin. Uygulama, belirli bir öğe türünü hedeflediğinde genellikle daha verimlidir.

C#'ın tür güvenliğinden yararlanmak için, genel olmayan bir arabirim yerine genel bir arabirimi tercih etmelisiniz. Genel bir arabirimin öğeleri, nesnenizi bildirirken belirttiğiniz türdendir, oysa genel olmayan arabirimlerin öğeleri, nesne türündedir. Genel olmayan bir arabirim kullanırken, C# derleyicisi kodunuzu kontrol edemez. Ayrıca, ilkel değer türleri koleksiyonlarıyla uğraşırken, genel olmayan bir koleksiyon kullanmak, bu türlerin tekrar tekrar kutulanmasına/kutunun açılmasına neden olur ve bu da uygun türden genel bir koleksiyonla karşılaştırıldığında önemli bir olumsuz performans etkisine neden olabilir.

Diğer bir yaygın C# sorunu, kendi koleksiyon nesnenizi yazmaktır. Bu asla uygun olmadığı anlamına gelmez, ancak .NET'in sunduğu kadar kapsamlı bir seçimle, tekerleği yeniden icat etmek yerine zaten var olanı kullanarak veya genişleterek muhtemelen çok zaman kazanabilirsiniz. Özellikle, C# ve CLI için C5 Genel Koleksiyon Kitaplığı, kalıcı ağaç veri yapıları, yığın tabanlı öncelik kuyrukları, karma dizinli dizi listeleri, bağlantılı listeler ve çok daha fazlası gibi "kullanıma hazır" çok çeşitli ek koleksiyonlar sunar.

Yaygın C# Programlama Hatası #8: Ücretsiz kaynakları ihmal etmek

CLR ortamı bir çöp toplayıcı kullanır, bu nedenle herhangi bir nesne için oluşturulan belleği açıkça boşaltmanız gerekmez. Aslında, yapamazsın. C'de C++ delete operatörünün veya free() işlevinin eşdeğeri yoktur. Ancak bu, onları kullanmayı bitirdikten sonra tüm nesneleri unutabileceğiniz anlamına gelmez. Birçok nesne türü, başka bir sistem kaynağı türünü (örneğin, bir disk dosyası, veritabanı bağlantısı, ağ soketi, vb.) içine alır. Bu kaynakları açık bırakmak, sistem kaynaklarının toplam sayısını hızla tüketebilir, performansı düşürebilir ve sonuçta program hatalarına yol açabilir.

Yıkıcı yöntemi herhangi bir C# sınıfında tanımlanabilirken, yıkıcılarla ilgili sorun (C#'ta sonlandırıcılar olarak da adlandırılır) ne zaman çağrılacaklarını kesin olarak bilememenizdir. Gelecekte belirsiz bir zamanda çöp toplayıcı tarafından (ek komplikasyonlara neden olabilecek ayrı bir iş parçacığında) çağrılır. GC.Collect() ile çöp toplamayı zorlayarak bu sınırlamaları aşmaya çalışmak, C# için en iyi uygulama değildir, çünkü bu, toplama için uygun olan tüm nesneleri toplarken iş parçacığını bilinmeyen bir süre boyunca engeller.

Bu, kesinleştiriciler için iyi bir kullanım olmadığı anlamına gelmez, ancak kaynakları deterministik bir şekilde serbest bırakmak bunlardan biri değildir. Bunun yerine, bir dosya, ağ veya veritabanı bağlantısı üzerinde çalışırken, işiniz biter bitmez temeldeki kaynağı açıkça serbest bırakmak istersiniz.

Kaynak sızıntıları hemen hemen her ortamda bir endişe kaynağıdır. Bununla birlikte, C#, kullanıldığında sızıntıları çok daha nadir hale getirebilecek, sağlam ve kullanımı basit bir mekanizma sağlar. .NET çerçevesi, yalnızca Dispose() yönteminden oluşan IDisposable arabirimini tanımlar. IDisposable uygulayan herhangi bir nesne, nesnenin tüketicisi onu manipüle etmeyi bitirdiğinde bu yöntemin çağrılmasını bekler. Bu, kaynakların açık, deterministik serbest bırakılmasıyla sonuçlanır.

Tek bir kod bloğu bağlamında bir nesne oluşturuyor ve elden çıkarıyorsanız, Dispose() öğesini çağırmayı unutmanız temelde affedilemez, çünkü C#, Dispose() () öğesinin kod bloğu nasıl olursa olsun çağrılmasını sağlayacak bir using ifadesi sağlar. çıkılır (ister bir istisna, ister bir dönüş ifadesi veya basitçe bloğun kapanması olsun). Ve evet, bu, daha önce bahsedilen ve dosyanızın en üstüne C# ad alanlarını eklemek için kullanılan using ifadesinin aynısıdır. Pek çok C# geliştiricisinin bilmediği, tamamen alakasız ikinci bir amacı vardır; yani, kod bloğundan çıkıldığında Dispose() öğesinin bir nesnede çağrılmasını sağlamak için:

 using (FileStream myFile = File.OpenRead("foo.txt")) { myFile.Read(buffer, 0, 100); }

Yukarıdaki örnekte bir using bloğu oluşturarak, Read() bir istisna atsa da atmasa da, myFile.Dispose() emin olabilirsiniz.

Yaygın C# Programlama Hatası #9: İstisnalardan Uzak Durma

C#, çalışma zamanına tür güvenliği uygulamasına devam eder. Bu, C#'daki birçok hata türünü, hatalı tür dönüştürmelerinin bir nesnenin alanlarına rastgele değerlerin atanmasına neden olabileceği C++ gibi dillerden çok daha hızlı bir şekilde saptamanıza olanak tanır. Ancak, programcılar bir kez daha bu harika özelliği israf ederek C# sorunlarına yol açabilir. Bu tuzağa düşüyorlar çünkü C# bir şeyler yapmak için iki farklı yol sağlıyor, biri bir istisna oluşturabilen ve diğeri yapmayacak. Bazıları, bir try/catch bloğu yazmak zorunda kalmamanın onları biraz kodlamadan kurtaracağını düşünerek istisna yolundan kaçınacaktır.

Örneğin, burada C#'ta açık bir tür atama gerçekleştirmenin iki farklı yolu vardır:

 // METHOD 1: // Throws an exception if account can't be cast to SavingsAccount SavingsAccount savingsAccount = (SavingsAccount)account; // METHOD 2: // Does NOT throw an exception if account can't be cast to // SavingsAccount; will just set savingsAccount to null instead SavingsAccount savingsAccount = account as SavingsAccount;

Yöntem 2'nin kullanımıyla ortaya çıkabilecek en belirgin hata, dönüş değerinin kontrol edilmemesidir. Bu, muhtemelen daha sonra ortaya çıkabilecek ve sorunun kaynağını bulmayı çok daha zor hale getirebilecek nihai bir NullReferenceException ile sonuçlanacaktır. Buna karşılık, Yöntem 1, sorunun kaynağını hemen daha belirgin hale getiren bir InvalidCastException hemen atardı.

Ayrıca, Yöntem 2'deki dönüş değerini kontrol etmeyi hatırlasanız bile, boş olduğunu bulursanız ne yapacaksınız? Yazdığınız yöntem bir hatayı bildirmek için uygun bir yer mi? Bu döküm başarısız olursa deneyebileceğiniz başka bir şey var mı? Değilse, o zaman bir istisna atmak yapılacak doğru şeydir, bu yüzden sorunun kaynağına mümkün olduğunca yakın olmasına izin verebilirsiniz.

Aşağıda, birinin istisna atıp diğerinin atmadığı diğer yaygın yöntem çiftlerine birkaç örnek verilmiştir:

 int.Parse(); // throws exception if argument can't be parsed int.TryParse(); // returns a bool to denote whether parse succeeded IEnumerable.First(); // throws exception if sequence is empty IEnumerable.FirstOrDefault(); // returns null/default value if sequence is empty

Bazı C# geliştiricileri o kadar "olumsuz istisna"dırlar ki, otomatik olarak bir istisna oluşturmayan yöntemin üstün olduğunu varsayarlar. Bunun doğru olabileceği bazı seçkin durumlar olsa da, bir genelleme olarak hiç de doğru değildir.

Spesifik bir örnek olarak, bir istisna oluşturulacaksa alternatif bir meşru (ör. varsayılan) eyleminizin olduğu bir durumda, o zaman istisnasız yaklaşım meşru bir seçim olabilir. Böyle bir durumda, şöyle bir şey yazmak gerçekten daha iyi olabilir:

 if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }

onun yerine:

 try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }

Bununla birlikte, TryParse bu nedenle mutlaka "daha iyi" bir yöntem olduğunu varsaymak yanlıştır. Bazen böyle olur, bazen olmaz. Bu yüzden bunu yapmanın iki yolu vardır. İçinde bulunduğunuz bağlam için doğru olanı kullanın, istisnaların kesinlikle bir geliştirici olarak arkadaşınız olabileceğini unutmayın.

Yaygın C# Programlama Hatası #10: Derleyici uyarılarının birikmesine izin verme

Bu problem kesinlikle C#'a özgü olmasa da, C# derleyicisi tarafından sunulan katı tip kontrolünün faydalarından vazgeçtiği için özellikle C# programlamasında berbattır.

Uyarılar bir nedenle oluşturulur. Tüm C# derleyici hataları kodunuzdaki bir kusuru belirtirken, birçok uyarı da aynı şeyi yapar. İkisini birbirinden ayıran şey, bir uyarı durumunda, derleyicinin kodunuzun temsil ettiği talimatları yaymakta hiçbir sorunu olmamasıdır. Buna rağmen, kodunuzu biraz şüpheli buluyor ve kodunuzun amacınızı tam olarak yansıtmaması için makul bir olasılık var.

Bu C# programlama öğreticisi için basit bir örnek, kullanmakta olduğunuz bir değişkenin kullanımını ortadan kaldırmak için algoritmanızı değiştirmeniz, ancak değişken bildirimini kaldırmayı unutmanızdır. Program kusursuz çalışacak, ancak derleyici işe yaramaz değişken bildirimini işaretleyecektir. Programın kusursuz çalışması, programcıların uyarının nedenini düzeltmeyi ihmal etmelerine neden olur. Ayrıca kodlayıcılar, yalnızca hatalara odaklanabilmeleri için "Hata Listesi" penceresindeki uyarıları gizlemelerini kolaylaştıran bir Visual Studio özelliğinden yararlanır. Düzinelerce uyarı gelene kadar uzun sürmez, hepsi mutlu bir şekilde göz ardı edilir (hatta daha kötüsü, gizlenir).

Ancak bu tür bir uyarıyı görmezden gelirseniz, er ya da geç, bunun gibi bir şey kodunuza çok iyi girebilir:

 class Account { int myId; int Id; // compiler warned you about this, but you didn't listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }

Ve Intellisense'in kod yazmamıza izin verdiği hızda, bu hata göründüğü kadar olası değil.

Artık programınızda ciddi bir hata var (her ne kadar derleyici daha önce açıklanan nedenlerden dolayı bunu yalnızca bir uyarı olarak işaretlemiş olsa da) ve programınızın ne kadar karmaşık olduğuna bağlı olarak, bunu izleyerek çok zaman kaybedebilirsiniz. İlk etapta bu uyarıya dikkat etmiş olsaydınız, beş saniyelik basit bir düzeltme ile bu sorunu önlemiş olurdunuz.

Unutmayın, C Sharp derleyicisi size kodunuzun sağlamlığı hakkında pek çok faydalı bilgi verir… eğer dinliyorsanız. Uyarıları göz ardı etmeyin. Düzeltmeleri genellikle yalnızca birkaç saniye sürer ve yenilerini olduklarında onarmak size saatler kazandırabilir. Visual Studio "Hata Listesi" penceresinin "0 Hata, 0 Uyarı" göstermesini beklemek için kendinizi eğitin, böylece herhangi bir uyarı sizi onları hemen ele almak için yeterince rahatsız eder.

Tabii ki, her kuralın istisnaları vardır. Buna göre, tam olarak istediğiniz gibi olmasına rağmen, kodunuzun derleyiciye biraz şüpheli görüneceği zamanlar olabilir. Bu çok nadir durumlarda, yalnızca uyarıyı tetikleyen kodun çevresinde ve yalnızca tetiklediği uyarı kimliği için #pragma warning disable [warning id] kullanın. Bu, o uyarıyı ve yalnızca o uyarıyı bastırır, böylece yenileri için hala tetikte kalabilirsiniz.

Sarmak

C#, üretkenliği büyük ölçüde artırabilen birçok mekanizma ve paradigmaya sahip güçlü ve esnek bir dildir. Bununla birlikte, herhangi bir yazılım aracı veya dilinde olduğu gibi, yeteneklerinin sınırlı bir şekilde anlaşılması veya takdir edilmesi, bazen bir faydadan çok bir engel olabilir ve kişiyi meşhur “tehlikeli olacak kadar bilmek” durumunda bırakır.

Bu makalede ortaya konan sorunlar gibi (ancak bunlarla sınırlı olmayan) C#'ın temel nüanslarına aşina olmak için bunun gibi bir C Sharp öğreticisi kullanmak, C# optimizasyonuna yardımcı olurken, daha yaygın olan bazı tuzaklardan kaçınacaktır. dilim.


Toptal Mühendislik Blogunda Daha Fazla Okuma:

  • Temel C# Mülakat Soruları
  • C# ve C++: Çekirdekte Neler Var?