ItTechGenie-Subject - Indexers (Warm-up + 10 Questions)

C# Question Bank

C# Indexers | Warm‑up (with hidden reference answer) + 10 Scenario Questions (5 Moderate + 5 Complex) — Questions Only

Instruction: Students should implement ONLY the methods/indexers marked with // ✅ TODO in Q2–Q11.
Warm‑up: Q1 includes a reference answer behind a button covering indexer syntax, overloading (string/int), bounds checks, and read-only wrappers.
All other questions contain NO answers and are fully visible by default (scenario + boilerplate + code).
Warm‑up: 1 Moderate: 5 Complex: 5 Coverage: string/int indexers, multi-parameter, read-only, nested stores, sparse matrix, LRU cache, thread-safe store, audited proxy, Range slicing, chained indexers
1

Warm‑up: Indexers Toolkit (string + int) — with hidden reference answer

Indexersthis[]OverloadReadOnlyWarm-up

Warm‑up (with answer): Build a small settings store that supports: - indexer with string key: store["theme"] - indexer with int key: store[0] (access keys by insertion order) - validation + friendly exceptions - get-only indexer pattern for "read-only view" ✅ Warm-up includes a reference answer (hidden behind a button) that covers indexer syntax and typical patterns.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary, List

namespace ItTechGenie.M1.Indexers.WarmUp
{
    public class SettingsStore                                       // settings container
    {
        private readonly Dictionary<string, string> _map =            // key/value map
            new(StringComparer.OrdinalIgnoreCase);                    // case-insensitive keys

        private readonly List<string> _keys = new();                  // insertion order keys

        // ✅ Warm-up TODO: Student must implement only this indexer (string key)
        public string this[string key]                               // indexer by key
        {
            get
            {
                // TODO:
                // - validate key (null/empty/whitespace)
                // - if missing, throw KeyNotFoundException with message
                // - return stored value
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - validate key
                // - add key to _keys if new
                // - store in _map
                throw new NotImplementedException();
            }
        }

        // ✅ Warm-up TODO: Student must implement only this indexer (int index)
        public string this[int index]                                // indexer by position
        {
            get
            {
                // TODO:
                // - validate index range
                // - map index -> key using _keys
                // - return this[key]
                throw new NotImplementedException();
            }
        }

        // (Optional pattern) a read-only view for safe sharing
        public IReadOnlySettings ReadOnly() => new ReadOnlySettings(this); // wrapper

        public sealed class ReadOnlySettings                          // nested wrapper
        {
            private readonly SettingsStore _inner;                    // wrapped store
            public ReadOnlySettings(SettingsStore inner) => _inner = inner; // ctor

            public string this[string key] => _inner[key];            // get-only indexer
        }
    }

    internal class Program
    {
        static void Main()
        {
            var store = new SettingsStore();                          // create store

            store["theme mode"] = "Dark ✅";                           // set via string indexer
            store["page-size"] = " 20 ";                               // set another

            Console.WriteLine(store["theme mode"]);                    // get by key
            Console.WriteLine(store[0]);                               // get by position

            var ro = store.ReadOnly();                                 // get read-only
            Console.WriteLine(ro["page-size"]);                         // read-only access
        }
    }
}

Warm‑up Reference Answer (Conceptual Steps)

Reference approach (key steps): 1) String indexer: - if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException(...) - if (! _map.TryGetValue(key, out var v)) throw new KeyNotFoundException(...) - return v - in set: if key new => _keys.Add(key); then _map[key]=value ?? "" (or allow null decision) 2) Int indexer: - if (index < 0 || index >= _keys.Count) throw new IndexOutOfRangeException(...) - var key = _keys[index]; return this[key]; 3) Read-only wrapper: - Provide get-only indexer that delegates to inner store to prevent writes.

2

Product Catalog by SKU — Indexer with Key Normalization

IndexersDictionaryNormalizationValidationModerate

Scenario: Your inventory system uses SKU strings that may include spaces/special chars. Expose an indexer that lets users access products by SKU safely. What to implement: - ProductCatalog.this[string sku] get/set Rules: - Normalize sku: trim + collapse multiple spaces + uppercase - If sku missing on get -> return null (do not throw) - If sku invalid (null/whitespace) -> throw ArgumentException - Store Product objects in dictionary ✅ Implement ONLY the TODO methods.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary

namespace ItTechGenie.M1.Indexers.Q2
{
    public record Product(string Sku, string Name, decimal Price);    // simple product model

