Visual Studio
Before you start
You need: any C# project to open — the console application from Track 03 is ideal.
Take this track alongside a programming track, not after it. The tools are only useful with something to point them at.
Time: about 50 minutes, at the keyboard.
Learning objective
Open an unfamiliar solution, build it, navigate it by keyboard, and debug a running application.
Topics
- Solutions and projects
- Opening and building
- Navigation shortcuts
- The debugger
- Watch, Call Stack and Immediate
- Refactoring
- NuGet
- Diagnosing a build failure
Solutions and projects
NexCoding.SchoolPortal.sln the solution — a container
├── NexCoding.SchoolPortal.Domain a project — compiles to one assembly
├── NexCoding.SchoolPortal.Data
├── NexCoding.SchoolPortal.Api
└── NexCoding.SchoolPortal.Tests
A solution groups projects. A project produces one .dll or .exe.
Solution Explorer is the map. Open a .sln and the first thing to read is the project list and the reference direction — which projects depend on which. In a layered application that tells you where everything lives before you open a single file.
Ctrl+Alt+L opens Solution Explorer. Ctrl+; searches it, which is faster than scrolling once there are more than about thirty files.
Set the startup project by right-clicking it — Visual Studio runs whichever is bold, and running the wrong one is a common first-day confusion in a multi-project solution.
Opening and building
| Action | Shortcut |
|---|---|
| Build solution | Ctrl+Shift+B |
| Rebuild | — (menu) |
| Clean | — (menu) |
| Run with debugging | F5 |
| Run without debugging | Ctrl+F5 |
| Stop | Shift+F5 |
Build compiles what changed; Rebuild cleans and compiles everything. Reach for Rebuild when the build succeeds but the running code is clearly stale — usually a locked or orphaned .dll in bin.
The Error List (Ctrl+\, E) shows errors and warnings; Output (Ctrl+Alt+O) shows what MSBuild actually did. When the Error List is empty and the build still failed, read Output — the real message is there.
Fix the first error first. One missing using produces twenty cascading errors, and nineteen of them disappear when the first is fixed.
Navigation
These are the shortcuts that separate someone comfortable in the IDE from someone scrolling.
| Action | Shortcut |
|---|---|
| Go to definition | F12 |
| Peek definition | Alt+F12 |
| Go to implementation | Ctrl+F12 |
| Find all references | Shift+F12 |
| Go to file | Ctrl+, |
| Go to line | Ctrl+G |
| Navigate backward | Ctrl+- |
| Navigate forward | Ctrl+Shift+- |
| Find in files | Ctrl+Shift+F |
| Rename | Ctrl+R, R |
| Quick actions | Ctrl+. |
| Comment selection | Ctrl+K, C |
F12 then Ctrl+- is how you read a codebase: follow a call, read the implementation, come back. Doing that ten times through a call chain is faster and more reliable than searching by name.
Shift+F12 before changing anything. Find all references answers "what breaks if I change this" in one keystroke, and it finds usages a text search misses — an interface implementation, a generic instantiation.
Ctrl+, accepts partial and camel-case matches. Typing stusrv finds StudentService.
Ctrl+. offers the fixes for whatever the cursor is on: add a missing using, generate a constructor, implement an interface, introduce a variable. It is the single most useful key in the IDE.
The debugger
Click the left gutter, or press F9, to set a breakpoint. Press F5 and execution stops there.
| Action | Shortcut |
|---|---|
| Toggle breakpoint | F9 |
| Continue | F5 |
| Step over | F10 |
| Step into | F11 |
| Step out | Shift+F11 |
| Run to cursor | Ctrl+F10 |
| Restart | Ctrl+Shift+F5 |
Step over runs the called method and moves to the next line. Step into enters it. Stepping into every framework call is how debugging becomes slow — step over until you reach code you own.
Step out finishes the current method and returns to the caller. Use it the moment you realise you have stepped into something irrelevant.
Conditional breakpoints
Right-click a breakpoint → Conditions.
student.RollNumber == "NCA-2024-0012"
This is the feature that makes debugging a loop practical. Stopping on one student out of four hundred, instead of pressing F5 four hundred times, is the difference between a two-minute investigation and abandoning the debugger for Console.WriteLine.
| Condition type | Does |
|---|---|
| Conditional expression | Break only when true |
| Hit count | Break on the 50th pass |
| Filter | Break on a specific thread |
Actions on a breakpoint print a message and continue — a Console.WriteLine that is not in your source code and cannot be accidentally committed.
Watch, Call Stack and Immediate
Locals shows every variable in scope. Autos shows those used near the current line.
Watch (Ctrl+Alt+W, 1) evaluates expressions you add, re-evaluated at every step:
students.Count(s => s.Status == StudentStatus.Active)
account.TotalFees - account.PaidAmount
Any valid C# expression works, including method calls — though calling a method with side effects from a watch changes program state, which is a genuine trap.
Call Stack (Ctrl+Alt+C) shows how execution reached here. Click any frame to jump there and inspect its locals.
That is how you find a bug whose symptom is three calls below its cause: the value is wrong here, so walk up the stack until you find the frame where it was still right.
Immediate (Ctrl+Alt+I) runs arbitrary code against the paused process:
? students.Count
? repository.GetByRollNumber(1, "NCA-2024-0012")
student.ClassName = "11th"
You can read values, call methods and assign to variables — testing a hypothesis without editing, rebuilding and re-running.
Exception settings
Debug → Windows → Exception Settings (Ctrl+Alt+E), tick Common Language Runtime Exceptions.
The debugger now breaks at the throw site rather than at the catch. For an exception swallowed several layers down, that is the only practical way to find where it originated.
Turn it off again afterwards — a codebase using exceptions for control flow becomes unusable with it on.
Refactoring
| Refactoring | Shortcut |
|---|---|
| Rename | Ctrl+R, R |
| Extract method | Ctrl+R, M |
| Extract interface | Ctrl+R, I |
| Encapsulate field | Ctrl+R, E |
| Remove and sort usings | Ctrl+R, G |
| Format document | Ctrl+K, Ctrl+D |
Always rename with Ctrl+R, R, never with find-and-replace. The refactoring is semantic — it renames the symbol and every reference to it, and leaves an unrelated variable of the same name alone. Find-and-replace does not know the difference.
Ctrl+. offers context-appropriate refactorings for whatever the cursor is on.
EditorConfig enforces formatting across the team:
# .editorconfig
root = true
[*.cs]
indent_style = space
indent_size = 4
csharp_new_line_before_open_brace = all
dotnet_sort_system_directives_first = true
Committed to the repository, it means every developer's IDE formats identically — and diffs stop containing whitespace changes.
NuGet
Right-click a project → Manage NuGet Packages. The four tabs do everything:
| Tab | Does |
|---|---|
| Browse | Search and install. The version dropdown pins a specific one |
| Installed | What this project has, and Uninstall |
| Updates | What has a newer version, with Update All |
| Consolidate | Appears when two projects use different versions of the same package — fix that |
Consolidate is the tab people never open and should. Two projects on different versions of the same package builds fine and fails at run time.
The same operations in the Package Manager Console (Tools → NuGet Package Manager → Package Manager Console):
Install-Package Dapper
Install-Package Dapper -Version 2.1.35
Uninstall-Package Dapper
Get-Package
To check for vulnerable packages, right-click the solution → Manage NuGet Packages for Solution → Updates, or run this one-liner in a terminal:
dotnet list package --vulnerable --include-transitive
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>
dotnet list package --vulnerable is worth running regularly. It reports known CVEs in your dependency tree, including transitive ones — and it takes seconds.
Pin exact versions in an application. A floating version means two developers, or the build server, get different code from the same commit. Libraries use ranges; applications pin.
Read the transitive dependencies before adding a package. One convenience package can pull in twenty others, each of which is now your problem to keep patched.
A Directory.Packages.props centralises versions across a multi-project solution, so every project uses the same version of a shared package.
Diagnosing a build failure
| Symptom | Cause |
|---|---|
| Twenty errors from one change | Fix the first; the rest usually cascade |
| "Type or namespace could not be found" | Missing using, or a missing project reference |
| "Could not load file or assembly" | Version mismatch, or a stale bin — Rebuild |
| Builds, but old code runs | Stale build, or the wrong startup project |
| Works locally, fails on the server | A dependency not committed, or a version difference |
| "The process cannot access the file" | The application is still running — Shift+F5 |
| Restore fails | No network, or a private feed not configured |
dotnet build -v normal # more detail
dotnet build -v diagnostic # everything
When the Error List is unhelpful, dotnet build from a terminal often gives a clearer message than the IDE.
Delete bin and obj as a last resort. It fixes a genuine class of stale-artefact problem, and it is not a substitute for reading the error.
Errors you will hit
| What you see | Cause | Fix |
|---|---|---|
| Breakpoint is hollow and never hits | Stale build, or wrong startup project | Rebuild; right-click → Set as Startup Project |
| A template you expect is missing | Workload not installed | Tools → Get Tools and Features |
Ctrl+. offers nothing | Cursor not on the symbol | Put it on the identifier |
| Find All References misses a usage | Late binding or reflection | Use Find in Files as well |
| The console window closes instantly | Ran with F5 | Use Ctrl+F5 |
| IntelliSense stops working | Corrupted cache | Close, delete .vs, reopen |
Deleting the hidden .vs folder fixes a surprising share of Visual Studio oddities. It is regenerated on the next open.
Common mistakes
- Scrolling Solution Explorer instead of
Ctrl+, - Not using
F12andCtrl+-to read a codebase - Changing a method without
Shift+F12first - Renaming with find-and-replace instead of
Ctrl+R, R Console.WriteLinedebugging instead of breakpoints- Not knowing conditional breakpoints exist
- Stepping into every framework call
- Leaving "break on all exceptions" enabled
- Fixing the last error first
- Floating package versions in an application
- Never checking
--vulnerable - Running the wrong startup project
Practice
The course exercise is open, build and debug a solution.
- Open a multi-project solution. List every project and its references without opening a file.
- Build it. Introduce a syntax error and read the Error List and the Output window.
- Introduce one missing
usingand count the resulting errors. Fix the first and re-count. - Navigate a call chain four levels deep with
F12, then return withCtrl+-. - Use
Shift+F12on a method before changing it. Note any usage a text search would miss. - Find a class with
Ctrl+,using a camel-case abbreviation. - Set a breakpoint in a loop over 400 students. Press
F5and count how many times you have to continue. - Add a condition for one roll number and repeat.
- Use a breakpoint action to print a value without editing the code.
- Step into a method, realise it is irrelevant, and step out.
- Add two Watch expressions and step through, watching them change.
- Use the Call Stack to jump three frames up and inspect that frame's locals.
- Use the Immediate window to call a repository method and assign to a variable.
- Enable break-on-all-CLR-exceptions and find where a swallowed exception originates. Turn it off.
- Rename a method with
Ctrl+R, R. Then rename another with find-and-replace and find what it broke. - Add an
.editorconfigand format a badly formatted file. - Run
dotnet list package --outdatedand--vulnerable.
Exercises 7, 8 and 14 are the three that most change how you debug.
You can now
- Open an unfamiliar solution and navigate it by keyboard
- Use
F12,Shift+F12,Ctrl+,andCtrl+. - Set a conditional breakpoint and a tracepoint
- Read Watch, Locals and the Call Stack
- Find a swallowed exception with Exception Settings
- Manage NuGet packages and check for vulnerable ones
Review questions
- What does
Shift+F12tell you that a text search does not? - When does a conditional breakpoint save the most time?
- Why rename with
Ctrl+R, Rrather than find-and-replace? - Why pin exact package versions in an application?
Next: VS Code