pavelzosim:~/atlas_SYS.ONLINE / UTC+3

~/blog/csharp-variables-lesson-1-part-3.md

C# Variables Lesson 1 Part 3

C# variables lesson 1 part 3 covering advanced variable usage and practical examples. Structured introduction for beginners and technical artists.

01 Overview

This C# variables lesson 1 part 3 continues the introduction to basic data types and variable usage for technical art workflows.

Money vs. gameplay math. decimal is exact but 10–100× slower than float/double — use it only for prices and currency, never inside Update() or any per-frame loop. Keep gameplay math (positions, physics, timers) on float.

decimal — money and financial calculations

Beginner explanation: The MOST precise decimal type, designed specifically for money and financial calculations. Has ~28-29 digits of precision and avoids the rounding errors that plague float/double. ALWAYS use decimal for currency, prices, and financial data. Much slower than float/double, so NEVER use in game loops or real-time calculations. Requires 'm' or 'M' suffix!

Technical specs

  • Size: 16 bytes (128 bits)
  • Precision: ~28-29 significant digits
  • Range: ±1.0 × 10⁻²⁸ to ±7.9228 × 10²⁸
  • Default Value: 0.0m

Performance notes

  • VERY SLOW - 10-100x slower than float/double
  • No hardware acceleration (software-emulated)
  • Uses 4x memory of float (16 vs 4 bytes)
  • EXACT - no rounding errors for decimal values
  • Perfect for financial calculations
  • NEVER use in Update()/FixedUpdate() - too slow!
  • Use ONLY for money, prices, currency

When to use decimal

USE
  • Money/Currency (prices, costs, balances)
  • Financial calculations (interest, tax, exchange rates)
  • Accounting/billing systems
  • Shop UI (display prices)
  • Any calculation where exact decimal precision is legally required
AVOID
  • NEVER in game loops (Update, FixedUpdate)
  • NEVER for positions, physics, transforms
  • NEVER for real-time calculations
  • Don't use if float/double are sufficient

Syntax and declaration

// CRITICAL: Always add 'm' or 'M' suffix!
decimal price = 19.99m; // Correct
decimal tax = 0.15m; // Correct (15%)
decimal wrong = 19.99; // Error! Needs 'm' suffix

// Financial examples
decimal accountBalance = 1234567.89m;
decimal interestRate = 0.0425m; // 4.25%
decimal productPrice = 49.95m;
decimal totalCost = 199.99m;

// High precision
decimal precise = 0.1234567890123456789012345678m; // 28 digits!

Practical use cases

// In-game shop/economy (ALWAYS use decimal for money!)
decimal itemPrice = 9.99m;
decimal playerGold = 1500.50m;
decimal taxRate = 0.08m; // 8% tax

decimal CalculateTotalCost(decimal price, decimal taxRate)
{
 return price * (1m + taxRate); // Exact calculation
}

decimal totalCost = CalculateTotalCost(itemPrice, taxRate);
playerGold -= totalCost;

// Shop system
class ShopItem
{
 public string Name;
 public decimal Price;
 public int Quantity;
}

ShopItem sword = new ShopItem 
{ 
 Name = "Legendary Sword", 
 Price = 499.99m, 
 Quantity = 1 
};

// Financial calculations (exact, no rounding errors!)
decimal principal = 1000.00m;
decimal annualRate = 0.05m; // 5%
decimal interest = principal * annualRate; // Exactly 50.00

decimal CompoundInterest(decimal principal, decimal rate, int years)
{
 decimal amount = principal;
 for (int i = 0; i < years; i++)
 {
 amount *= (1m + rate);
 }
 return amount - principal;
}

// Currency conversion
decimal usdAmount = 100.00m;
decimal exchangeRate = 1.18m; // USD to EUR
decimal eurAmount = usdAmount * exchangeRate; // Exactly 118.00

// Inventory system with precise values
decimal weaponPrice = 499.99m;
decimal armorPrice = 799.99m;
decimal discount = 0.15m; // 15% off

decimal CalculateDiscountedPrice(decimal originalPrice, decimal discountPercent)
{
 return originalPrice * (1.0m - discountPercent);
}

decimal totalOriginal = weaponPrice + armorPrice; // 1299.98
decimal totalDiscounted = CalculateDiscountedPrice(totalOriginal, discount); // 1104.98

// Subscription/billing
decimal monthlyFee = 14.99m;
decimal yearlyFee = monthlyFee * 12m; // Exactly 179.88
decimal yearlyWithDiscount = 149.99m; // Annual plan price
decimal savings = yearlyFee - yearlyWithDiscount; // 29.89 saved

// Tax calculations
decimal CalculateSalesTax(decimal amount, decimal taxRate)
{
 return amount * taxRate;
}

decimal CalculatePriceWithTax(decimal price, decimal taxRate)
{
 return price + CalculateSalesTax(price, taxRate);
}

decimal itemPrice = 29.99m;
decimal taxRate = 0.08m; // 8%
decimal tax = CalculateSalesTax(itemPrice, taxRate); // 2.3992
decimal total = CalculatePriceWithTax(itemPrice, taxRate); // 32.3892

// Multiple items with exact totals
class CartItem
{
 public decimal Price;
 public int Quantity;
 
 public decimal Total => Price * Quantity;
}