    public class ProductCatalog
    {
        private readonly Dictionary<string, Product> _items = new();  // normalized key store

        // ✅ TODO: Student must implement only this method
        private static string NormalizeSku(string sku)
        {
            // TODO:
            // - trim
            // - collapse multiple spaces into one
            // - uppercase
            // - return normalized
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public Product? this[string sku]                              // access by sku
        {
            get
            {
                // TODO:
                // - validate sku
                // - normalize
                // - return product or null
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - validate sku
                // - normalize
                // - if value is null, remove item
                // - else store with normalized key
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var cat = new ProductCatalog();

            cat[" sk- 001 ✅ "] = new Product("SK-001", "Laptop Stand", 1299.50m); // add
            cat["sk-  002  !@#"] = new Product("SK-002", "Cable 2m ✅", 499m);     // add

            Console.WriteLine(cat["SK-001"]?.Name);                     // get normalized
            Console.WriteLine(cat["sk-  999"] == null);                 // missing => null
        }
    }
}
3

Address Book — Multi‑Parameter Indexer (city, pincode)

IndexersMulti-parameterTuple keyModerate

Scenario: A delivery service needs quick lookup by (City, Pincode). Expose an indexer with 2 parameters. What to implement: - AddressBook.this[string city, string pincode] get/set Rules: - Normalize city: trim + TitleCase optional (your choice) + collapse spaces - Normalize pincode: remove spaces; keep as string because of leading zeros - Key should be stable: (cityNormalized + "|" + pinNormalized) - On get: return Address? (null if missing) ✅ Implement ONLY the TODO methods.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary

namespace ItTechGenie.M1.Indexers.Q3
{
    public record Address(string City, string Pincode, string Line1); // address model

    public class AddressBook
    {
        private readonly Dictionary<string, Address> _db = new();     // key->address

        // ✅ TODO: Student must implement only this helper
        private static string MakeKey(string city, string pincode)
        {
            // TODO:
            // - validate both
            // - normalize city (trim + collapse spaces)
            // - normalize pin (remove spaces)
            // - return "CITY|PIN" (choose casing)
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public Address? this[string city, string pincode]              // indexer with 2 params
        {
            get
            {
                // TODO:
                // - key = MakeKey(...)
                // - return address or null
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - key = MakeKey(...)
                // - if value is null remove else set
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var book = new AddressBook();

            book["New   Delhi ✅", " 110 001 "] = new Address("New Delhi", "110001", "Connaught Place"); // add
            book["Chennai", "600 001"] = new Address("Chennai", "600001", "Mount Road");               // add

            Console.WriteLine(book["New Delhi ✅", "110001"]?.Line1);                                   // get
        }
    }
}
4

Student Registry — Read‑Only Indexer over Internal List

IndexersRead-onlySearchModerate

Scenario: A training portal stores students in a list, but you want a clean read-only API. Expose an indexer that searches by roll number and returns Student? without exposing the list. What to implement: - StudentRegistry.this[string rollNo] get-only Rules: - rollNo normalization: trim + uppercase - Search in list (linear is OK) - Return null if missing - Do not provide a set accessor ✅ Implement ONLY the TODO method.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // List

namespace ItTechGenie.M1.Indexers.Q4
{
    public record Student(string RollNo, string Name);                // student model

    public class StudentRegistry
    {
        private readonly List<Student> _students = new();             // internal list

        public void Add(Student s) => _students.Add(s);                // add helper

        // ✅ TODO: Student must implement only this indexer (get-only)
        public Student? this[string rollNo]                           // lookup by roll number
        {
            get
            {
                // TODO:
                // - validate rollNo
                // - normalize
                // - search _students
                // - return match or null
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var reg = new StudentRegistry();
            reg.Add(new Student("M1-001", "Sana ✅"));
            reg.Add(new Student("M1-002", "Arjun α/β"));

            Console.WriteLine(reg[" m1- 001 ✅ "]?.Name);              // should find
            Console.WriteLine(reg["M1-999 !@#"] == null);              // missing
        }
    }
}
5

Config Section Store — Indexer that Auto‑Creates Missing Sections

IndexersNested storeAuto-createModerate

Scenario: You store configuration as Section -> (Key -> Value). Expose nested indexer access: - store["db"]["conn string"] Students implement a SectionStore indexer that returns a Section object. The Section itself has an indexer for keys. What to implement: - ConfigStore.this[string section] get-only (auto-create) - ConfigSection.this[string key] get/set Rules: - Normalize section and key: trim + collapse spaces + case-insensitive - Missing section should be created automatically on get - Missing key on get should return empty string (not null) ✅ Implement ONLY the TODO parts.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary

namespace ItTechGenie.M1.Indexers.Q5
{
    public class ConfigSection
    {
        private readonly Dictionary<string, string> _kv =             // key/value store
            new(StringComparer.OrdinalIgnoreCase);

