C # on üks mitmest Microsofti sihtinud keelest Üldkeele kestus (CLR). Keeled, mis on suunatud CLR-ile, saavad kasu sellistest funktsioonidest nagu keelteülene integreerimine ja erandite käsitlemine, täiustatud turvalisus, komponentide interaktsiooni lihtsustatud mudel ning silumis- ja profileerimisteenused. Tänapäevastest CLR-keeltest kasutatakse keerukate ja professionaalsete keelte jaoks C # -d arendusprojektid mis sihivad Windowsi töölaua-, mobiili- või serverikeskkondi.
C # on objektile orienteeritud, tugevalt sisestatud keel. C # tüübi range kontrollimine nii kompileerimise kui ka käitamise ajal põhjustab enamiku tüüpiliste C # programmeerimisvigade teatamise võimalikult varakult ja nende asukohad on täpselt kindlaks määratud. See võib sisse aidata palju aega C Terav programmeerimine , võrreldes mõistatuslike vigade põhjuste väljaselgitamisega, mis võivad ilmneda kaua pärast õigusrikkumise toimumist keeltes, mis on tüübikinnituse tagamisel liberaalsemad. Kuid paljud C # kooderid viskavad tahtmatult (või hoolimatult) ära selle tuvastamise eelised, mis toob kaasa mõned selles C # õpetuses käsitletud probleemid.
Selles õpetuses kirjeldatakse 10 kõige tavalisemat C # programmeerimisviga või välditavat probleemi, mida C # programmeerijad teevad, ja pakub neile abi.
Ehkki enamik selles artiklis käsitletud vigadest on spetsiifilised C # -le, on mõned asjakohased ka teiste CLR-i sihtivate keelte või Raamklassi raamatukogu (FCL).
C ++ ja paljude teiste keelte programmeerijad on harjunud kontrollima, kas muutujaile omistatud väärtused on lihtsalt väärtused või viited olemasolevatele objektidele. C Sharpi programmeerimisel teeb selle otsuse siiski objekti kirjutanud programmeerija, mitte programmeerija, kes objekti instantiseerib ja määrab selle muutujale. See on tavaline “gotcha” neile, kes üritavad õppida C # programmeerimist.
Kui te ei tea, kas teie kasutatav objekt on väärtuse või viite tüüp, võite kokku puutuda mõningate üllatustega. Näiteks:
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
Nagu näete, on nii Point
ja Pen
objektid loodi täpselt samamoodi, kuid point1
väärtus jäi muutumatuks, kui uus X
koordinaatväärtus määrati point2
-le, samas kui pen1
oli muudetud, kui pen2
-le määrati uus värv. Seetõttu saame järeldada et point1
ja point2
kumbki sisaldab oma eksemplari Point
objekt, kusjuures pen1
ja pen2
sisaldavad viiteid samale Pen
objekt. Aga kuidas me saame seda eksperimenti tegemata teada?
Vastus on vaadata objektitüüpide määratlusi (mida saate Visual Studios hõlpsalt teha, viies kursori objektitüübi nime kohale ja vajutades klahvi F12):
public struct Point { ... } // defines a “value” type public class Pen { ... } // defines a “reference” type
Nagu eespool näidatud, on C # programmeerimisel struct
märksõna kasutatakse väärtuse tüübi määratlemiseks, samas kui class
märksõna kasutatakse viite tüübi määratlemiseks. Neile, kellel on C ++ taust, keda C ++ ja C # märksõnade paljud sarnasused meelitasid valesse turvatundesse, tuleb selline käitumine tõenäoliselt üllatusena, mis võib teil paluda abi C # juhendajalt.
Kui sõltute mõnest käitumisest, mis erineb väärtuse ja viite tüübist - näiteks võime edastada objekti meetodi parameetrina ja lasta sellel meetodil objekti olekut muuta - veenduge, et tegeleksite õige tüüpi objekt, et vältida C # programmeerimisprobleeme.
C # puhul ei saa väärtuste tüübid olla nullid. Definitsiooni järgi on väärtustüüpidel väärtus ja isegi väärtustüüpide initsialiseerimata muutujatel peab olema väärtus. Seda nimetatakse selle tüübi vaikeväärtuseks. See toob kaasa järgmise, tavaliselt ootamatu tulemuse, kui kontrollitakse, kas muutuja on initsialiseerimata:
class Program { static Point point1; static Pen pen1; static void Main(string[] args) { Console.WriteLine(pen1 == null); // True Console.WriteLine(point1 == null); // False (huh?) } }
Miks ei ole point1
null? Vastus on, et Point
on väärtuse tüüp ja a Point
vaikeväärtus on (0,0), mitte null. Selle tuvastamata jätmine on C #-s väga lihtne (ja tavaline) viga.
Paljudel (kuid mitte kõigil) väärtustüüpidel on IsEmpty
atribuut, mida saate kontrollida, kas see on võrdne vaikeväärtusega:
Console.WriteLine(point1.IsEmpty); // True
Kui kontrollite, kas muutuja on initsialiseeritud või mitte, veenduge, et teate, milline väärtus on seda tüüpi initsialiseerimata muutujal vaikimisi, ja ärge lootke, et see on null.
Stringide C # võrdlemiseks on palju erinevaid viise.
Kuigi paljud programmeerijad kasutavad ==
operaator stringide võrdlemiseks, on see tegelikult üks vähemalt soovitavad meetodid, peamiselt seetõttu, et see ei täpsusta koodis selgesõnaliselt, millist tüüpi võrdlust soovitakse.
Pigem on C # programmeerimisel stringide võrdsuse testimiseks eelistatud viis Equals
meetod:
public bool Equals(string value); public bool Equals(string value, StringComparison comparisonType);
Esimese meetodi allkiri (st ilma parameetrita comparisonType
) on tegelikult sama, mis kasutada ==
operaator, kuid selle eeliseks on see, et seda selgesõnaliselt stringidele rakendatakse. See teostab stringide järjestuslikku võrdlust, mis on põhimõtteliselt baitide kaupa võrdlus. Paljudel juhtudel on just see võrdlustüüp, mida soovite, eriti kui võrrelda stringe, mille väärtused on programmeeritud, näiteks failinimed, keskkonnamuutujad, atribuudid jne. Sellistel juhtudel, kui järjestuslik võrdlus on tõepoolest õige tüüp selle olukorra võrdlus on ainus negatiivne külg Equals
meetod ilma comparisonType
on see, et keegi, kes koodi loeb, ei pruugi teada, millist tüüpi võrdlust teete.
Kasutades Equals
meetodi allkiri, mis sisaldab comparisonType
Iga kord, kui te stringe võrdlete, ei tee teie kood ainult selgemaks, vaid paneb teid selgesõnaliselt mõtlema, millist tüüpi võrdlust peate tegema. See on väärt asi, mida teha, sest isegi kui inglise keel ei pruugi järjestuslike ja kultuuritundlike võrdluste vahel väga palju erinevusi pakkuda, pakuvad teised keeled küllaga ning teiste keelte võimaluse eiramine avab teile palju võimalusi vigu mööda teed. Näiteks:
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));
Kõige turvalisem on alati anda comparisonType
parameeter Equals
meetod. Siin on mõned põhijuhised:
CurrentCulture
või CurrentCultureIgnoreCase
).Ordinal
või OrdinalIgnoreCase
).InvariantCulture
ja InvariantCultureIgnoreCase
neid ei tohi tavaliselt kasutada, välja arvatud väga piiratud olukordades, sest järjestuslikud võrdlused on tõhusamad. Kui kultuuriteadlik võrdlus on vajalik, tuleks see tavaliselt läbi viia praeguse või mõne muu konkreetse kultuuriga.Lisaks Equals
meetodi korral pakuvad stringid ka Compare
meetod, mis annab teile teavet stringide suhtelise järjestuse kohta, mitte ainult võrdsuse testi jaoks. Seda meetodit eelistatakse <
, <=
, >
-le ja >=
operaatoritel samadel põhjustel, mida eespool arutati, et vältida C # probleeme.
C # 3.0-s lisatakse Keelega integreeritud päring (LINQ) keeleks muutus igaveseks viis, kuidas kollektsioone päritakse ja nendega manipuleeritakse. Alates sellest ajast, kui kasutate iteratiivseid avaldusi kollektsioonide manipuleerimiseks, ei kasutanud te LINQ-i siis, kui ilmselt oleksite pidanud.
milline kasutajanimi on õigesti kodeeritud?
Mõned C # programmeerijad ei tea isegi LINQi olemasolust, kuid õnneks muutub see arv üha väiksemaks. Paljud arvavad siiski, et LINQ-i märksõnade ja SQL-lausete sarnasuse tõttu kasutatakse seda ainult andmebaase pärivate koodide puhul.
Kuigi andmebaasi päringud on LINQ-lausete väga levinud kasutusviis, töötavad need tegelikult mis tahes loendamatu kogu (s.o mis tahes objekti puhul, mis rakendab liidest IEnumerable). Näiteks kui teil oli massiiv kontosid, selle asemel et kirjutada C # loendi foreach:
decimal total = 0; foreach (Account account in myAccounts) { if (account.Status == 'active') { total += account.Balance; } }
sa võid lihtsalt kirjutada:
decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
Kuigi see on üsna lihtne näide selle tavalise C # programmeerimisprobleemi vältimiseks, on juhtumeid, kus üks LINQ-lause võib teie koodis hõlpsasti asendada kümneid iteratiivsilmuses (või pesastatud silmustes) olevaid avaldusi. Ja vähem koodi üldist tähendab vähem võimalusi vigade tutvustamiseks. Pidage siiski meeles, et toimivuse osas võib tekkida kompromiss. Toimivuskriitilistes stsenaariumides, eriti kui teie iteratiivne kood suudab teie kollektsiooni kohta oletusi teha, mida LINQ ei saa, tehke kindlasti kahe meetodi toimivuse võrdlus.
LINQ sobib suurepäraselt kollektsioonide manipuleerimise ülesande abstraktseks muutmiseks, olgu need siis mälusisesed objektid, andmebaasitabelid või XML-dokumendid. Täiuslikus maailmas ei pea te teadma, mis on nende aluseks olevad objektid. Kuid siin on viga eeldada, et elame täiuslikus maailmas. Tegelikult võivad identsed LINQ-i laused anda täpselt samadele andmetele käivitamisel erinevaid tulemusi, kui need andmed juhtuvad olema erinevas vormingus.
Näiteks kaaluge järgmist väidet:
decimal total = (from account in myAccounts where account.Status == 'active' select account.Balance).Sum();
Mis juhtub, kui mõni objekti account.Status
võrdub aktiivsega (pange tähele suurtähte A)? Noh, kui myAccounts
oli DbSet
objekt (mis oli loodud vaiketähtedeta tundliku konfiguratsiooniga), where
väljend sobiks ikkagi selle elemendiga. Kui myAccounts
oli mälusiseses massiivis, see ei sobinud ja annaks seetõttu kogusummale teistsuguse tulemuse.
Aga oota hetk. Kui rääkisime varem stringide võrdlusest, nägime, et ==
operaator viis järjestuste järjestuse võrdluse läbi. Miks on sel juhul ==
operaator, kes teeb juhtude tundetut võrdlust?
Vastus on see, et kui LINQ-avaldise aluseks olevad objektid on viited SQL-i tabeli andmetele (nagu see on selle näite objektil Entity Framework DbSet), teisendatakse lause T-SQL-i lauseks. Operaatorid järgivad seejärel T-SQL-i programmeerimisreegleid, mitte C # -programmeerimisreegleid, nii et ülaltoodud juhtumite võrdlus jääb lõpuks väiketähtetundetuks.
Üldiselt, kuigi LINQ on abivalmis ja järjepidev viis objektide kogumite päringute tegemiseks, peate tegelikkuses ikkagi teadma, kas teie lause tõlgitakse mujal kui C # -il kapoti all, et tagada teie koodi käitumise tõrjumine. olema tööajal ootuspärane.
Nagu varem mainitud, töötavad LINQ-i avaldused iga objektiga, mis rakendab rakendust IEnumerable. Näiteks lisab järgmine lihtne funktsioon kõigi kontokogude saldod:
public decimal SumAccounts(IEnumerable myAccounts) { return myAccounts.Sum(a => a.Balance); }
Ülaltoodud koodis deklareeritakse parameetri myAccounts tüüp IEnumerable
. Kuna myAccounts
viited a Sum
meetod (C # kasutab klassis või liideses meetodile viitamiseks tuttavat punktmärkimist), eeldame, et näeme meetodit nimega Sum()
IEnumerable
määratluse kohta liides. IEnumerable
Määratluses ei viidata aga ühegi Sum
meetod ja näeb lihtsalt välja selline:
public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); }
Nii et kus on Sum()
määratletud meetod? C # on tugevalt kirjutatud, nii et kui viide Sum
meetod oli vale, märkis C # kompilaator selle kindlasti veana. Seetõttu teame, et see peab olemas olema, aga kus? Veelgi enam, kus on kõigi teiste meetodite definitsioonid, mida LINQ nende kollektsioonide päringute esitamiseks või liitmiseks pakub?
Vastus on, et Sum()
ei ole meetodil, mis on määratletud IEnumerable
-s liides. Pigem on see staatiline meetod (nn laiendusmeetod), mis on määratletud System.Linq.Enumerable
-s klass:
namespace System.Linq { public static class Enumerable { ... // the reference here to “this IEnumerable source” is // the magic sauce that provides access to the extension method Sum public static decimal Sum(this IEnumerable source, Func selector); ... } }
Mis siis muudab laiendusmeetodi mis tahes muust staatilisest meetodist ja mis võimaldab meil sellele juurde pääseda teistes klassides?
Laiendusmeetodi eristavaks tunnuseks on this
selle esimese parameetri muutja. See on “maagia”, mis identifitseerib selle kompilaatori kui laiendusmeetodi järgi. Parameetri tüüp, mida see muudab, (antud juhul IEnumerable
) tähistab klassi või liidest, mis seejärel ilmuvad selle meetodi rakendamiseks.
(Kõrvalt võib öelda, et liidese IEnumerable
nime ja klassi Enumerable
nime, milles laiendusmeetod on määratletud, sarnasuses pole midagi maagilist. See sarnasus on lihtsalt meelevaldne stiililine valik .)
Selle arusaamaga näeme ka, et sumAccounts
ülaltoodud funktsiooni oleks võinud selle asemel rakendada järgmiselt:
public decimal SumAccounts(IEnumerable myAccounts) { return Enumerable.Sum(myAccounts, a => a.Balance); }
Asjaolu, et oleksime võinud selle ka sel viisil rakendada, tekitab küsimuse, miks laiendusmeetodeid üldse on? Pikendamismeetodid on sisuliselt C # programmeerimiskeele mugavus, mis võimaldab teil olemasolevatele tüüpidele meetodeid 'lisada' ilma uut tuletatud tüüpi loomata, algset tüüpi ümber kompileerimata või muul viisil muutmata.
Laiendusmeetodid võetakse kasutusele, lisades a using [namespace];
avaldus faili ülaosas. Peate teadma, milline C # nimeruum sisaldab otsitavaid laiendusmeetodeid, kuid seda on üsna lihtne kindlaks teha, kui teate, mida see on.
Kui C # kompilaator kohtab objekti eksemplari meetodi kutsega ja ei leia seda meetodit viidatud objektiklassis määratletud, vaatab ta kõiki ulatusse kuuluvaid laiendusmeetodeid, et proovida leida vajalikule meetodile vastav meetod allkiri ja klass. Kui see leitakse, edastab see eksemplari viite esimese laiendusmeetodi argumendina, seejärel edastatakse ülejäänud argumendid laiendusmeetodile järgnevate argumentidena, kui neid on. (Kui C # kompilaator ei leia ühtegi vastavat laiendusmeetodit oma kohaldamisalast, viskab see vea.)
Laiendusmeetodid on C # kompilaatori näide „süntaktilisest suhkrust“, mis võimaldab meil kirjutada (tavaliselt) selgemat ja hooldatavamat koodi. Selgem, see tähendab, kui olete teadlik nende kasutamisest. Muidu võib see veidi segadust tekitada, eriti esialgu.
Kuigi laiendusmeetodite kasutamisel on kindlasti eeliseid, võivad need tekitada probleeme ja karjuda C # programmeerimise järele neile arendajatele, kes pole neist teadlikud või ei mõista neid õigesti. See kehtib eriti siis, kui vaatate koodinäiteid veebis või mõnda muud eelnevalt kirjutatud koodi. Kui selline kood tekitab kompilaatori tõrkeid (kuna see kutsub esile meetodeid, mida pole selgelt määratletud klassides, millele neid kasutatakse), kaldutakse arvama, et kood kehtib teegi teisele versioonile või hoopis teisele teegile. Palju aega võib kulutada uue versiooni otsimisele või fantoomi puuduvale teegile, mida pole olemas.
Isegi arendajad, kes on laiendusmeetoditega kursis, jäävad ikka aeg-ajalt vahele, kui objektil on sama nimega meetod, kuid selle meetodi allkiri erineb peenelt laiendusmeetodi omast. Palju aega võib raisata kirjavea või vea otsimisele, mida lihtsalt pole.
Laiendamismeetodite kasutamine C # raamatukogudes on üha levinum. Lisaks LINQ-ile on Rakenduse Unity blokeerimine ja Veebi API raamistik on näited kahest Microsofti tihedalt kasutatavast kaasaegsest teegist, mis kasutavad ka laiendusmeetodeid, ja on palju muid. Mida kaasaegsem on raamistik, seda tõenäolisem on, et see sisaldab laiendusmeetodeid.
Muidugi võite kirjutada ka oma laiendusmeetodid. Mõistke siiski, et kuigi laiendusmeetodeid näib kasutavat nagu tavalisi eksemplarimeetodeid, on see tegelikult vaid illusioon. Täpsemalt ei saa teie laiendusmeetodid viidata oma laiendatava klassi era- või kaitstud liikmetele ja seetõttu ei saa need olla traditsioonilisema klassi pärandi täielikuks asendajaks.
C # pakub palju erinevaid kollektsiooniobjekte, kusjuures järgmine on ainult osaline loend: Array
, ArrayList
, BitArray
, BitVector32
, Dictionary
, HashTable
, HybridDictionary
, List
, NameValueCollection
, OrderedDictionary
, Queue, Queue
, SortedList
, Stack, Stack
, StringCollection
, StringDictionary
.
Kuigi võib olla juhtumeid, kus liiga palju valikuid on sama halb kui vähe valikuid, ei ole see kollektsiooniobjektide puhul nii. Saadaval olevate valikute arv võib kindlasti teie kasuks töötada. Varuge uurimiseks veidi lisaaega ja valige oma otstarbeks optimaalne kollektsiooni tüüp. Selle tulemuseks on tõenäoliselt parem jõudlus ja vähem eksimisruumi.
Kui on olemas kogumistüüp, mis on suunatud konkreetselt teie valitud elemendi tüübile (nt string või bitt), kasutage seda elementi kõigepealt. Rakendamine on üldiselt tõhusam, kui see on suunatud teatud tüüpi elementidele.
C # tüüpi turvalisuse kasutamiseks peaksite tavaliselt eelistama üldist liidest mitte-üldisele liidesele. Üldise liidese elemendid on tüüpi, mille määrate objekti deklareerimisel, samas kui mitte-üldiste liideste elemendid on tüüpi objekti. Mitte-üldise liidese kasutamisel ei saa C # kompilaator teie koodi tüüpi kontrollida. Samuti on primitiivsete väärtustüüpide kollektsioonidega tegelemisel korduv mittegeneerilise kollektsiooni kasutamine poks / lahtipakkimine nende tüüpide puhul, mis võib avaldada märkimisväärset negatiivset mõju tulemuslikkusele, võrreldes sobiva tüübi üldise kollektsiooniga.
Teine levinud C # probleem on oma kogumisobjekti kirjutamine. See ei tähenda, et see pole kunagi sobiv, kuid nii põhjaliku valiku abil, kui pakub .NET, saate tõenäoliselt palju aega kokku hoida, kasutades selleks juba olemasolevat või pikendades selle asemel, et ratast uuesti leiutada. Eelkõige pakub C5 ja CLI jaoks üldine C5-teek C5 laia valikut täiendavaid kollektsioone 'karbist väljas', nagu näiteks püsivad puude andmestruktuurid, kuhjapõhised prioriteedijärjekorrad, räsitud indekseeritud massiivide loendid, lingitud loendid ja palju muud.
CLR-keskkonnas töötab prügikoguja, nii et te ei pea ühegi objekti jaoks loodud mälu sõnaselgelt vabastama. Tegelikult ei saa. C ++ delete
-le pole vastet operaator või free()
funktsioon C-s. Kuid see ei tähenda, et pärast nende kasutamise lõpetamist võite kõik objektid lihtsalt unustada. Paljud tüüpi objektid kapseldavad mõnda muud tüüpi süsteemiressurssi (nt kettafail, andmebaasiühendus, võrgupesa jne). Nende ressursside avatuks jätmine võib süsteemi ressursside koguarvu kiiresti tühjendada, halvendada jõudlust ja viia lõpuks programmivigadeni.
Ehkki destruktori meetodit saab määratleda igas C # klassis, on destruktorite (mida nimetatakse ka C # vormingus finalistideks) probleemiks see, et te ei saa täpselt teada, millal neid kutsutakse. Neile helistab prügikoguja (eraldi niidil, mis võib põhjustada täiendavaid komplikatsioone) tulevikus määramata ajal. Proovin neist piirangutest mööda saada, sundides prügivedu nupuga GC.Collect()
ei ole a C # parim tava , kuna see blokeerib lõime tundmatuks ajaks, kui see kogub kõik kogumiseks sobivad objektid.
See ei tähenda, et finaliseerijatel pole häid kasutusviise, kuid ressursside vabastamine deterministlikul viisil ei kuulu nende hulka. Pigem soovite faili-, võrgu- või andmebaasiühenduse kasutamisel vabastada alusressurssi selgesõnaliselt kohe, kui olete sellega valmis.
uued tutvumissaidid 2015 tasuta
Ressursside lekkimine on peaaegu murettekitav mis tahes keskkonnas . C # pakub aga tugeva ja lihtsa kasutusega mehhanismi, mille kasutamine võib lekkeid muuta palju harvemaks. .NET-raamistik määratleb IDisposable
liides, mis koosneb ainult Dispose()
meetod. Iga objekt, mis rakendab IDisposable
loodab, et seda meetodit kutsutakse alati, kui objekti tarbija on sellega manipuleerimise lõpetanud. Selle tulemuseks on ressursside selge, deterministlik vabastamine.
Kui loote ja utiliseerite objekti ühe koodiploki kontekstis, on põhimõtteliselt andestamatu unustada helistada Dispose()
, sest C # annab using
avaldus, mis tagab Dispose()
kutsutakse, olenemata sellest, kuidas koodiplokist väljutakse (olgu see siis erand, return-lause või lihtsalt ploki sulgemine). Ja jah, see on sama using
varem mainitud lause, mida kasutatakse C # nimeruumide lisamiseks teie faili ülaossa. Sellel on teine, täiesti mitteseotud eesmärk, millest paljud C # arendajad pole teadlikud; nimelt tagada, et Dispose()
kutsutakse objektile, kui koodiplokist väljutakse:
using (FileStream myFile = File.OpenRead('foo.txt')) { myFile.Read(buffer, 0, 100); }
Luues ülaltoodud näites using
ploki, teate kindlasti, et myFile.Dispose()
helistatakse kohe, kui olete failiga valmis, olenemata sellest, kas Read()
viskab erandi.
C # jätkab tüübiohutuse rakendamist käitamisajas. See võimaldab teil paljude # C-tüüpi vigade täpsustamist märksa kiiremini kui sellistes keeltes nagu C ++, kus vigased tüübikonversioonid võivad põhjustada suvaliste väärtuste määramise objekti väljadele. Kuid veel kord saavad programmeerijad selle suurepärase funktsiooni raisata, mis toob kaasa probleeme C # -ga. Nad satuvad sellesse lõksu, kuna C # pakub kahte erinevat viisi toiminguteks, millest üks võib erandi teha ja mis mitte. Mõni loobub eranditeest, arvates, et kui ei pea kirjutama try / catch-plokki, säästab ta kodeeringut.
Näiteks siin on kaks erinevat viisi C-tüüpi valatud selgesõnalise tüübi teostamiseks:
// 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;
Kõige ilmsem viga, mis võib tekkida 2. meetodi kasutamisel, oleks tagastusväärtuse kontrollimata jätmine. Selle tulemuseks oleks tõenäoliselt NullReferenceException, mis võib ilmneda palju hiljem, muutes probleemi allika jälitamise palju raskemaks. Seevastu 1. meetod oleks kohe visanud InvalidCastException
muutes probleemi allika kohe palju ilmsemaks.
Veelgi enam, isegi kui mäletate 2. meetodi tagastusväärtust kontrollida, mida teete siis, kui leiate, et see on null? Kas teie kirjutatav meetod on sobiv koht veast teatamiseks? Kas on midagi muud, mida saate proovida, kui see näitlejaskond ebaõnnestub? Kui ei, siis on erandi viskamine õige, nii et võite lasta sellel juhtuda võimalikult probleemi allikale.
Siin on paar näidet teistest levinud meetodipaaridest, kus üks loob erandi ja teine mitte:
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
Mõni C # arendaja on nii erandlik ebasoodne, et eeldab, et meetod, mis erandit ei tee, on parem. Kuigi on teatud valitud juhtumeid, kus see võib tõsi olla, pole see üldistusena sugugi õige.
Konkreetse näitena võib juhtuda, et kui teil on alternatiivne seaduslik (nt vaikimisi) toiming, kui erand oleks loodud, võib eranditeta lähenemine olla õigustatud valik. Sellisel juhul võib tõesti olla parem kirjutada midagi sellist:
if (int.TryParse(myString, out myInt)) { // use myInt } else { // use default value }
selle asemel:
try { myInt = int.Parse(myString); // use myInt } catch (FormatException) { // use default value }
Siiski on vale eeldada, et TryParse
on seega tingimata “parem” meetod. Mõnikord on see nii, mõnikord mitte. Sellepärast on seda kahel viisil. Kasutage konteksti jaoks õiget, pidades meeles, et erandid võivad kindlasti olla teie kui arendaja sõber.
Kuigi see probleem ei ole kindlasti C # spetsiifiline, on see C # programmeerimisel eriti ränk, kuna loobub C # kompilaatori pakutava range tüübikontrolli eelistest.
Hoiatused luuakse põhjusel. Kui kõik C # kompilaatori vead tähistavad teie koodi defekti, siis ka paljud hoiatused. Neid kahte eristab see, et hoiatuse korral pole kompilaatoril probleeme teie juhitud juhiste väljaandmisega. Sellegipoolest leiab see, et teie kood on veidi ebamäärane ja on tõenäoline, et kood ei kajasta täpselt teie kavatsust.
Selle C # programmeerimisõpetuse jaoks on tavaline lihtne näide see, kui muudate oma algoritmi kasutatava muutuja kasutamise välistamiseks, kuid unustate muutuja deklaratsiooni eemaldada. Programm töötab suurepäraselt, kuid kompilaator märgistab kasutu muutuja deklaratsiooni. Asjaolu, et programm töötab ideaalselt, sunnib programmeerijaid tähelepanuta jätma hoiatuse põhjuse. Lisaks kasutavad kooderid Visual Studio funktsiooni, mis hõlbustab hoiatuste peitmist akna „Vigaloend” peitmisel, et saaksid keskenduda ainult vigadele. Ei lähe kaua, kuni on kümneid hoiatusi, mida kõiki õnnelikult ignoreeritakse (või mis veelgi hullem, varjatud).
Kuid kui te seda tüüpi hoiatusi eirate, võib varem või hiljem midagi sellist teie koodi leida:
class Account { int myId; int Id; // compiler warned you about this, but you didn’t listen! // Constructor Account(int id) { this.myId = Id; // OOPS! } }
Ja kiirusel Intellisense võimaldab meil koodi kirjutada, pole see viga nii ebatõenäoline, kui see välja näeb.
Teie programmis on nüüd tõsine viga (kuigi kompilaator on selle juba selgitatud põhjustel märgistanud ainult hoiatusena) ja sõltuvalt sellest, kui keeruline teie programm on, võite raisata palju aega selle jälgimiseks. Oleksite sellele hoiatusele kõigepealt tähelepanu pööranud, oleksite seda probleemi viie sekundi lihtsa lahendusega vältinud.
Pidage meeles, et C Sharpi kompilaator annab teile palju kasulikku teavet teie koodi vastupidavuse kohta ... kui kuulate. Ärge ignoreerige hoiatusi. Tavaliselt kulub nende parandamiseks vaid mõni sekund ja uute juhtumite parandamine võib juhtuda, et säästate tunde. Treenige end eeldama, et Visual Studio'i aknas „Error List“ kuvatakse „0 viga, 0 hoiatust“, nii et kõik hoiatused muudavad teid piisavalt ebamugavaks, et neid kohe lahendada.
Muidugi on igast reeglist erandeid. Seetõttu võib juhtuda, et teie kood näib kompilaatori jaoks veidi ebamugav, kuigi see on täpselt selline, nagu te seda kavatsesite. Nendel väga harvadel juhtudel kasutage #pragma warning disable [warning id]
ainult hoiatuse käivitava koodi ümber ja ainult selle käivitatava hoiatuse ID jaoks. See surub selle hoiatuse ja ainult selle hoiatuse alla, nii et saate uute hoiatuste osas endiselt tähelepanelik olla.
C # on võimas ja paindlik keel, millel on palju mehhanisme ja paradigmasid, mis võivad oluliselt suurendada tootlikkust. Nagu kõigi tarkvaratööriistade või keelte puhul, võib ka selle võimaluste piiratud mõistmine või hindamine olla mõnikord pigem takistus kui kasu, jättes selle vanasõnalisse olekusse „teadmine piisavalt ohtlikuks”.
Sellise C Sharpi õpetuse kasutamine end kurssi viima koos C # peamiste nüanssidega, näiteks (kuid mitte mingil juhul ainult) käesolevas artiklis tõstatatud probleemid, aitavad C # optimeerimisel, vältides samas selle keele levinumaid lõkse.
Seotud: 6 olulist C # intervjuu küsimustC # on üks mitmest programmeerimiskeelest, mis on suunatud Microsofti CLR-ile, mis annab talle automaatselt eelised keelteülesest integreerimisest ja erandite käsitlemisest, täiustatud turvalisusest, komponentide koostoimimise lihtsustatud mudelist ning silumis- ja profileerimisteenustest.
C ++ ja C # on kaks täiesti erinevat keelt, hoolimata nime ja süntaksiga sarnasustest. C # oli kavandatud mõnevõrra kõrgemale tasemele kui C ++ ja need kaks keelt kasutavad ka erinevaid lähenemisviise üksikasjadele, näiteks sellele, kes määrab, kas parameeter edastatakse viite või väärtuse järgi.
C # kasutatakse mitmel põhjusel, kuid Microsofti CLR-i tööriistakomplekti ja suure arendajaskonna eelised on kaks peamist keelt.