學(xué)習(xí)路線:從基礎(chǔ)語法到面向?qū)ο蠛彤惒骄幊虒?shí)戰(zhàn))
簡介《C#玩家指南》第三版是一份面向C#零基礎(chǔ)讀者的系統(tǒng)性入門教程以Visual Studio 2017為實(shí)踐環(huán)境從安裝配置、編寫第一個(gè)Hello World程序起步漸進(jìn)講解變量、類型系統(tǒng)、數(shù)學(xué)運(yùn)算、決策與循環(huán)、數(shù)組、枚舉、方法等基礎(chǔ)語法并深入類的設(shè)計(jì)、繼承、多態(tài)、泛型等面向?qū)ο蠛诵母拍?。全書的五大部分遞進(jìn)組織入門指南與工具安裝、語言基礎(chǔ)、面向?qū)ο缶幊?、高?jí)主題、工具與實(shí)踐建議高級(jí)部分還涉及文件處理、異常、線程等實(shí)際問題最后給出如何利用.NET平臺(tái)與Visual Studio提升開發(fā)效率的實(shí)用技巧。該資源為單個(gè)PDF文件整體約9.27MB清晰呈現(xiàn)原版目錄、章節(jié)結(jié)構(gòu)與代碼示例適合按章研讀、隨手查閱。目前已有131人學(xué)習(xí)/下載對(duì)需要系統(tǒng)構(gòu)建C#知識(shí)框架的新手尤為實(shí)用既能打牢語法基礎(chǔ)也能初步了解真實(shí)項(xiàng)目開發(fā)中的工具鏈與工程思路。1. 從復(fù)制粘貼到寫會(huì)項(xiàng)目這條 C# 入門路線為什么值得走一遍很多人學(xué) C# 的第一天是從復(fù)制粘貼開始的從一篇教程里抄來 Hello World在另一個(gè)視頻里看幾眼循環(huán)再到問答社區(qū)問為什么 int 除以 int 得不到小數(shù)。我也踩過同樣的坑。后來把《C# 玩家指南》第三版從頭過了一遍才意識(shí)到新手真正缺的并不是某個(gè)語法點(diǎn)而是一條把語法、面向?qū)ο缶幊?、高?jí)主題串起來的完整路徑。這本書從安裝開發(fā)工具開始一路講到泛型、事件、文件和異步編程中間還專門留了一章教你把類用進(jìn)井字棋。適合兩類人零基礎(chǔ)想系統(tǒng)入門 C# 的以及寫過一點(diǎn)代碼但沒理順面向?qū)ο蠛?.NET 工具的。按后面這條路線學(xué)完你至少能獨(dú)立寫出帶用戶輸入、分支循環(huán)、自定義類和文件讀寫的控制臺(tái)程序。2. 整書結(jié)構(gòu)拆解從環(huán)境安裝到線程的五段式學(xué)習(xí)路線整本書一共 51 章拆成六個(gè)部分。前兩段是打地基第三段是提升代碼組織能力第四段是往深度走第五段回到日常工具第六段是一次收口。最明顯的特征是每一部分都依托上一部分的能力來安排知識(shí)點(diǎn)而不是按語法難度硬分成“基礎(chǔ)”和“進(jìn)階”。這種組織方式很適合自學(xué)者因?yàn)槟忝孔x完一章都能在上一個(gè)章節(jié)里找到對(duì)應(yīng)練習(xí)。2.1 第一部分與第二部分先讓程序跑起來再談?wù)Z法第一部分只有四章。第 1 章先回答 C# 是什么、.NET 平臺(tái)是什么讓沒有背景的人建立一個(gè)整體定位。第 2 章講開發(fā)工具怎么選擇包括不同版本怎么區(qū)分、安裝過程里容易漏掉哪些組件以及 Mac 和 Linux 上的替代方案。第 3 章進(jìn)入第一個(gè)程序創(chuàng)建新項(xiàng)目、認(rèn)識(shí) IDE 布局、理解項(xiàng)目、解決方案和程序集的關(guān)系然后是編譯和運(yùn)行。第 4 章是注釋重點(diǎn)不是語法而是什么時(shí)候?qū)懽⑨?、怎么寫注釋不?huì)變成噪音。第二部分是我認(rèn)為最容易勸退新手的部分因?yàn)樗?C# 的核心語法一口氣鋪開。第 5 章變量講數(shù)據(jù)和存儲(chǔ)方式第 6 章類型系統(tǒng)是整本書的地基int、byte、short、long、float、double、decimal、char、bool、string還有數(shù)值字面量和類型推斷 var。第 7 章和第 9 章都是數(shù)學(xué)運(yùn)算但第 9 章明顯更深整數(shù)除法、類型轉(zhuǎn)換、除零、溢出、遞增遞減。第 8 章控制臺(tái)輸入配合 Convert 類型轉(zhuǎn)換是第一次把輸入、運(yùn)算、輸出完整串起來。第 10 章的 if 語句和第 11 章的 switch 語句解決選擇問題第 12 章解決重復(fù)執(zhí)行的問題第 13 章數(shù)組和 foreach 讓你開始處理批量數(shù)據(jù)第 14 章枚舉把魔法數(shù)字變成可讀的名字第 15 章方法教你拆分函數(shù)第 16 章值類型與引用類型則補(bǔ)上了最容易踩坑的內(nèi)存模型基礎(chǔ)。讀這一部分時(shí)我的建議是不要急著背所有類型把 int、double、bool、string、var 用熟其他幾個(gè)用到的時(shí)候回來翻。重點(diǎn)目標(biāo)是能寫一個(gè)至少包含輸入、if/else、while 和方法的控制臺(tái)程序再往后走。很多人在這部分逗留太久反而消磨了興致。2.2 第三部分面向?qū)ο缶幊痰乃膫€(gè)核心抽象從第 17 章開始書進(jìn)入面向?qū)ο缶幊?。?17 章先講 OOP 為什么存在第 18 章讓你親手創(chuàng)建類第 19 章講屬性把字段暴露和封裝的關(guān)系理清。第 20 章是井字棋把前面三章內(nèi)容放進(jìn)一個(gè)完整游戲里這是全書的第一個(gè)小高潮。很多讀者前面都能看懂到第 20 章才意識(shí)到類和對(duì)象到底用來干什么。第 21 章結(jié)構(gòu)體是第一次出現(xiàn)和類相似但不同的類型。第 22 章繼承第 23 章多態(tài)、虛方法和抽象類第 24 章接口這四章是面向?qū)ο缶幊痰暮诵某橄?。書里的解釋方式是先用游戲角色舉例再抽象到普遍場景不會(huì)上來就甩一堆 UML 圖。第 25 章和第 26 章講泛型從使用 List 到定義自己的泛型類型跨度不算大但知識(shí)密度很高。這一部分的練習(xí)建議是把第 20 章的井字棋自己重寫一遍然后嘗試往里加一個(gè)支持繼承的 NPC 類型讓不同 NPC 的反饋不同。能完成這一步面向?qū)ο蟮幕竟退阍鷮?shí)了。2.3 第四到第六部分高級(jí)主題與工具鏈?zhǔn)瘴驳谒牟糠謴拿臻g開始第 28 章重新審視方法講了重載、可選參數(shù)和命名實(shí)參這些在真實(shí)開發(fā)中大量使用。第 29 章讀寫文件第 30 章錯(cuò)誤處理和異常是每個(gè)程序都必須掌握的能力。第 31 章模式匹配給了 C# 7 之后的寫法能把類型判斷和分支寫得干凈。之后是委托、事件、運(yùn)算符重載、索引器、擴(kuò)展方法、Lambda、查詢表達(dá)式、線程、異步編程、動(dòng)態(tài)對(duì)象和不安全代碼。這一部分的章節(jié)不是每章都要精讀。我一般建議初學(xué)者至少先看第 29 章、第 30 章、第 37 章 Lambda 和第 40 章異步編程其他章節(jié)按項(xiàng)目需求補(bǔ)。比如你發(fā)現(xiàn)代碼里大量存在把一個(gè)方法作為參數(shù)傳遞的需求再回頭讀委托和事件。如果你要處理批量內(nèi)存數(shù)據(jù)再看第 38 章查詢表達(dá)式。第五部分回到工具和工程層面第 44 章 .NET 平臺(tái)第 45 章 IDE 使用技巧第 46 章依賴關(guān)系和多個(gè)項(xiàng)目第 47 章處理常見編譯錯(cuò)誤第 48 章調(diào)試代碼第 49 章項(xiàng)目文件組織。對(duì)新人來說第 47 章和第 48 章非常值得提前讀因?yàn)閺牡?3 章寫第一個(gè)程序開始你就會(huì)不停遇到編譯錯(cuò)誤。第六部分只有第 50 章和第 51 章第 50 章給出一組綜合練習(xí)第 51 章提供后續(xù)學(xué)習(xí)方向。我一直認(rèn)為一本入門書如果最后不把讀者指向真實(shí)的下一步就不算完整這本書至少給出了具體路徑。3. 基礎(chǔ)語法核心模塊拆解從 Hello World 到方法前面把章節(jié)結(jié)構(gòu)梳理了一遍現(xiàn)在落到真正能跑的代碼上。我自己讀這種書有一個(gè)習(xí)慣每讀完一章就把它里面的示例代碼敲一遍敲完再改幾個(gè)值看輸出變化。這一章我會(huì)挑書里最核心的幾個(gè)語法點(diǎn)按“新建項(xiàng)目 → 變量 → 控制流 → 方法”的順序拆開講。3.1 第一個(gè) C# 程序Hello World 與項(xiàng)目結(jié)構(gòu)在 IDE 里新建一個(gè)控制臺(tái)項(xiàng)目會(huì)自動(dòng)生成一個(gè) Program.cs。第一次接觸時(shí)代碼看起來是這樣的using System; // 引入 System 命名空間這樣可以寫 Console namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine(Hello, World!); // 在控制臺(tái)輸出一行文字 } } }這里有幾個(gè)固定結(jié)構(gòu)需要理解。using System;相當(dāng)于告訴編譯器“我要用 System 里定義的類”Console就在這個(gè)命名空間里namespace HelloWorld是在給你的類型分組避免不同工程里的同名類沖突。class Program定義了一個(gè)類控制臺(tái)應(yīng)用需要這樣一個(gè)入口類型static void Main是程序入口static表示不需要?jiǎng)?chuàng)建對(duì)象就能調(diào)用void表示不返回任何值string[] args是命令行參數(shù)數(shù)組。這段代碼的作用很單純把字符串輸出到控制臺(tái)。我一般會(huì)讓新手立刻做兩件事第一把WriteLine改成Write觀察輸出差異理解換行的意義第二刪掉static看編譯報(bào)錯(cuò)記住入口方法必須是 static。這種“故意弄壞再修好”的方式比死記規(guī)定有用得多。3.2 變量與類型系統(tǒng)int、double、bool、string 的選擇第 5 章和第 6 章講變量和類型這是理解后面所有代碼的起點(diǎn)。C# 是靜態(tài)類型語言每個(gè)變量聲明時(shí)都要明確類型。下面這一段基本覆蓋了最常用的幾種int lives 3; // 整數(shù)適合生命數(shù)、次數(shù)、數(shù)量 double speed 12.5; // 雙精度浮點(diǎn)數(shù)適合分?jǐn)?shù)、速度、坐標(biāo) bool isGameOver false; // 布爾值適合開關(guān)狀態(tài) string playerName Demo; // 字符串適合名稱、描述文本 int total lives 5; double half speed / 2; Console.WriteLine(${playerName} has {total} lives);類型選擇會(huì)影響計(jì)算結(jié)果的精度。int做除法會(huì)截?cái)嘈?shù)double會(huì)有浮點(diǎn)精度誤差decimal適合貨幣計(jì)算。如果你在寫一個(gè)金額相關(guān)的程序用decimal而不是double否則累計(jì)多了會(huì)出現(xiàn) 0.1 0.2 不等于 0.3 的尷尬。這里還要理解var關(guān)鍵字。C# 允許用var讓編譯器推斷類型var score 10;會(huì)被推斷成int。書里第 6 章專門講了類型推斷我的建議是明確類型寫出來更利于新手排查問題等熟悉之后再在局部變量里用 var。3.3 控制流與數(shù)組if、switch、foreach 的組合拳第 10 到第 13 章的內(nèi)容是所有程序邏輯的骨架。沒有分支和循環(huán)程序就只能從上往下順序執(zhí)行。先看一個(gè)帶 else if 的判斷int score 85; if (score 90) Console.WriteLine(A); else if (score 80) Console.WriteLine(B); else Console.WriteLine(C);單個(gè)if語句塊后面如果只有一行代碼花括號(hào)可以省略但我不建議新手這么寫。省略花括號(hào)在后續(xù)加代碼時(shí)容易出現(xiàn)邏輯歸屬錯(cuò)誤。判斷條件里的是相等判斷是賦值這個(gè)坑在下一章避坑指南里還會(huì)單獨(dú)說。switch 是 if 鏈的替代寫法。C# 的 switch 有幾點(diǎn)和別的語言不同不允許隱式貫穿每個(gè) case 塊必須以break、return或goto結(jié)尾switch 可以匹配整數(shù)、字符串、枚舉配合模式匹配還能做更復(fù)雜的判斷。string enemyType Goblin; switch (enemyType) { case Slime: Console.WriteLine(Splits into two); break; case Goblin: Console.WriteLine(Throws a rock); break; default: Console.WriteLine(A mysterious enemy); break; }數(shù)組是批量數(shù)據(jù)的容器。創(chuàng)建數(shù)組時(shí)長度一旦確定就不能改變。foreach 是遍歷數(shù)組最省事的寫法不用關(guān)心下標(biāo)string[] enemies { Slime, Goblin, Boss }; foreach (string enemy in enemies) { Console.WriteLine($Encounter: {enemy}); } for (int i 0; i enemies.Length; i) { Console.WriteLine($Round {i 1}: {enemies[i]}); }foreach 適合按順序讀取每個(gè)元素for 循環(huán)適合需要知道下標(biāo)的場景。如果你想邊遍歷邊刪除元素直接用 foreach 會(huì)拋異常常見做法是把要?jiǎng)h的對(duì)象收集到另一個(gè)列表里遍歷結(jié)束后再統(tǒng)一移除。3.4 方法與作用域把重復(fù)邏輯拆成可復(fù)用函數(shù)方法是對(duì)一段邏輯命名方便復(fù)用和測試。第 15 章會(huì)講方法的定義、參數(shù)和返回值但真正的深度在第 28 章重訪方法時(shí)。第一個(gè)可運(yùn)行的方法結(jié)構(gòu)是這樣的static int Add(int a, int b) { return a b; } static void PrintMessage(string message) { Console.WriteLine(message); }Add方法返回int調(diào)用后能得到一個(gè)整數(shù)結(jié)果PrintMessage返回void它只負(fù)責(zé)輸出不產(chǎn)生返回值。方法的參數(shù)類型也有限制傳進(jìn)去的實(shí)參類型必須能隱式轉(zhuǎn)換為形參類型否則要先轉(zhuǎn)換。關(guān)于方法參數(shù)我最想提醒的是值傳遞和引用傳遞的區(qū)別也就是第 16 章的內(nèi)容int是值類型傳參時(shí)復(fù)制一份class是引用類型傳參時(shí)復(fù)制的是引用修改對(duì)象內(nèi)容會(huì)影響到原對(duì)象。這個(gè)區(qū)別不搞清楚后面寫數(shù)據(jù)結(jié)構(gòu)時(shí)會(huì)經(jīng)常發(fā)現(xiàn)“為什么我在方法里改了值外面沒變”。4. 面向?qū)ο笈c高級(jí)主題拆解從類到異步的完整鏈路基礎(chǔ)語法只是工具面向?qū)ο缶幊滩攀前汛a組織成可維護(hù)結(jié)構(gòu)的關(guān)鍵。這本書的第三部分不只教你怎么寫 class還會(huì)解釋為什么需要屬性和封裝繼承和多態(tài)到底解決了什么問題。第四部分則把這些能力延伸到文件、異常、并發(fā)場景。這一章我會(huì)用一組游戲相關(guān)的例子把類和接口、泛型、異步的寫法串起來。4.1 類、屬性與封裝用玩家對(duì)象承載狀態(tài)先做一個(gè)最簡單的玩家類。字段存數(shù)據(jù)屬性對(duì)外暴露數(shù)據(jù)方法定義行為。這是面向?qū)ο蠓庋b的核心public class Player { private string name; private int health; public Player(string name, int health) { this.name name; this.health health; } public string Name { get { return name; } set { name value; } } public int Health { get { return health; } set { health value; } } public void TakeDamage(int amount) { health - amount; if (health 0) health 0; Console.WriteLine(${name} took {amount} damage, {health} HP left); } }構(gòu)造函數(shù)Player在創(chuàng)建對(duì)象時(shí)執(zhí)行保證對(duì)象一開始就有合理的狀態(tài)。Name和Health是屬性它們的 getter 負(fù)責(zé)讀setter 負(fù)責(zé)寫。你也可以在 setter 里加校驗(yàn)比如 Health 不能小于 0。字段name和health是 private外部代碼無法直接修改只能通過方法或?qū)傩栽L問這就是封裝。書里第 19 章特意把屬性和字段對(duì)比講因?yàn)楹芏嘈率謺?huì)直接public int health;雖然能跑但后續(xù)無法在賦值時(shí)加日志或校驗(yàn)?,F(xiàn)代寫法里如果不需要額外邏輯可以直接寫自動(dòng)屬性public string Name { get; set; }。它背后有一個(gè)隱藏字段幫你存儲(chǔ)。不過初學(xué)階段建議先把普通屬性寫熟練理解 get 和 set 到底是什么。4.2 繼承、多態(tài)與接口讓敵人和道具各司其職繼承解決的是“同類事物共享行為”的問題?;惗x通用行為子類通過 override 提供具體實(shí)現(xiàn)public class Enemy { public virtual void Attack() { Console.WriteLine(Enemy attacks); } } public class Goblin : Enemy { public override void Attack() { Console.WriteLine(Goblin throws a rock); } } public class Slime : Enemy { public override void Attack() { Console.WriteLine(Slime splits and lunges); } }virtual告訴編譯器“這個(gè)方法可以在子類中重寫”override則真正替換了基類的實(shí)現(xiàn)。如果你漏寫任何一個(gè)關(guān)鍵字調(diào)用Goblin.Attack()時(shí)可能執(zhí)行的是基類版本多態(tài)就會(huì)失效。在游戲里你只需要把對(duì)象聲明成Enemy實(shí)際運(yùn)行時(shí)它會(huì)自己決定調(diào)用哪個(gè)版本Enemy e1 new Goblin(); Enemy e2 new Slime(); e1.Attack(); // 輸出 Goblin throws a rock e2.Attack(); // 輸出 Slime splits and lunges接口則更純粹只定義能力不包含實(shí)現(xiàn)public interface ICollectible { void Collect(); } public class Coin : ICollectible { public void Collect() { Console.WriteLine(Coin collected); } } public class Key : ICollectible { public void Collect() { Console.WriteLine(Key collected); } }接口適合描述“能做什么”繼承適合描述“是什么”。一個(gè)類可以實(shí)現(xiàn)多個(gè)接口但只能繼承一個(gè)基類。設(shè)計(jì)時(shí)我一般先問自己這些類型真的共享同一套狀態(tài)和行為嗎如果是用繼承如果只是需要統(tǒng)一調(diào)用某個(gè)方法就用接口。4.3 泛型與集合用 List 和自定義泛型類消滅重復(fù)代碼第 25 章和第 26 章講泛型。泛型解決的問題是同樣的邏輯不想為每種類型寫一遍。最常用的是ListT、DictionaryTKey, TValueListstring itemNames new Liststring(); itemNames.Add(Sword); itemNames.Add(Shield); Dictionarystring, int inventory new Dictionarystring, int(); inventory[Gold] 50; inventory[Silver] 20;ListT是動(dòng)態(tài)數(shù)組你不用管擴(kuò)容。DictionaryTKey, TValue是鍵值對(duì)按鍵訪問速度很快。自定義泛型類型也不難語法是在類名后面加尖括號(hào)public class RepositoryT { private T item; public void Store(T value) { item value; } public T Retrieve() { return item; } }這里T是一個(gè)占位符使用時(shí)才確定具體類型。比如RepositoryPlayer playerRepo new RepositoryPlayer();。泛型真正的好處在于編譯期就能檢查類型減少運(yùn)行時(shí)裝箱和強(qiáng)制轉(zhuǎn)換。4.4 文件、異常與異步讓程序穩(wěn)定運(yùn)行并響應(yīng)等待第 29 章讀寫文件、第 30 章異常處理、第 39 和第 40 章線程與異步這三塊是很多入門讀者最容易忽略、但實(shí)際項(xiàng)目中會(huì)被反復(fù)用到的能力。文件讀寫最怕的就是文件不存在或格式不對(duì)所以異常處理一定是配套出現(xiàn)的try { string content File.ReadAllText(data.txt); Console.WriteLine(content); } catch (FileNotFoundException ex) { Console.WriteLine($File missing: {ex.Message}); } catch (Exception ex) { Console.WriteLine($Unexpected error: {ex.StackTrace}); } finally { Console.WriteLine(Cleanup done); }try塊里放可能出錯(cuò)的代碼catch按異常類型捕獲越具體的類型放越前面finally無論是否出錯(cuò)都會(huì)執(zhí)行適合做釋放資源的收尾。我見過很多新手在 catch 里只寫一句Console.WriteLine(error)這樣會(huì)丟失堆棧信息排錯(cuò)全靠猜。正確做法至少要把ex.Message和ex.StackTrace輸出到日志。異步編程是另一個(gè)重點(diǎn)??刂婆_(tái)程序里如果讀取大文件時(shí)用同步方式界面會(huì)卡住用異步方法程序可以繼續(xù)響應(yīng)其他操作static async Taskstring ReadFileAsync(string path) { string content await File.ReadAllTextAsync(path); return content; }async標(biāo)記方法可能包含 awaitawait表示等待異步操作完成但不會(huì)阻塞當(dāng)前線程。這里要特別注意如果調(diào)用鏈里某個(gè)方法沒有用 await而是直接.Result在多線程環(huán)境里可能出現(xiàn)死鎖。異步編程最核心的一條原則是整個(gè)調(diào)用鏈都要 async 化不要半路用同步方式阻塞回去。這個(gè)點(diǎn)也是面試和技術(shù)討論里常被拿出來追問的“高級(jí)主題”。5. 避坑指南編譯、類型、繼承、調(diào)試?yán)锏母甙l(fā)問題這本書讀下來真正把新手?jǐn)r住的往往不是概念而是幾個(gè)反復(fù)出現(xiàn)的編譯和運(yùn)行期問題。我把最常見的幾種整理成“現(xiàn)象 → 原因 → 解決”的記錄方便你卡住時(shí)對(duì)照排查。5.1 新建項(xiàng)目后提示沒有入口Main 方法不生效現(xiàn)象剛創(chuàng)建好項(xiàng)目按運(yùn)行按鈕卻報(bào)錯(cuò)說找不到合適的入口方法。原因最常見的是把類名改了而沒改Main或者手動(dòng)新建 .cs 文件后忘了寫static void Main。還有種情況是項(xiàng)目里有多個(gè)類都寫了 Main啟動(dòng)對(duì)象沒有選中你真正想運(yùn)行的那個(gè)。解決檢查 Program.cs 里是否保留了static void Main(string[] args)。如果存在多個(gè)入口右鍵項(xiàng)目選擇“設(shè)為啟動(dòng)項(xiàng)目”或到項(xiàng)目屬性的“應(yīng)用程序”頁簽里調(diào)整啟動(dòng)對(duì)象。養(yǎng)成一個(gè)習(xí)慣任何編譯錯(cuò)誤都先看是哪一行再判斷是語法問題還是入口問題。5.2 整數(shù)除法結(jié)果變成 0等號(hào)和賦值混用現(xiàn)象int a 5; int b 2; Console.WriteLine(a / b);輸出 2而不是 2.5。或者在if (a 1)處編譯報(bào)錯(cuò)。原因int類型相除結(jié)果只保留整數(shù)部分這是很多語言都有的行為C# 里也不例外。是賦值才是相等判斷寫混了編譯器會(huì)直接攔截。解決想讓保留小數(shù)至少讓其中一個(gè)操作數(shù)是浮點(diǎn)類型比如(double)a / b。判斷相等時(shí)寫成if (a 1)。順帶記住浮點(diǎn)數(shù)用判斷相等也不可靠應(yīng)該比較兩個(gè)數(shù)差的絕對(duì)值是否小于一個(gè)很小的閾值。書里第 9 章用一整節(jié)講“使用不同類型的轉(zhuǎn)換”就是把這類邊界情況列清楚了。5.3 子類方法沒被調(diào)用多態(tài)失效現(xiàn)象定義了一個(gè)Goblin : Enemy子類重寫了 Attack 方法但調(diào)用時(shí)輸出的還是基類的 “Enemy attacks”。原因基類方法沒有標(biāo)virtual子類方法沒有標(biāo)override。C# 里子類方法默認(rèn)是隱藏基類方法不是重寫。這時(shí)候編譯器通常會(huì)給警告但很多人看都不看就運(yùn)行了。解決確定你要重寫時(shí)基類方法加virtual子類方法加override。如果只是一個(gè)類要實(shí)現(xiàn)能力優(yōu)先考慮接口而不是繼承。這里有一個(gè)判斷技巧寫完子類后用基類類型的變量去調(diào)用方法如果輸出不是子類版本就說明多態(tài)沒有建立逐個(gè)檢查關(guān)鍵字是否齊全。5.4 catch 吞掉異常斷點(diǎn)不觸發(fā)現(xiàn)象程序運(yùn)行出錯(cuò)但控制臺(tái)只有一行簡單提示看不到任何堆棧信息或者設(shè)置了斷點(diǎn)程序卻一次都沒停下。原因catch 塊里只寫了Console.WriteLine(error)沒有打印異常對(duì)象的細(xì)節(jié)斷點(diǎn)沒觸發(fā)則可能是因?yàn)楫?dāng)前配置是 Release 模式或者你把斷點(diǎn)打在了不會(huì)執(zhí)行到的分支里。解決排查異常時(shí)在 catch 塊里打印ex.Message和ex.StackTrace這兩個(gè)字段幾乎能定位 90% 的問題。調(diào)試問題時(shí)確認(rèn)頂部下拉框選的是 Debug 配置再重新生成解決方案。斷點(diǎn)別打在空行或方法聲明上打到實(shí)際執(zhí)行的賦值語句和調(diào)用語句上。如果你發(fā)現(xiàn)斷點(diǎn)圖標(biāo)帶感嘆號(hào)鼠標(biāo)懸停會(huì)提示不命中的原因。6. 改造與擴(kuò)展把這本書變成第一組實(shí)戰(zhàn)項(xiàng)目讀這本書最怕的狀態(tài)是“每章都看懂了關(guān)上書什么也寫不出”。我自己的解法是把書里的知識(shí)點(diǎn)全部折算成一個(gè)小項(xiàng)目用項(xiàng)目把這些知識(shí)點(diǎn)串起來。下面這條改造路線可以直接照做不需要額外引入框架純控制臺(tái)項(xiàng)目就能完成。第一步用第二部分的知識(shí)寫一個(gè)猜數(shù)字游戲。程序生成一個(gè) 1 到 100 的隨機(jī)數(shù)玩家循環(huán)輸入太大給提示太小給提示猜中結(jié)束。這個(gè)小項(xiàng)目能覆蓋變量、類型轉(zhuǎn)換、while、if、break。第二步用第 17 到第 20 章的知識(shí)把它改造成面向?qū)ο蟀姹?。定義Game、Player、NumberGenerator三個(gè)類把流程拆進(jìn)方法里。你會(huì)發(fā)現(xiàn)原來寫在一坨的代碼拆開之后更容易加入新規(guī)則。第三步用第 29 章和第 30 章加存檔功能。每局結(jié)束把玩家姓名、難度、用時(shí)寫進(jìn)文本文件下次啟動(dòng)時(shí)讀取并顯示歷史記錄。這里會(huì)自然用到File.WriteAllText和try-catch。第四步如果你還想繼續(xù)深入引入接口和泛型。定義IGameMode接口讓猜數(shù)字、猜單詞、記憶翻牌各實(shí)現(xiàn)一個(gè)模式再用ListIGameMode統(tǒng)一管理。到這里你實(shí)際上已經(jīng)把面向?qū)ο?、接口、泛型、文件、異常都練了一遍能力已?jīng)遠(yuǎn)超“入門”范疇。驗(yàn)證方式也很直接先按正常流程玩一遍再輸入非法值看程序是否報(bào)錯(cuò)接著用調(diào)試器給Main入口打斷點(diǎn)單步走完整個(gè)流程最后讓另一個(gè)人去試玩不提供任何說明看他能不能順利上手。這幾關(guān)過了這本書對(duì)你來說就算真正讀完了。從那以后我每次讀完一本技術(shù)書都強(qiáng)制自己在一周內(nèi)完成一個(gè)能跑的小項(xiàng)目驗(yàn)證完才敢說學(xué)會(huì)了。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取