        // ✅ TODO: Student must implement only this indexer
        public string this[string key]
        {
            get
            {
                // TODO:
                // - validate key
                // - return value if exists else "" (empty)
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - validate key
                // - set value (store as-is or trimmed; your choice)
                throw new NotImplementedException();
            }
        }
    }

    public class ConfigStore
    {
        private readonly Dictionary<string, ConfigSection> _sections = // section store
            new(StringComparer.OrdinalIgnoreCase);

        // ✅ TODO: Student must implement only this helper
        private static string Normalize(string text)
        {
            // TODO:
            // - trim
            // - collapse spaces
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer (get-only)
        public ConfigSection this[string section]
        {
            get
            {
                // TODO:
                // - validate section
                // - normalize
                // - if section missing => create and store new ConfigSection
                // - return section object
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var store = new ConfigStore();

            store[" DB  Settings ✅ "][" conn string "] = "Server=.;Db=Test !@#"; // set
            store["db settings"]["timeout"] = " 30 ";                               // set

            Console.WriteLine(store["Db Settings"]["Conn String"]);                 // get
            Console.WriteLine(store["Db Settings"]["missing-key"] == "");           // missing => empty
        }
    }
}
6

Sparse Matrix — Indexer [row, col] with Lazy Storage

Indexers2DSparseValidationComplex

Scenario: A reporting engine needs a large 2D numeric grid, but most cells are 0. Implement a sparse matrix with indexer [row, col]. What to implement: - SparseMatrix.this[int row, int col] get/set Rules: - Validate row/col within [0..Rows-1], [0..Cols-1] - Internally store only non-zero cells in Dictionary<(int,int), decimal> - On get: return 0 if missing - On set: if value == 0 remove cell entry ✅ Implement ONLY the TODO indexer.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary

namespace ItTechGenie.M1.Indexers.Q6
{
    public class SparseMatrix
    {
        public int Rows { get; }                                      // row count
        public int Cols { get; }                                      // col count

        private readonly Dictionary<(int r, int c), decimal> _cells =  // sparse storage
            new();

        public SparseMatrix(int rows, int cols) { Rows = rows; Cols = cols; } // ctor

        // ✅ TODO: Student must implement only this indexer
        public decimal this[int row, int col]
        {
            get
            {
                // TODO:
                // - validate bounds
                // - return value if exists else 0m
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - validate bounds
                // - if value == 0 remove else set
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var m = new SparseMatrix(3, 4);

            m[0, 1] = 1999.25m;                                       // set
            m[2, 3] = 499m;                                           // set

            Console.WriteLine(m[1, 1]);                                // missing => 0
            m[0, 1] = 0m;                                              // remove
            Console.WriteLine(m[0, 1]);                                // now 0
        }
    }
}
7

LRU Cache — Indexer with Eviction Policy

IndexersCacheLRUData structuresComplex

Scenario: Your API gateway caches responses by key, but memory is limited. Expose an indexer cache[key] = value that evicts least recently used entries. What to implement: - LruCache.this[string key] get/set - Internal eviction when capacity exceeded Rules: - Capacity passed in constructor - Get updates recency - Set updates recency; if new and capacity exceeded => evict LRU - Missing get should return null (do not throw) ✅ Implement ONLY the TODO methods.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary, LinkedList

namespace ItTechGenie.M1.Indexers.Q7
{
    public class LruCache
    {
        private readonly int _capacity;                               // max items
        private readonly Dictionary<string, LinkedListNode<string>> _nodes =
            new(StringComparer.OrdinalIgnoreCase);                    // key->node

        private readonly Dictionary<string, string> _values =
            new(StringComparer.OrdinalIgnoreCase);                    // key->value

        private readonly LinkedList<string> _lru = new();             // least->most recent

        public LruCache(int capacity) => _capacity = capacity;        // ctor