List<CartItem> cart = new List<CartItem>
{
 new CartItem { Price = 19.99m, Quantity = 2 },
 new CartItem { Price = 5.50m, Quantity = 3 },
 new CartItem { Price = 12.75m, Quantity = 1 }
};

decimal cartTotal = cart.Sum(item => item.Total); // Exact sum

Common pitfalls

// PITFALL 1: Forgetting 'm' suffix
decimal price = 19.99; // Error!
decimal price = 19.99m; // Correct

// PITFALL 2: Using in game loops (VERY SLOW!)
void Update() // BAD! Called 60 times per second!
{
 decimal time = (decimal)Time.deltaTime; // WRONG! deltaTime is float
 decimal calculation = time * 5.5m; // 10-100x slower than float!
}

// CORRECT: Use decimal ONLY for money/UI, not real-time calculations
void BuyItem() // Called once when player clicks "Buy"
{
 decimal cost = 19.99m; // OK - not in real-time loop
 playerMoney -= cost;
}

// PITFALL 3: Mixing with float/double requires casting
float health = 100.0f;
decimal price = 19.99m;
decimal wrong = health * price; // Error! Can't mix float and decimal

// Fix: Cast explicitly (but why are you mixing?)
decimal correct = (decimal)health * price; // Works, but reconsider design

// PITFALL 4: Math class doesn't support decimal!
decimal value = 25.0m;
decimal result = Math.Sqrt(value); // Error! Math.Sqrt takes double

// Fix: Cast to double, calculate, cast back (loses precision!)
decimal result = (decimal)Math.Sqrt((double)value);

// PITFALL 5: Much slower than float/double (~10-100x!)
// Benchmark comparison:
float floatCalc = 0.0f;
for (int i = 0; i < 1000000; i++)
{
 floatCalc += 0.1f; // Fast (~2ms)
}

decimal decimalCalc = 0.0m;
for (int i = 0; i < 1000000; i++)
{
 decimalCalc += 0.1m; // Slow (~200ms) - 100x slower!
}

// PITFALL 6: Division precision
decimal result = 1m / 3m; // 0.3333333333333333333333333333 (28 digits)
// Still limited, just more precise than float/double

// PITFALL 7: Performance in tight loops
// WRONG - never use decimal for gameplay!
void FixedUpdate()
{
 decimal position = (decimal)transform.position.x; // TERRIBLE!
 position += 0.1m;
 transform.position = new Vector3((float)position, 0, 0);
}

// CORRECT - use float for gameplay, decimal for money
float position = transform.position.x;
position += 0.1f;
transform.position = new Vector3(position, 0, 0);

Pro tips

// TIP 1: ALWAYS use decimal for money - it's the law in financial software!
decimal productPrice = 29.99m;
decimal tax = productPrice * 0.08m; // Exact: 2.3992 (no rounding errors)
decimal total = productPrice + tax; // Exact: 32.3892

// TIP 2: Rounding money correctly
decimal amount = 19.996m;
decimal rounded = Math.Round(amount, 2); // 20.00 (2 decimal places)
decimal roundedDown = Math.Floor(amount * 100m) / 100m; // 19.99 (floor)
decimal roundedUp = Math.Ceiling(amount * 100m) / 100m; // 20.00 (ceiling)

// TIP 3: Banker's rounding (MidpointRounding)
decimal value = 2.5m;
decimal banker = Math.Round(value, MidpointRounding.ToEven); // 2.0 (even)
decimal away = Math.Round(value, MidpointRounding.AwayFromZero); // 3.0

// Always specify mode for financial calculations
decimal RoundMoney(decimal amount)
{
 return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
}

// TIP 4: Currency formatting
decimal price = 1234.56m;
string formatted = price.ToString("C"); // "$1,234.56" (culture-specific)
string custom = price.ToString("0.00"); // "1234.56"
string withSymbol = price.ToString("$#,##0.00"); // "$1,234.56"

// TIP 5: Comparison (no epsilon needed - exact!)
decimal a = 0.1m + 0.2m;
decimal b = 0.3m;
if (a == b) // TRUE! (exact comparison works with decimal)
{
 // Decimal has no precision errors like float/double!
}

// TIP 6: Don't use decimal for game calculations
// WRONG:
decimal playerPosition = 10.5m; // Use float!
decimal velocity = 5.5m; // Use float!
decimal deltaTime = 0.016m; // Use float!

// CORRECT:
decimal itemPrice = 19.99m; // Money
decimal accountBalance = 500.0m; // Money
decimal taxAmount = 1.60m; // Money

// TIP 7: Store prices as integers (cents) for database (alternative)
// Alternative approach: Store as int (cents), convert to decimal for display
int priceInCents = 1999; // $19.99 stored as 1999 cents
decimal displayPrice = priceInCents / 100.0m; // Convert to 19.99m

int CentsToInt(decimal price)
{
 return (int)(price * 100m);
}

decimal IntToCents(int cents)
{
 return cents / 100.0m;
}

// TIP 8: Separate financial logic from gameplay logic
class GameEconomy // Money calculations - use decimal
{
 public decimal PlayerGold { get; set; }
 public decimal ItemPrice { get; set; }
 
 public bool CanAfford(decimal cost)
 {
 return PlayerGold >= cost;
 }
 
 public void Purchase(decimal cost)
 {
 if (CanAfford(cost))
 {
 PlayerGold -= cost;
 }
 }
}

