BuildQuill
C# learning path
10 of 25
Lesson 10 of 25
Lesson 10Beginner18 min

C# Scope and How Values Move

Understand where names exist, why local changes sometimes stay local, and how reference-type objects can be shared between callers and methods.

Before this lesson

  • Methods and parameters
  • Variables
  • Blocks and braces

Predict where local variables are visible

Explain pass-by-value for value and reference types

Avoid hidden shared-state changes

The short answer

Scope is the region in which a name can be used. C# passes arguments by value by default: a method receives its own parameter variable. For a value type that copies the value; for a reference type it copies the reference, so both variables can point to the same object.

01

Scope limits where a name can be used

A local variable exists from its declaration to the end of its containing block, subject to C#'s definite-assignment rules. A variable declared inside an if block cannot be read afterward because that branch may not run and because the name's scope ends at the closing brace.

Narrow scope reduces accidental interaction. A loop counter belongs inside the loop when no later code needs it. A calculated result belongs outside a branch only when all relevant paths assign it. Do not widen a variable's scope merely to avoid thinking about ownership.

C#
if (temperature < 0)
{
  string warning = "Ice risk";
  Console.WriteLine(warning);
}

// warning is not in scope here
02

Parameters are local variables

C# passes arguments by value unless ref, out, or in explicitly changes that rule. For an int, the copied value is the number itself. Assigning to the parameter changes only that local copy, not the caller's variable.

Returning the new value makes the data flow explicit. A caller can choose to store it or ignore it. ref can allow a method to assign the caller's variable, but it creates a tighter relationship and is unnecessary for most ordinary transformations.

C#
using System;

class Program
{
  static int AddBonus(int score)
  {
      score += 10;
      return score;
  }

  static void Main()
  {
      int original = 50;
      int withBonus = AddBonus(original);

      Console.WriteLine(original);
      Console.WriteLine(withBonus);
  }
}

Expected output

50
60
03

A copied reference can still reach the same object

Classes, arrays, strings, and List<T> are reference types. A variable holding one of them contains a reference to an object. Passing it by value copies that reference. The caller and method then have separate variables, but both references can point to the same mutable object.

That is why AddGuest can change the shared List. Reassigning the parameter to a new list would only change the method's local reference, but calling Add on the existing object changes the object both references reach. Strings are reference types too, but their immutability prevents character-by-character mutation.

ArgumentDefault copyWhat method changes can affect
int, bool, decimal, structThe valueIts local copy unless returned or passed with ref/out
class, array, List<T>The referenceThe shared object's mutable state
stringThe referenceNo characters in the existing string; strings are immutable
04

Make mutation visible in the design

A method named GetGuestCount should not quietly remove guests. Names, return types, and parameters should help a caller predict side effects. When practical, return a new value rather than mutating distant shared state. When mutation is the point, use an action name such as AddGuest and keep ownership clear.

Fields and properties have wider lifetimes than local variables and can make several methods depend on shared state. They are useful inside well-designed objects, but global mutable state makes tests and reasoning difficult. Start with locals and explicit parameters, then move state into an object that owns the relevant rules.

Good habits

  • Declare a variable in the narrowest block that contains all legitimate uses.
  • Return transformed value types instead of using ref by default.
  • Document or name methods so mutation is unsurprising.

Quick knowledge check

Answer before you reveal.

01Does default argument passing copy an entire List object?

No. It copies the reference value; both variables can still point to the same List object.

02Why can a variable declared inside an if block be unavailable afterward?

Its scope ends with that block, and the branch may not have run.

Practice challenge

Now build it without copying.

Write one method that returns a discounted decimal without changing the caller's variable, and another method that adds an item to a passed List<string>. Print before and after values to explain both behaviors.

You are done when

  • The original decimal remains unchanged until the returned value is assigned
  • The list count changes after the method call
  • Your output labels identify caller state before and after each call

Stretch: Inside the list method, assign the parameter to a new list and add an item. Observe why the caller's list no longer changes from that addition.

Open challenge in playground

Lesson checkpoint

One small step locks it in

Mark this lesson complete, then keep the momentum going.

Complete and continue

Clear up the details

Frequently asked questions

Are value types always stored on the stack?

No. Storage depends on context and runtime decisions. The useful language-level distinction is copy semantics and whether a variable contains a value or a reference, not a simplistic stack-versus-heap slogan.

When should I use ref or out?

Use them when an API genuinely needs to write through a caller-provided variable or return an additional result, as TryParse does with out. A normal return value is clearer for most methods.

Can two local variables use the same name?

They can in separate non-overlapping scopes, but shadowing and reused names can confuse readers. Prefer distinct meaningful names when the values play different roles.