        // ✅ TODO: Student must implement only this helper
        private static string Normalize(string key)
        {
            // TODO:
            // - validate key (not null/whitespace)
            // - trim (keep internal spaces)
            // - return normalized
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public string? this[string key]
        {
            get
            {
                // TODO:
                // - normalize
                // - if missing return null
                // - mark as most recent
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - normalize
                // - if value is null => remove key (optional design)
                // - else set value and mark most recent
                // - evict if capacity exceeded (remove least recent)
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var cache = new LruCache(2);

            cache["u-α12 ✅"] = "token=tok_123 !@#";
            cache["u-β77"] = "token=tok_999";

            Console.WriteLine(cache["u-α12 ✅"]);                      // access => MRU
            cache["u- 001 "] = "token=tok_new";                        // triggers eviction

            Console.WriteLine(cache["u-β77"] == null);                 // likely evicted => true
        }
    }
}
8

Thread‑Safe Store — Indexer Protected by ReaderWriterLockSlim

IndexersThread safetyLockingComplex

Scenario: Multiple threads update and read a shared in-memory store. Implement a thread-safe indexer using ReaderWriterLockSlim. What to implement: - SafeStore.this[string key] get/set - SafeStore.TryGet(string key, out string value) Rules: - Use ReaderWriterLockSlim - Reads should take read lock; writes should take write lock - Missing get should return null - Normalize key: trim + case-insensitive store ✅ Implement ONLY the TODO parts.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary
using System.Threading;                                              // ReaderWriterLockSlim

namespace ItTechGenie.M1.Indexers.Q8
{
    public class SafeStore
    {
        private readonly Dictionary<string, string> _data =            // shared store
            new(StringComparer.OrdinalIgnoreCase);

        private readonly ReaderWriterLockSlim _lock = new();          // lock

        // ✅ TODO: Student must implement only this helper
        private static string Normalize(string key)
        {
            // TODO:
            // - validate key
            // - trim
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public string? this[string key]
        {
            get
            {
                // TODO:
                // - normalize
                // - enter read lock
                // - return value or null
                // - exit lock in finally
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - normalize
                // - enter write lock
                // - set or remove if value null (your choice)
                // - exit lock in finally
                throw new NotImplementedException();
            }
        }

        // ✅ TODO: Student must implement only this method
        public bool TryGet(string key, out string value)
        {
            // TODO:
            // - normalize
            // - read lock
            // - try get
            throw new NotImplementedException();
        }
    }

    internal class Program
    {
        static void Main()
        {
            var s = new SafeStore();

            s[" feature flag ✅ "] = "ON";                             // set
            Console.WriteLine(s["FEATURE FLAG ✅"]);                   // get
            Console.WriteLine(s.TryGet("missing !@#", out var v));     // false
        }
    }
}
9

Proxy Indexer — Validate + Audit Each Access

IndexersProxyAuditingValidationComplex

Scenario: You wrap a store and need to audit every get/set with timestamp and caller label. What to implement: - AuditedStore.this[string key] get/set - Audit list should record: action(GET/SET), key, valueLength, label, timestamp Rules: - Underlying store is Dictionary - Validate key not empty; normalize by trim - On GET: if missing return null but still log attempt - On SET: if value too long (> 50 chars) throw InvalidOperationException ✅ Implement ONLY the TODO parts.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // Dictionary, List

namespace ItTechGenie.M1.Indexers.Q9
{
    public record AuditEntry(string Action, string Key, int ValueLen, string Label, DateTime At); // audit record

    public class AuditedStore
    {
        private readonly Dictionary<string, string> _inner = new(StringComparer.OrdinalIgnoreCase); // store
        public List<AuditEntry> Audit { get; } = new();            // audit list
        public string Label { get; }                                // who is calling

        public AuditedStore(string label) => Label = label;          // ctor

        // ✅ TODO: Student must implement only this helper
        private static string Normalize(string key)
        {
            // TODO:
            // - validate
            // - trim
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public string? this[string key]
        {
            get
            {
                // TODO:
                // - normalize
                // - try get
                // - log attempt
                // - return value or null
                throw new NotImplementedException();
            }
            set
            {
                // TODO:
                // - normalize
                // - validate value length <= 50 (if not throw)
                // - set
                // - log set
                throw new NotImplementedException();
            }
        }
    }

    internal class Program
    {
        static void Main()
        {
            var s = new AuditedStore("Admin ✅");

            s["notes !@#"] = "Hello World 123 ✅ α/β";
            Console.WriteLine(s["notes !@#"]);
            Console.WriteLine(s["missing key"] == null);

            Console.WriteLine("Audit=" + s.Audit.Count);
        }
    }
}
10

Log Buffer — Indexer Using Range for Slicing

IndexersRangeSlicingC# 8+Complex

Scenario: A log viewer wants to read a slice of recent messages. Implement an indexer that accepts Range: buffer[1..^1] What to implement: - LogBuffer.this[Range range] get -> string[] - LogBuffer.Add(string line) Rules: - Store lines in List<string> - The range should return a new array (copy) - Support '^' from end correctly - Validate bounds; if invalid throw ArgumentOutOfRangeException ✅ Implement ONLY the TODO methods.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console
using System.Collections.Generic;                                    // List

namespace ItTechGenie.M1.Indexers.Q10
{
    public class LogBuffer
    {
        private readonly List<string> _lines = new();                 // log lines list