class GameCharacter // Gameplay - use float
{
 public float Health { get; set; }
 public float Speed { get; set; }
}

// TIP 9: Percentage calculations (exact)
decimal CalculatePercentage(decimal value, decimal total)
{
 if (total == 0m) return 0m;
 return (value / total) * 100m;
}

decimal percentage = CalculatePercentage(75m, 100m); // 75.0

// TIP 10: Discount calculations
decimal ApplyDiscount(decimal price, decimal discountPercent)
{
 decimal discount = price * discountPercent;
 return price - discount;
}

decimal originalPrice = 99.99m;
decimal discountedPrice = ApplyDiscount(originalPrice, 0.20m); // 79.99

// TIP 11: Split bill calculations (exact)
decimal SplitBill(decimal total, int people)
{
 return Math.Round(total / people, 2, MidpointRounding.AwayFromZero);
}

decimal billTotal = 127.50m;
decimal perPerson = SplitBill(billTotal, 3); // 42.50 each

// TIP 12: Tax calculation helpers
class TaxCalculator
{
 public decimal CalculateTax(decimal amount, decimal rate)
 {
 return Math.Round(amount * rate, 2, MidpointRounding.AwayFromZero);
 }
 
 public decimal CalculateTotal(decimal amount, decimal taxRate)
 {
 decimal tax = CalculateTax(amount, taxRate);
 return amount + tax;
 }
 
 public decimal CalculatePreTaxAmount(decimal total, decimal taxRate)
 {
 return total / (1m + taxRate);
 }
}

// TIP 13: Constants for financial calculations
const decimal VAT_RATE = 0.20m; // 20% VAT
const decimal SALES_TAX = 0.08m; // 8% sales tax
const decimal DISCOUNT_RATE = 0.15m; // 15% discount

// TIP 14: Min/Max for decimal
decimal min = Math.Min(price1, price2);
decimal max = Math.Max(price1, price2);

// TIP 15: Clamp for decimal
decimal Clamp(decimal value, decimal min, decimal max)
{
 if (value < min) return min;
 if (value > max) return max;
 return value;
}

decimal clampedPrice = Clamp(userInput, 0.01m, 9999.99m);

char — single character

Beginner explanation: Stores a single character like 'A', '5', '@', or '中'. Uses single quotes, not double quotes! Internally it's just a number (0-65535) representing a Unicode character. Perfect for keyboard input, single letters, or text parsing.

Technical specs

  • Size: 2 bytes (16 bits)
  • Range: '\u0000' to '\uffff' (0 to 65,535 - Unicode characters)
  • Default Value: '\0' (null character)

Performance notes

  • Fast (just a 16-bit integer internally)
  • 2 bytes (same as ushort)
  • Good for menu systems, keyboard input
  • String is usually more practical for text
  • Use char for single-character operations only

When to use char

USE
  • Single character storage
  • Keyboard input (single key)
  • Menu options (A, B, C, D)
  • Character-by-character text parsing
  • Grades, ratings (A, B, C)
  • Direction indicators (N, S, E, W)
AVOID
  • Don't use for multi-character text (use string)
  • Emojis need string (surrogate pairs)

Syntax and declaration

// Basic declaration (use SINGLE quotes!)
char letter = 'A'; // Correct
char number = '5'; // Correct (character '5', not number 5!)
char symbol = '@'; // Correct
char wrong = "A"; // Error! Double quotes are for strings

// Special characters (escape sequences)
char newline = '\n'; // Newline
char tab = '\t'; // Tab
char backslash = '\\'; // Backslash
char quote = '\''; // Single quote
char nullChar = '\0'; // Null character (default)

// Unicode characters
char smiley = '\u263A'; // ☺ (Unicode code)
char chinese = '中'; // Chinese character
char euro = '€'; // Euro symbol

// Getting char from string
string name = "Player";
char firstLetter = name[0]; // 'P'
char lastLetter = name[name.Length - 1]; // 'r'

Practical use cases

// Keyboard input
char keyPressed = 'W'; // WASD movement
char menuOption = 'A'; // Menu selection (A, B, C)
char direction = 'N'; // N, S, E, W

void ProcessInput(char key)
{
 switch (key)
 {
 case 'W': MoveForward(); break;
 case 'A': MoveLeft(); break;
 case 'S': MoveBackward(); break;
 case 'D': MoveRight(); break;
 default: break;
 }
}

// Unity keyboard input
void Update()
{
 if (Input.anyKeyDown)
 {
 foreach (char c in Input.inputString)
 {
 ProcessCharacter(c);
 }
 }
}

// Game mechanics
char grade = 'A'; // Student grade (A, B, C, D, F)
char rank = 'S'; // S, A, B, C rank system
char difficulty = 'H'; // E (Easy), N (Normal), H (Hard)

// Menu system
char[] menuOptions = { 'A', 'B', 'C', 'D' };
char selectedOption = 'A';

// Text-based commands
char command = GetUserCommand();
if (command == 'Q') QuitGame();
if (command == 'I') OpenInventory();
if (command == 'M') OpenMap();

// Character analysis
char letter = 'A';
bool isUpper = char.IsUpper(letter); // true
bool isLower = char.IsLower(letter); // false
bool isLetter = char.IsLetter(letter); // true
bool isDigit = char.IsDigit(letter); // false