        // ✅ TODO: Student must implement only this method
        public void Add(string line)
        {
            // TODO:
            // - validate line (allow spaces/special chars)
            // - add to list
            throw new NotImplementedException();
        }

        // ✅ TODO: Student must implement only this indexer
        public string[] this[Range range]
        {
            get
            {
                // TODO:
                // - compute start/end using range.GetOffsetAndLength(_lines.Count)
                // - copy that slice into array
                // - return array
                throw new NotImplementedException();
            }
        }

        public int Count => _lines.Count;                             // count helper
    }

    internal class Program
    {
        static void Main()
        {
            var b = new LogBuffer();

            b.Add("Login ✅ user='u-α12' ip='10.0.0.1'");
            b.Add("Order !@# id='O- 90 01' amount='₹ 1,999.25'");
            b.Add("Warning α/β: retry #3");

            var slice = b[0..2];                                      // first 2 lines
            Console.WriteLine("SliceLen=" + slice.Length);
        }
    }
}
11

Spreadsheet Grid — Chained Indexers (grid[row][col])

IndexersChained accessDesignComplex

Scenario: You are designing a mini spreadsheet API: - grid[row][col] = "value" where grid[row] returns a Row object that has its own indexer. What to implement: - Spreadsheet.this[int row] get-only => Row - Row.this[int col] get/set Rules: - Grid dimensions: rows/cols provided in constructor - Validate bounds at both levels (row, then col) - Store as string[,] (2D array) OR Dictionary based storage (your choice) - Empty cell should return "" (empty string) - Values may contain spaces/special chars; store as-is ✅ Implement ONLY the TODO parts.

✅ Sample input includes spaces, quotes, unicode α/β, emoji ✅, and special chars to test parsing and indexer edge cases.
Indexers scenario (students implement ONLY TODO methods) Ctrl+C
using System;                                                     // Console

namespace ItTechGenie.M1.Indexers.Q11
{
    public class Spreadsheet
    {
        private readonly string[,] _cells;                            // grid cells
        public int Rows { get; }                                      // rows
        public int Cols { get; }                                      // cols

        public Spreadsheet(int rows, int cols)
        {
            Rows = rows; Cols = cols;                                 // store dimensions
            _cells = new string[rows, cols];                           // allocate grid
        }

        // Row object gives second indexer
        public sealed class Row
        {
            private readonly Spreadsheet _grid;                       // back reference
            private readonly int _row;                                // row index

            public Row(Spreadsheet grid, int row) { _grid = grid; _row = row; } // ctor

            // ✅ TODO: Student must implement only this indexer (col)
            public string this[int col]
            {
                get
                {
                    // TODO:
                    // - validate col
                    // - return cell value or "" if null
                    throw new NotImplementedException();
                }
                set
                {
                    // TODO:
                    // - validate col
                    // - set value (store as-is)
                    throw new NotImplementedException();
                }
            }
        }

        // ✅ TODO: Student must implement only this indexer (row)
        public Row this[int row]
        {
            get
            {
                // TODO:
                // - validate row
                // - return new Row(this,row)
                throw new NotImplementedException();
            }
        }

        // helper for row/col validation
        internal void EnsureInRange(int row, int col)
        {
            if (row < 0 || row >= Rows) throw new ArgumentOutOfRangeException(nameof(row));
            if (col < 0 || col >= Cols) throw new ArgumentOutOfRangeException(nameof(col));
        }

        internal string GetCell(int row, int col) => _cells[row, col] ?? ""; // safe get
        internal void SetCell(int row, int col, string value) => _cells[row, col] = value; // set
    }

    internal class Program
    {
        static void Main()
        {
            var grid = new Spreadsheet(2, 3);

            grid[0][1] = "₹ 1,999.25 ✅";                               // set
            grid[1][2] = "Note: α/β !@#";                               // set

            Console.WriteLine(grid[0][0]);                               // empty => ""
            Console.WriteLine(grid[0][1]);                               // value
        }
    }
}