// Parsing text
string text = "HP:100";
for (int i = 0; i < text.Length; i++)
{
 char c = text[i];
 if (char.IsDigit(c))
 {
 int digit = c - '0'; // Convert char digit to int
 }
}

// Direction system
enum Direction { North, South, East, West }

Direction GetDirectionFromChar(char c)
{
 return c switch
 {
 'N' or 'n' => Direction.North,
 'S' or 's' => Direction.South,
 'E' or 'e' => Direction.East,
 'W' or 'w' => Direction.West,
 _ => Direction.North
 };
}

Common pitfalls

// PITFALL 1: Single vs double quotes
char letter = 'A'; // char - single quotes
string word = "A"; // string - double quotes
char wrong = "A"; // Error!

// PITFALL 2: '5' is NOT the number 5!
char charFive = '5'; // Character '5' (Unicode 53)
int numFive = 5; // Number 5

if (charFive == 5) // FALSE! (53 != 5)
if (charFive == '5') // TRUE!

// Convert char digit to int:
int digit = charFive - '0'; // '5' - '0' = 5 (the number!)

// PITFALL 3: char is actually a number (ushort)
char a = 'A'; // Internally: 65
char b = (char)(a + 1); // 'B' (66)
char c = (char)(a + 32); // 'a' (97) - lowercase!

int asciiValue = (int)a; // 65

// PITFALL 4: Emojis need TWO chars (surrogate pairs)
char emoji = '😀'; // Doesn't work correctly!
string emojiStr = "😀"; // Use string for emojis

// Most emojis are outside the 16-bit range (> U+FFFF)

// PITFALL 5: Can't store multiple characters
char multiple = 'AB'; // Error! Only single character allowed
string multiple = "AB"; // Use string

// PITFALL 6: Case sensitivity
char upper = 'A';
char lower = 'a';
if (upper == lower) // FALSE! ('A' != 'a')

// Case-insensitive comparison
if (char.ToLower(upper) == char.ToLower(lower)) // TRUE!

// PITFALL 7: Null character vs null
char nullChar = '\0'; // Null character (valid char, value = 0)
string nullStr = null; // Null reference (no object)
// These are DIFFERENT!

Pro tips

// TIP 1: Check if char is letter/digit/etc
char input = 'A';
bool isLetter = char.IsLetter(input); // true
bool isDigit = char.IsDigit(input); // false
bool isUpper = char.IsUpper(input); // true
bool isLower = char.IsLower(input); // false
bool isWhiteSpace = char.IsWhiteSpace(' '); // true
bool isLetterOrDigit = char.IsLetterOrDigit(input); // true
bool isPunctuation = char.IsPunctuation('!'); // true

// TIP 2: Convert case
char lower = char.ToLower('A'); // 'a'
char upper = char.ToUpper('a'); // 'A'

// TIP 3: Keyboard input in Unity
void Update()
{
 if (Input.anyKeyDown)
 {
 foreach (char c in Input.inputString)
 {
 if (char.IsLetter(c))
 {
 ProcessLetter(c);
 }
 }
 }
}

// TIP 4: Parse single character commands
char command = GetUserInput();
switch (command)
{
 case 'W': case 'w': MoveForward(); break;
 case 'A': case 'a': MoveLeft(); break;
 case 'S': case 's': MoveBack(); break;
 case 'D': case 'd': MoveRight(); break;
 case 'Q': case 'q': Quit(); break;
 default: InvalidCommand(); break;
}

// TIP 5: Convert between char and int
char letter = 'A';
int ascii = (int)letter; // 65
char back = (char)ascii; // 'A'

// Unicode value
char unicode = '\u0041'; // 'A'
int unicodeValue = (int)unicode; // 65

// TIP 6: Iterate through alphabet
for (char c = 'A'; c <= 'Z'; c++)
{
 Console.WriteLine(c); // A, B, C, ..., Z
}

for (char c = 'a'; c <= 'z'; c++)
{
 Console.WriteLine(c); // a, b, c, ..., z
}

// TIP 7: Check character range
bool IsVowel(char c)
{
 c = char.ToLower(c);
 return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

bool IsConsonant(char c)
{
 return char.IsLetter(c) && !IsVowel(c);
}

// TIP 8: Convert char digit to int
char digit = '7';
if (char.IsDigit(digit))
{
 int number = digit - '0'; // 7
}

// TIP 9: Check if char is in range
bool IsUppercaseLetter(char c)
{
 return c >= 'A' && c <= 'Z';
}

bool IsLowercaseLetter(char c)
{
 return c >= 'a' && c <= 'z';
}

bool IsDigitChar(char c)
{
 return c >= '0' && c <= '9';
}

// TIP 10: Character comparison (case-insensitive)
bool EqualsIgnoreCase(char a, char b)
{
 return char.ToLower(a) == char.ToLower(b);
}

// TIP 11: Get character from string safely
char GetCharAt(string text, int index)
{
 if (index >= 0 && index < text.Length)
 return text[index];
 return '\0'; // Return null char if out of bounds
}

// TIP 12: Build string from chars
char[] chars = { 'H', 'e', 'l', 'l', 'o' };
string word = new string(chars); // "Hello"

// TIP 13: Char array for text processing
char[] buffer = new char[100];
int length = 0;

void AddChar(char c)
{
 if (length < buffer.Length)
 {
 buffer[length++] = c;
 }
}

string GetText()
{
 return new string(buffer, 0, length);
}

// TIP 14: Character validation
bool IsValidUsernameChar(char c)
{
 return char.IsLetterOrDigit(c) || c == '_' || c == '-';
}

// TIP 15: Escape sequence reference
char newline = '\n'; // Line feed
char carriageReturn = '\r'; // Carriage return
char tab = '\t'; // Horizontal tab
char backspace = '\b'; // Backspace
char formFeed = '\f'; // Form feed
char backslash = '\\'; // Backslash
char singleQuote = '\''; // Single quote
char doubleQuote = '\"'; // Double quote (in char literal)
char zeroChar = '\0'; // Null character

string — text sequences

Beginner explanation: Stores text of any length - from empty "" to entire novels! Uses double quotes. Strings are IMMUTABLE (can't be changed after creation) - modifying a string actually creates a new one in memory. Use for names, messages, UI text, file paths - anything textual.

Technical specs

  • Size: Dynamic (depends on text length)
  • Type: Reference type (stored on heap, not stack)
  • Immutable: Once created, cannot be modified
  • Default Value: null

Performance notes

  • Immutable - every modification creates new string object
  • Concatenation in loops is VERY slow (use StringBuilder)
  • String interpolation ($"") is optimized by compiler
  • String comparison is fast (interned strings)
  • Use StringBuilder for 10+ concatenations
  • Memory: Stores on heap (garbage collected)

When to use string

USE
  • Player names, item names, descriptions
  • UI text, dialogue, messages
  • File paths, URLs
  • JSON, XML, serialized data
  • User input, commands
AVOID
  • Don't concatenate in loops (use StringBuilder)
  • Don't use for single characters (use char)

Syntax and declaration

// Basic declaration
string playerName = "Hero";
string message = "Hello, World!";
string empty = ""; // Empty string (not null!)
string nullString = null; // No string assigned

// Multi-line strings (verbatim string literal)
string multiLine = @"Line 1
Line 2
Line 3";

// Escape sequences
string withQuotes = "He said \"Hello\""; // He said "Hello"
string withNewline = "Line1\nLine2"; // Line break
string path = "C:\\Users\\Player\\"; // Backslashes
string verbatimPath = @"C:\Users\Player\"; // Verbatim (no escaping needed)

// String interpolation (MODERN WAY - use this!)
int score = 100;
string text = $"Your score is {score}"; // "Your score is 100"
string complex = $"Player: {playerName}, HP: {health}/100";

// Old way (concatenation - avoid!)
string old = "Score: " + score; // Slower, less readable

Practical use cases

// Game examples
string playerName = "Steve";
string dialogueText = "Welcome to the adventure!";
string itemName = "Legendary Sword";
string itemDescription = "A powerful weapon forged in ancient times.";

// UI text
string healthText = $"HP: {currentHealth}/{maxHealth}";
string scoreText = $"Score: {score:N0}"; // "Score: 1,000,000" (formatted)
string timerText = $"Time: {timeRemaining:F2}s"; // "Time: 45.32s"

// File paths (use @ verbatim strings!)
string savePath = @"C:\Users\Player\Saves\save001.dat";
string assetPath = "Assets/Prefabs/Player.prefab"; // Unity style
string configPath = Path.Combine(Application.dataPath, "config.json");

// JSON/Data
string jsonData = "{\"name\":\"Player\",\"level\":10}";
string csvLine = "Name,Health,Score\nPlayer,100,1500";

// Commands/Input
string userInput = Console.ReadLine();
string command = GetConsoleInput().ToLower().Trim();

if (command == "attack")
{
 PerformAttack();
}

// Dialogue system
Dictionary<string, string> dialogues = new Dictionary<string, string>
{
 { "greeting", "Hello, traveler!" },
 { "quest", "I need your help with something..." },
 { "farewell", "Safe travels!" }
};

string dialogue = dialogues["greeting"];

// Save data
string SaveToJson()
{
 return JsonUtility.ToJson(playerData);
}

// Localization
Dictionary<string, string> translations = new Dictionary<string, string>
{
 { "en_greeting", "Hello" },
 { "ru_greeting", "Здравствуйте" },
 { "jp_greeting", "こんにちは" }
};

string GetLocalizedText(string key, string language)
{
 return translations[$"{language}_{key}"];
}

Common pitfalls

// PITFALL 1: Strings are IMMUTABLE!
string name = "Bob";
name.ToUpper(); // This does NOTHING! Doesn't modify 'name'
// ToUpper() returns a NEW string, doesn't change original

// Correct:
string upperName = name.ToUpper(); // "BOB" (new string)
name = name.ToUpper(); // Reassign to same variable

// PITFALL 2: String concatenation in loops is SLOW!
string result = "";
for (int i = 0; i < 1000; i++)
{
 result += i.ToString(); // Creates 1000 string objects! Very slow!
}

// Use StringBuilder for loops:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
 sb.Append(i); // Much faster!
}
string result = sb.ToString();

// PITFALL 3: null vs empty string
string nullStr = null;
string emptyStr = "";

if (nullStr == "") // NullReferenceException!
if (emptyStr == null) // false

// Safe check:
if (string.IsNullOrEmpty(nullStr)) // true
if (string.IsNullOrEmpty(emptyStr)) // true
if (string.IsNullOrWhiteSpace(" ")) // true (also checks whitespace)

// PITFALL 4: Comparing strings (case-sensitive!)
string a = "Hello";
string b = "hello";

if (a == b) // false (case-sensitive!)
if (a.Equals(b, StringComparison.OrdinalIgnoreCase)) // true (ignore case)

// PITFALL 5: Forgetting to use @ for paths
string path1 = "C:\Users\New\test.txt"; // \U and \N are escape sequences!
string path2 = @"C:\Users\New\test.txt"; // Verbatim - no escaping

// PITFALL 6: String methods don't modify original
string text = " hello ";
text.Trim(); // Does nothing to 'text'!
text = text.Trim(); // Correct - reassign result

// PITFALL 7: Using + instead of interpolation
string name = "Player";
int level = 10;
string bad = "Name: " + name + ", Level: " + level; // Slow, ugly
string good = $"Name: {name}, Level: {level}"; // Fast, readable

Pro tips

// TIP 1: String interpolation is your friend!
int health = 75;
int maxHealth = 100;
string oldWay = "HP: " + health + "/" + maxHealth; // Ugly, slow
string newWay = $"HP: {health}/{maxHealth}"; // Clean, fast

// TIP 2: Formatting numbers
decimal price = 1234.56m;
string formatted = $"Price: ${price:F2}"; // "Price: $1234.56"
string withCommas = $"{score:N0}"; // "1,234,567"
string percent = $"{ratio:P0}"; // "75%" (if ratio = 0.75)

// Format specifiers:
// F2 - fixed-point, 2 decimals
// N0 - number with thousands separator, 0 decimals
// P0 - percentage, 0 decimals
// C - currency (culture-specific)

// TIP 3: Multi-line interpolation
string report = $@"
Player Stats:
-----------------
Name: {playerName}
Health: {health}/{maxHealth}
Score: {score:N0}
Level: {level}
";

// TIP 4: Common string operations
string text = "Hello World";
bool contains = text.Contains("World"); // true
bool starts = text.StartsWith("Hello"); // true
bool ends = text.EndsWith("World"); // true
string upper = text.ToUpper(); // "HELLO WORLD"
string lower = text.ToLower(); // "hello world"
string replaced = text.Replace("World", "Unity"); // "Hello Unity"
string[] words = text.Split(' '); // ["Hello", "World"]
string trimmed = " text ".Trim(); // "text"
string trimStart = " text ".TrimStart(); // "text "
string trimEnd = " text ".TrimEnd(); // " text"

// TIP 5: Substring operations
string full = "Player_001";
string prefix = full.Substring(0, 6); // "Player"
string number = full.Substring(7); // "001"
int index = full.IndexOf('_'); // 6
int lastIndex = full.LastIndexOf('_'); // 6

// TIP 6: Check for null or empty (Unity-safe)
if (!string.IsNullOrEmpty(playerName))
{
 DisplayName(playerName);
}

if (!string.IsNullOrWhiteSpace(userInput))
{
 ProcessInput(userInput);
}

// TIP 7: StringBuilder for lots of concatenation
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
sb.AppendLine("!"); // Adds newline
sb.Insert(0, ">> "); // Insert at position
sb.Remove(0, 3); // Remove 3 chars from position 0
string result = sb.ToString();

// TIP 8: String joining
string[] items = { "Sword", "Shield", "Potion" };
string inventory = string.Join(", ", items); // "Sword, Shield, Potion"

// With newlines
string list = string.Join("\n", items);

// TIP 9: Parsing from strings
string numberStr = "123";
int number = int.Parse(numberStr); // 123

// Safer with TryParse:
if (int.TryParse(numberStr, out int result))
{
 // result = 123, parsing succeeded
}
else
{
 // Parsing failed, result = 0
}

// Other types:
float.TryParse("3.14", out float floatResult);
bool.TryParse("true", out bool boolResult);

// TIP 10: Path operations (use Path class!)
string combined = Path.Combine("Assets", "Prefabs", "Player.prefab");
string directory = Path.GetDirectoryName(filePath);
string filename = Path.GetFileName(filePath);
string extension = Path.GetExtension(filePath); // ".prefab"
string withoutExt = Path.GetFileNameWithoutExtension(filePath);

// TIP 11: String comparison options
string a = "Hello";
string b = "HELLO";

bool equal = a.Equals(b, StringComparison.OrdinalIgnoreCase); // true
bool contains = a.Contains("ello", StringComparison.OrdinalIgnoreCase); // true (.NET 5+)

// Comparison types:
// Ordinal - byte-by-byte comparison (fastest)
// OrdinalIgnoreCase - case-insensitive byte comparison
// CurrentCulture - culture-specific comparison
// InvariantCulture - culture-invariant comparison

// TIP 12: String padding
string text = "123";
string padded = text.PadLeft(5, '0'); // "00123"
string paddedRight = text.PadRight(5, '0'); // "12300"

// TIP 13: Removing characters
string text = "Hello, World!";
string noCommas = text.Replace(",", ""); // "Hello World!"
string noSpaces = text.Replace(" ", ""); // "Hello,World!"

// Remove specific chars
char[] charsToRemove = { ',', '!' };
string cleaned = new string(text.Where(c => !charsToRemove.Contains(c)).ToArray());

// TIP 14: Checking if string contains only certain chars
bool IsAlphanumeric(string text)
{
 return text.All(char.IsLetterOrDigit);
}

bool IsNumeric(string text)
{
 return text.All(char.IsDigit);
}

// TIP 15: String constants (better than magic strings)
public static class GameText
{
 public const string GAME_OVER = "Game Over";
 public const string PAUSED = "Paused";
 public const string LOADING = "Loading...";
}

// Use:
uiText.text = GameText.GAME_OVER;

// TIP 16: Format templates
string FormatHealth(int current, int max)
{
 return $"HP: {current}/{max} ({(float)current / max:P0})";
}
// "HP: 75/100 (75%)"

// TIP 17: Empty string constant
string empty1 = "";
string empty2 = string.Empty; // Same thing, more explicit

// TIP 18: String builder capacity (optimization)
StringBuilder sb = new StringBuilder(1000); // Pre-allocate for 1000 chars
// Avoids multiple memory reallocations

bool — true/false values

Beginner explanation: Stores only two values: true or false. Essential for game logic, conditions, and states. Think of it as an on/off switch, yes/no answer, or enabled/disabled flag. Uses 1 byte of memory despite being just 1 bit of information.

Technical specs

  • Size: 1 byte (8 bits) - but logically 1 bit
  • Values: true or false only
  • Default Value: false

Performance notes

  • Very fast (1 byte, simple operations)
  • Comparison operators are optimized
  • Short-circuit evaluation saves CPU
  • Despite being 1 bit logically, uses 1 byte (memory alignment)
  • For thousands of flags, consider bit packing

When to use bool

USE
  • Any yes/no, true/false, on/off state
  • Conditions and flags
  • Ability checks (canJump, canAttack)
  • State tracking (isAlive, isPaused)
  • UI visibility (showMenu, isEnabled)
AVOID
  • Don't use int (0/1) instead of bool
  • Don't use for multi-state (use enum instead)

Syntax and declaration

// Basic declaration
bool isAlive = true;
bool hasKey = false;
bool gameOver = false;

// From comparisons
bool isGreater = 10 > 5; // true
bool isEqual = health == 100; // true/false
bool isNotEqual = score != 0; // true/false

// Logical operators
bool canAttack = isAlive && hasWeapon; // AND
bool canMove = !isStunned; // NOT (negation)
bool shouldHeal = health < 50 || hasBuff; // OR

// Default value
bool flag; // Defaults to false (not null!)

Practical use cases

// Game state flags
bool isPlayerAlive = true;
bool isPaused = false;
bool isGameOver = false;
bool isLevelComplete = false;
bool isLoading = false;

// Player abilities
bool canJump = true;
bool canDoubleJump = false;
bool canDash = false;
bool isGrounded = false;
bool isCrouching = false;
bool isSprinting = false;

// Inventory/Items
bool hasKey = false;
bool hasWeapon = true;
bool hasArmor = false;
bool inventoryFull = false;
bool canPickup = true;

// UI state
bool isMenuOpen = false;
bool isInventoryVisible = false;
bool showHealthBar = true;
bool showMinimap = true;
bool muteSound = false;

// Enemy AI
bool playerDetected = false;
bool isChasing = false;
bool isAttacking = false;
bool canSeePlayer = false;
bool isPatrolling = true;

// Conditions
if (isPlayerAlive && !isGameOver)
{
 UpdateGameplay();
}

if (hasKey && isNearDoor)
{
 OpenDoor();
}

// Toggle
bool soundEnabled = true;
soundEnabled = !soundEnabled; // Toggle (true → false, false → true)

// Movement system
class PlayerController
{
 bool isGrounded;
 bool isJumping;
 bool isFalling;
 
 void Update()
 {
 isGrounded = CheckGrounded();
 
 if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
 {
 Jump();
 isJumping = true;
 }
 }
}

// Ability cooldown
bool canUseAbility = true;
float abilityCooldown = 5.0f;

void UseAbility()
{
 if (canUseAbility)
 {
 PerformAbility();
 canUseAbility = false;
 StartCoroutine(AbilityCooldown());
 }
}

IEnumerator AbilityCooldown()
{
 yield return new WaitForSeconds(abilityCooldown);
 canUseAbility = true;
}

Common pitfalls

// PITFALL 1: Redundant comparisons
bool isAlive = true;
if (isAlive == true) // Redundant! 'isAlive' is already bool
{
 // ...
}

// Correct:
if (isAlive) // Clean, readable
{
 // ...
}

if (!isAlive) // Check for false
{
 // ...
}

// PITFALL 2: Using 0/1 instead of bool
int flag = 1; // Don't use int for true/false!
bool flag = true; // Use bool

// PITFALL 3: Confusing = with ==
bool isAlive = true;
if (isAlive = false) // ASSIGNMENT, not comparison!
{
 // This SETS isAlive to false and executes the block!
}

if (isAlive == false) // Comparison
{
 // Checks if isAlive is false
}

// Better:
if (!isAlive) // Most readable
{
 // ...
}

// PITFALL 4: Complex boolean expressions without parentheses
if (a && b || c && d) // Unclear! What's the order?
if ((a && b) || (c && d)) // Clear with parentheses

// PITFALL 5: Not initializing flags
bool initialized; // Defaults to false, but be explicit!
bool initialized = false; // Better - clear intent

// PITFALL 6: Using bool for multi-state
// Bad: Multiple bools for states
bool isIdle = true;
bool isWalking = false;
bool isRunning = false;
// What if multiple are true?

// Better: Use enum for states
enum PlayerState { Idle, Walking, Running }
PlayerState state = PlayerState.Idle;

// PITFALL 7: Negative boolean names
bool notReady = true; // Confusing!
if (!notReady) // Double negative - hard to read

// Better:
bool isReady = false;
if (isReady) // Clear and readable

Pro tips

// TIP 1: Use meaningful names (is/has/can/should prefixes)
bool isAlive; // State
bool hasWeapon; // Possession
bool canJump; // Ability
bool shouldUpdate; // Condition
bool willRespawn; // Future state

// Bad names:
bool flag1, flag2, alive, weapon;

// TIP 2: Logical operators
bool a = true, b = false;
bool and = a && b; // AND: both must be true → false
bool or = a || b; // OR: at least one must be true → true
bool not = !a; // NOT: inverts the value → false
bool xor = a ^ b; // XOR: exactly one must be true → true

// TIP 3: Short-circuit evaluation
if (player != null && player.health > 0)
{
 // If player is null, player.health is never evaluated!
 // Prevents NullReferenceException
}

if (isGameOver || CheckWinCondition())
{
 // If isGameOver is true, CheckWinCondition() is not called!
 // Saves CPU if function is expensive
}

// TIP 4: Ternary operator (conditional expression)
bool isAlive = health > 0;
string status = isAlive ? "Alive" : "Dead";

int damage = isCritical ? baseDamage * 2 : baseDamage;

// Multi-line for readability
string message = hasKey 
 ? "You unlocked the door!" 
 : "You need a key.";

// TIP 5: Toggle pattern
bool soundEnabled = true;
soundEnabled = !soundEnabled; // Flip value

// Or with XOR (advanced):
soundEnabled ^= true; // Toggle

// TIP 6: Combining conditions
bool canAttack = isAlive && !isStunned && hasWeapon && !isReloading;

bool shouldRespawn = isDead && (respawnTime <= 0 || playerPressedButton);

// TIP 7: Bool methods for clarity
bool IsEnemyInRange()
{
 float distance = Vector3.Distance(player.position, enemy.position);
 return distance <= attackRange;
}

bool CanAfford(int cost)
{
 return playerGold >= cost;
}

// Usage:
if (IsEnemyInRange())
{
 Attack();
}

// TIP 8: Bool arrays for grid-based games
bool[,] tileOccupied = new bool[10, 10];
tileOccupied[5, 3] = true; // Mark tile as occupied

bool IsTileBlocked(int x, int y)
{
 return tileOccupied[x, y];
}

// TIP 9: Validation patterns
bool IsValidInput(string input)
{
 if (string.IsNullOrEmpty(input)) return false;
 if (input.Length > 100) return false;
 if (!char.IsLetter(input[0])) return false;
 return true;
}

// TIP 10: State machine with bools
bool isIdle = true;
bool isWalking = false;
bool isRunning = false;

void SetState(string newState)
{
 // Reset all
 isIdle = isWalking = isRunning = false;
 
 // Set new state
 switch (newState)
 {
 case "Idle": isIdle = true; break;
 case "Walk": isWalking = true; break;
 case "Run": isRunning = true; break;
 }
}

// TIP 11: Nullable bool for tri-state
bool? nullableBool = null; // Can be true, false, or null
bool value = nullableBool ?? false; // Use false if null

// Usage:
bool? userChoice = GetUserChoice(); // null if not chosen yet
if (userChoice == true) { }
if (userChoice == false) { }
if (userChoice == null) { } // Not decided yet

// TIP 12: Bit flags (advanced - store multiple bools in one int)
[Flags]
enum PlayerFlags
{
 None = 0,
 IsAlive = 1 << 0, // Bit 0
 HasWeapon = 1 << 1, // Bit 1
 CanJump = 1 << 2, // Bit 2
 IsInvincible = 1 << 3 // Bit 3
}

PlayerFlags flags = PlayerFlags.IsAlive | PlayerFlags.CanJump;
bool isAlive = (flags & PlayerFlags.IsAlive) != 0;

// TIP 13: Guard clauses (early returns)
void Attack()
{
 if (!isAlive) return;
 if (isStunned) return;
 if (!hasWeapon) return;
 
 // Perform attack
 PerformAttackLogic();
}

// TIP 14: All/Any patterns with collections
bool allEnemiesDead = enemies.All(e => e.health <= 0);
bool anyEnemyAlive = enemies.Any(e => e.health > 0);

// TIP 15: Bool expression caching
// Recalculate every frame
void Update()
{
 if (player.health > 0 && !player.isStunned && player.hasWeapon)
 {
 // ...
 }
}

// Cache if expensive
bool canAttack;
void Update()
{
 canAttack = player.health > 0 && !player.isStunned && player.hasWeapon;
 
 if (canAttack)
 {
 // ...
 }
}

// TIP 16: Boolean operators truth table
// AND (&&): true only if BOTH are true
// true && true → true
// true && false → false
// false && true → false
// false && false → false

// OR (||): true if AT LEAST ONE is true
// true || true → true
// true || false → true
// false || true → true
// false || false → false

// NOT (!): inverts the value
// !true → false
// !false → true

// XOR (^): true if EXACTLY ONE is true
// true ^ true → false
// true ^ false → true
// false ^ true → true
// false ^ false → false

02 References

// END OF ARTICLE // CSHARP_VARIABLES_LESSON_1_PART_3 // EOF