C# జెనెరిక్ కలెక్షన్స్ (List<T>, Dictionary<K,V>, HashSet<T>)
స్వాగతం అధ్యాయం 12 కి! 🗃️ జెనెరిక్స్ విప్లవం & ఎంటర్ప్రైజ్ డేటా కలెక్షన్స్
గత అధ్యాయంలో మనం నేర్చుకున్న అర్రేలు చాలా వేగవంతమైనవే అయినప్పటికీ, వాటికి ఒక పెద్ద బలహీనత ఉంది: ఫిక్స్డ్ సైజు (Fixed Size)! ఒకసారి 10 ఎలిమెంట్స్ తో అర్రే సృష్టించిన తర్వాత 11వ ఎలిమెంట్ చేర్చడం కుదరదు; కొత్త అర్రే సృష్టించి పాత డేటాను కాపీ చేయాల్సిందే. కానీ రియల్ వరల్డ్ ప్రాజెక్టులలో వినియోగదారులు ఎన్ని ఆర్డర్లు చేస్తారో, ఎన్ని మెసేజ్లు పంపుతారో ముందే తెలియదు. డేటా నిరంతరం పెరుగుతూ, తగ్గుతూ ఉంటుంది!
ఈ సమస్యకు పరిష్కారమే డైనమిక్ కలెక్షన్స్ (Dynamic Collections). ప్రారంభ C# లో ArrayList ఉండేది, కానీ అది ప్రతి వస్తువును object గా మార్చడం వల్ల విపరీతమైన మెమరీ నష్టం (Boxing/Unboxing) మరియు రన్టైమ్ ఎర్రర్లు వచ్చేవి. C# 2.0 లో మైక్రోసాఫ్ట్ Generics (<T>) ను ప్రవేశపెట్టి ప్రోగ్రామింగ్ చరిత్రలోనే అతిపెద్ద విప్లవాన్ని సృష్టించింది!
ఈ సమగ్ర 12వ అధ్యాయంతో మనం Phase 03 (మెథడ్స్, స్ట్రింగ్స్ & కలెక్షన్స్) ను ఘనంగా పూర్తి చేస్తూ — డైనమిక్ అర్రే కింగ్ List<T>, మెరుపు వేగంతో పనిచేసే Dictionary<TKey, TValue>, డూప్లికేట్స్ లేని HashSet<T>, మరియు Queue<T> / Stack<T> లను లోతుగా నేర్చుకొని — ఒక ఈ-కామర్స్ వేర్హౌస్ ఇన్వెంటరీ ప్రాసెసింగ్ ఇంజిన్ను నిర్మిస్తాం!
1 🗃️ అర్రేల పరిమితులు & డైనమిక్ కలెక్షన్ల ఆవిర్భావం
అర్రేలు కంప్యూటర్ మెమరీలో కంటిగ్యుయస్గా ఉంటాయి కాబట్టి వాటి సైజును మార్చడం అసాధ్యం. ఉదాహరణకు 100 సైజు గల అర్రే నిండిపోయినప్పుడు, 101వ ఎలిమెంట్ కోసం మెమరీలో పక్కనే స్థలం ఉండకపోవచ్చు.
దీనివల్ల సాఫ్ట్వేర్ ఆర్కిటెక్చర్ లో ఆటోమేటిక్ సైజ్ రీ-సైజింగ్ గల డైనమిక్ కలెక్షన్ల ఆవశ్యకత ఏర్పడింది. ఎలిమెంట్స్ చేరుస్తున్న కొద్దీ వాటంతట అవే పెరుగుతూ, తొలగించినప్పుడు తగ్గే కలెక్షన్స్ ఎంటర్ప్రైజ్ సాఫ్ట్వేర్కు అత్యంత కీలకం.
2 ⚠️ నాన్-జెనెరిక్ కలెక్షన్ల ప్రమాదాలు & Boxing / Unboxing పెనాల్టీ
జెనరిక్స్ రాకముందు C# లో System.Collections.ArrayList ఉండేది. ఇది ఏదైనా డేటాను object గా నిల్వ చేస్తుంది:
ఒక int ని object లోకి పెట్టినప్పుడు అది స్టాక్ నుండి హీప్లోకి మారుతుంది (Boxing - 10 రెట్లు స్లో). అంతేకాకుండా ArrayList లో నంబర్లు, పేర్లు, బూలియన్లు అన్నీ కలిసిపోతాయి. బయటకు తీసేటప్పుడు (int)list[0] అని కాస్ట్ చేయాలి. పొరపాటున స్ట్రింగ్ ఉంటే రన్టైమ్లో InvalidCastException వచ్చి క్రాష్ అవుతుంది!
3 🧬 జెనరిక్స్ (Generics <T>) విప్లవం
System.Collections.Generic నేమ్స్పేస్ లోని జెనరిక్స్ సిస్టమ్ టైప్ సేఫ్టీ (Type Safety) ని కంపైల్-టైమ్ లోనే అందిస్తుంది.
ఇక్కడ <T> అనేది ఒక టైప్ ప్లేస్హోల్డర్ (Type Parameter). మీరు List<int> అని సృష్టించినప్పుడు, అది కేవలం పూర్ణాంకాలను మాత్రమే అనుమతిస్తుంది. అందులోకి పొరపాటున స్ట్రింగ్ పెట్టడానికి ప్రయత్నించినా కంపైలర్ అక్కడికక్కడే అడ్డుకుంటుంది! ఎటువంటి బాక్సింగ్ ఓవర్హెడ్ ఉండదు.
4 📋 List<T> — ది డైనమిక్ అర్రే కింగ్ & Capacity Doubling
C# లో అత్యధికంగా ఉపయోగించే కలెక్షన్ List<T>.
అంతర్గత పనితీరు (Capacity Doubling Algorithm):
List<T> అంతర్గతంగా ఒక సాధారణ అర్రేను నిర్వహిస్తుంది. డీఫాల్ట్గా దాని కెపాసిటీ 0 ఉంటుంది. మొదటి ఎలిమెంట్ చేర్చగానే కెపాసిటీ 4 అవుతుంది. 4 నిండిపోగానే ఆటోమేటిక్గా రెట్టింపు (8) అవుతుంది. 8 నిండగానే 16, 32, 64... ఇలా రెట్టింపు అవుతూ కొత్త అంతర్గత అర్రేను తయారుచేసుకుంటుంది.
using System.Collections.Generic;
// C# 12 కలెక్షన్ ఎక్స్ప్రెషన్ తో List సృష్టి
List<string> employees = ["రాకేష్", "సునీత", "వినోద్"];
employees.Add("అనిత"); // చేర్చడం
employees.AddRange(["ప్రవీణ్", "శైలజ"]); // బహుళ వ్యక్తులను కలపడం
employees.Remove("వినోద్"); // నిర్దిష్ట వ్యక్తిని తొలగించడం
employees.Insert(0, "డైరెక్టర్ శర్మ"); // ఇండెక్స్ 0 వద్ద చేర్చడం
Console.WriteLine($"మొత్తం ఉద్యోగులు: {employees.Count}");
Console.WriteLine($"అంతర్గత కెపాసిటీ: {employees.Capacity}");
// లూప్ ద్వారా ప్రింట్ చేయడం
foreach (var emp in employees)
{
Console.WriteLine($" - {emp}");
}
5 🔑 Dictionary<TKey, TValue> — O(1) మెరుపు వేగ హాష్ టేబుల్
10 లక్షల మంది కస్టమర్లలో ఆధార్ నంబర్ లేదా ఈమెయిల్ ఆధారంగా ఒకరి వివరాలు వెతకాలంటే — లిస్ట్ వాడితే లీనియర్గా వెతకడానికి సెకన్లు పడుతుంది ((O(N))).
కానీ Dictionary<TKey, TValue> ఒక అంతర్గత హాష్ టేబుల్ (Hash Table) ను ఉపయోగిస్తుంది. 10 లక్షల రికార్డులు ఉన్నా సరే — కీ (Key) తెలిస్తే విలువను కేవలం (O(1)) స్థిర సమయంలో (ఒక్క మైక్రోసెకనులో) నేరుగా లాగుతుంది!
var currencyRates = new Dictionary<string, decimal>
{
["USD"] = 83.50m,
["EUR"] = 90.25m,
["GBP"] = 106.10m
};
// ❌ ప్రమాదకర మార్గం: లేని కీ ని పిలిస్తే KeyNotFoundException వచ్చి క్రాష్ అవుతుంది
// decimal jpy = currencyRates["JPY"];
// ✅ ప్రొఫెషనల్ రక్షిత మార్గం: TryGetValue (ఎక్సెప్షన్ రహితం)
if (currencyRates.TryGetValue("USD", out decimal inrRate))
{
Console.WriteLine($"1 USD = ₹{inrRate:N2}");
}
else
{
Console.WriteLine("కరెన్సీ లభించలేదు.");
}
6 🎯 HashSet<T> — డూప్లికేట్ రహిత హై-స్పీడ్ సెట్ ఆపరేషన్స్
ఒక కలెక్షన్లో కేవలం ప్రత్యేకమైన (Unique) అంశాలు మాత్రమే ఉండాలి, డూప్లికేట్స్ అస్సలు ఉండకూడదు అనుకున్నప్పుడు HashSet<T> ఉత్తమ ఎంపిక.
ఇది గణిత శాస్త్రంలోని యూనియన్ (Union), ఇంటర్సెక్షన్ (Intersection) ఆపరేషన్లను క్షణాల్లో చేస్తుంది:
HashSet<string> cricketFans = ["రాహుల్", "విరాట్", "రోహిత్", "అజయ్"];
HashSet<string> cinemaFans = ["అజయ్", "మహేష్", "ప్రభాస్", "రాహుల్"];
// ఇద్దరికీ కామన్ గా ఉన్న అభిమానులను కనుగొనడం (Intersection)
HashSet<string> commonFans = new HashSet<string>(cricketFans);
commonFans.IntersectWith(cinemaFans);
Console.WriteLine($"రెండు రంగాలనూ అభిమానించేవారు: {string.Join(", ", commonFans)}"); // రాహుల్, అజయ్
7 🥞 Stack<T> (LIFO) & Queue<T> (FIFO)
ప్రత్యేక ఆర్డరింగ్ అవసరమైనప్పుడు రెండు క్లాసిక్ డేటా స్ట్రక్చర్స్:
Stack<T>(Last-In, First-Out - LIFO): చివరగా వచ్చినది మొదట బయటకు వస్తుంది. మెథడ్స్:Push()(చేర్చడం),Pop()(తీసివేయడం),Peek()(చూడటం). ఉపయోగాలు: Undo/Redo ఫీచర్లు, బ్రౌజర్ హిస్టరీ బ్యాక్ బటన్.Queue<T>(First-In, First-Out - FIFO): మొదట వచ్చిన వారికి మొదటి ప్రాధాన్యత (క్యూ లైన్). మెథడ్స్:Enqueue()(క్యూలో చేరడం),Dequeue()(సర్వీస్ పొంది నిష్క్రమించడం),Peek(). ఉపయోగాలు: ప్రింటర్ జాబ్స్, వెబ్ సర్వర్ రిక్వెస్ట్ క్యూలు.
// 1. Queue (FIFO)
Queue<string> ticketCounter = new Queue<string>();
ticketCounter.Enqueue("కస్టమర్ 1");
ticketCounter.Enqueue("కస్టమర్ 2");
Console.WriteLine($"మొదట సర్వ్ చేయబడినది: {ticketCounter.Dequeue()}"); // కస్టమర్ 1
// 2. Stack (LIFO)
Stack<string> browserHistory = new Stack<string>();
browserHistory.Push("google.com");
browserHistory.Push("github.com");
browserHistory.Push("ourcompiler.com");
Console.WriteLine($"బ్యాక్ బటన్ కొడితే వచ్చేది: {browserHistory.Pop()}"); // ourcompiler.com
8 ⚡ మెమరీ ఆప్టిమైజేషన్: Capacity ప్రాపర్టీ ప్రాముఖ్యత
ఒక List<T> లో మీకు ఖచ్చితంగా 10,000 ఐటెమ్స్ వస్తాయని తెలిసినప్పుడు — సాధారణ new List<int>() అని రాస్తే, అది 0 నుండి 10,000 వరకు చేరుకోవడానికి 12 సార్లు ఇంటర్నల్ అర్రేను రీ-సైజ్ చేసి మెమరీ కాపీలు చేస్తుంది!
పరిష్కారం: new List<int>(10000) అని ప్రారంభ కెపాసిటీని ముందే నిర్దేశిస్తే, ఒక్కసారి మాత్రమే మెమరీ అలోకేట్ అవుతుంది. వేగం 3 రెట్లు పెరుగుతుంది!
9 📊 తులనాత్మక పట్టిక: Array vs List vs Dictionary vs HashSet
ప్రధాన డేటా కలెక్షన్ల పనితీరు మరియు సామర్థ్యాల మ్యాట్రిక్స్:
| కలెక్షన్ రకం | సైజు స్వభావం | లుకప్ వేగం (Search) | డూప్లికేట్స్ అనుమతి | కీలక ఉపయోగ సందర్భం |
|---|---|---|---|---|
| T[] (Array) | స్థిరమైనది (Fixed) | ఇండెక్స్ తో (O(1)), విలువతో (O(N)) | అవును | పరిమాణం ముందే తెలిసిన స్థిరమైన గ్రిడ్స్ & బఫర్స్ |
| List<T> | డైనమిక్ | ఇండెక్స్ తో (O(1)), సెర్చ్ (O(N)) | అవును | క్రమబద్ధమైన సాధారణ ఆబ్జెక్ట్ జాబితాలకు |
| Dictionary<K,V> | డైనమిక్ | కీ ద్వారా (O(1)) (అత్యుత్తమం) | కీలు డూప్లికేట్ ఉండకూడదు | ఐడీ, ఈమెయిల్ లేదా కోడ్ ద్వారా వేగవంతమైన సెర్చ్ లకు |
| HashSet<T> | డైనమిక్ | (O(1)) ఉనికి తనిఖీ | అస్సలు అనుమతించదు | డూప్లికేట్లను నిరోధించడానికి, సెట్ మ్యాథమెటిక్స్ కు |
10 🔒 ReadOnly కలెక్షన్స్ & డేటా రక్షణ
ఒక క్లాస్ తన అంతర్గత లిస్ట్ను బయటి ప్రపంచానికి అందించినప్పుడు — బయటి కోడ్ list.Clear() చేసి మొత్తం డేటాను తుడిచిపెట్టే ప్రమాదం ఉంది.
దీనికి పరిష్కారంగా IReadOnlyList<T> లేదా list.AsReadOnly() ను ఎక్స్పోజ్ చేయడం వల్ల బయటి వారు కేవలం చదవగలరు (Read-Only) తప్ప, డేటాను మార్చలేరు.
11 🚀 రియల్-వరల్డ్ ప్రాజెక్ట్: "E-Commerce Warehouse Inventory Engine"
Dictionary (స్టాక్ నిర్వహణ), Queue (ఆర్డర్ ప్రాసెసింగ్), HashSet (ప్రీమియం కస్టమర్లు), మరియు List (ఆడిట్ ఇన్వాయిస్) అన్నింటినీ సమన్వయం చేస్తూ — ఒక పూర్తి స్థాయి ఈ-కామర్స్ వేర్హౌస్ ఇన్వెంటరీ ఇంజిన్ ను తయారు చేద్దాం!
using System;
using System.Collections.Generic;
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.WriteLine("=================================================");
Console.WriteLine("📦 మన వేర్హౌస్ - స్మార్ట్ ఇన్వెంటరీ & ఆర్డర్ ఇంజిన్ v1.0");
Console.WriteLine("=================================================\n");
// 1. ఇన్వెంటరీ స్టాక్ (Dictionary<SKU, Quantity>)
var inventoryStock = new Dictionary<string, int>
{
["LAPTOP_PRO"] = 10,
["PHONE_X"] = 25,
["HEADSET_Z"] = 50
};
// 2. విక్రయ ధరలు (Dictionary<SKU, Price>)
var productPrices = new Dictionary<string, decimal>
{
["LAPTOP_PRO"] = 75000.00m,
["PHONE_X"] = 45000.00m,
["HEADSET_Z"] = 2500.00m
};
// 3. ఆర్డర్ ప్రాసెసింగ్ క్యూ (Queue<Order> - FIFO)
var orderQueue = new Queue<(string Customer, string Sku, int Qty)>();
orderQueue.Enqueue(("ఆనంద్", "LAPTOP_PRO", 2));
orderQueue.Enqueue(("భావన", "PHONE_X", 1));
orderQueue.Enqueue(("చరణ్", "HEADSET_Z", 4));
// 4. ప్రత్యేక కస్టమర్ల సెట్ (HashSet<Customer>)
HashSet<string> processedCustomers = new HashSet<string>();
// 5. కంప్లీట్ అయిన ఇన్వాయిస్ ల జాబితా (List<string>)
List<string> completedInvoices = new List<string>();
decimal totalRevenue = 0m;
Console.WriteLine("ఆర్డర్ల ప్రాసెసింగ్ ప్రారంభమైంది (FIFO Queue):\n");
while (orderQueue.Count > 0)
{
var currentOrder = orderQueue.Dequeue();
// స్టాక్ చెక్ చేయడం
if (inventoryStock.TryGetValue(currentOrder.Sku, out int availableQty) && availableQty >= currentOrder.Qty)
{
// స్టాక్ తగ్గించడం
inventoryStock[currentOrder.Sku] -= currentOrder.Qty;
decimal itemPrice = productPrices[currentOrder.Sku];
decimal billAmount = itemPrice * currentOrder.Qty;
totalRevenue += billAmount;
processedCustomers.Add(currentOrder.Customer);
string invoiceRecord = $"కస్టమర్: {currentOrder.Customer,-8} | వస్తువు: {currentOrder.Sku,-10} | Qty: {currentOrder.Qty} | బిల్: ₹{billAmount:N2}";
completedInvoices.Add(invoiceRecord);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"✅ ఆర్డర్ కన్ఫర్మ్: {invoiceRecord}");
Console.ResetColor();
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"❌ ఆర్డర్ విఫలం: {currentOrder.Sku} స్టాక్ సరిపోదు ({currentOrder.Customer} కు నిరాకరించాం)");
Console.ResetColor();
}
}
// నివేదిక ప్రింటింగ్
Console.WriteLine("\n=================================================");
Console.WriteLine(" వేర్హౌస్ డిస్పాచ్ నివేదిక ");
Console.WriteLine("=================================================");
Console.WriteLine($"మొత్తం సేవలందుకున్న ప్రత్యేక కస్టమర్లు: {processedCustomers.Count} మంది");
Console.WriteLine($"మొత్తం విక్రయాల రాబడి : ₹{totalRevenue:N2}");
Console.WriteLine("-------------------------------------------------");
Console.WriteLine("మిగిలిన తాజా స్టాక్ స్థితి:");
foreach (var item in inventoryStock)
{
Console.WriteLine($" - {item.Key,-12}: {item.Value} యూనిట్లు మిగిలాయి");
}
Console.WriteLine("=================================================\n");
ఈ పూర్తి కోడ్ను బ్రౌజర్ ఎడిటర్లో రన్ చేసి — కొత్త ఆర్డర్లను ఎన్క్యూ చేసి ఇన్వెంటరీ ఎలా అప్డేట్ అవుతుందో స్వయంగా చూడండి!
12 🛑 బిగినర్స్ చేసే 7 ఘోరమైన కలెక్షన్ తప్పులు
కలెక్షన్లతో పనిచేసేటప్పుడు సాఫ్ట్వేర్ ఇంజనీర్లు తరచుగా చేసే 7 పొరపాట్లు:
dict["UNKNOWN_KEY"] రాస్తే KeyNotFoundException వస్తుంది. ఎల్లప్పుడూ TryGetValue వాడాలి.
లూప్లో వేల ఐటెమ్స్ చేర్చేటప్పుడు పదే పదే అర్రే డబ్లింగ్ జరిగి మెమరీ వేస్ట్ అవుతుంది. ముందే సైజు ఇవ్వడం ఉత్తమం.
dict.Add("k", 1); dict.Add("k", 2); రాస్తే ఎక్సెప్షన్ వస్తుంది. విలువను ఓవర్రైట్ చేయాలంటే dict["k"] = 2; వాడాలి.
ArrayList లెగసీ కోడ్. ఆధునిక ప్రాజెక్టులలో కేవలం List<T> మాత్రమే వాడాలి.
లూప్ తిరుగుతున్నప్పుడు list.Remove() చేస్తే InvalidOperationException వస్తుంది.
కస్టమ్ ఆబ్జెక్ట్స్కు Equals మరియు GetHashCode రాయకపోతే హాష్ సెట్ డూప్లికేట్స్ను పసిగట్టలేదు.
ఐటెమ్స్ లేనప్పుడు పాప్ చేస్తే క్రాష్ అవుతుంది. TryDequeue లేదా TryPop వాడాలి.
13 🧠 ఇంటరాక్టివ్ సెల్ఫ్-అసెస్మెంట్ క్విజ్ (5 లోతైన ప్రాక్టికల్ ప్రశ్నలు & వివరణలు)
జెనెరిక్ కలెక్షన్లపై మీ పట్టును ఈ 5 ప్రశ్నలతో పరీక్షించుకోండి:
❓ ప్రశ్న 1: List<int> లో కెపాసిటీ గ్రోత్ అల్గారిథమ్ ఎలా పనిచేస్తుంది?
List<T> తన కెపాసిటీని రెట్టింపు (Doubling) చేస్తుంది (0 ➔ 4 ➔ 8 ➔ 16 ➔ 32...). ఒక కొత్త పెద్ద అర్రేను కేటాయించి పాత అంశాలను అందులోకి కాపీ చేస్తుంది.
❓ ప్రశ్న 2: డిక్షనరీలో dict["key"] కి బదులుగా dict.TryGetValue("key", out val) ఎందుకు వాడాలి?
KeyNotFoundException విసిరి అప్లికేషన్ను క్రాష్ చేస్తుంది. కానీ TryGetValue కీ ఉంటే విలువను ఇచ్చి true, లేకపోతే నిశ్శబ్దంగా false ఇస్తుంది (ఎటువంటి క్రాష్ ఉండదు).
❓ ప్రశ్న 3: Stack<T> మరియు Queue<T> ల మధ్య ప్రధాన వ్యత్యాసం ఏమిటి?
Stack<T> అనేది LIFO (Last-In, First-Out) సూత్రంపై పనిచేస్తుంది (చివరగా పెట్టినది మొదట వస్తుంది). Queue<T> అనేది FIFO (First-In, First-Out) సూత్రంపై పనిచేస్తుంది (మొదట చేరినది మొదట ప్రాసెస్ అవుతుంది).
❓ ప్రశ్న 4: C# లో "Boxing" అంటే ఏమిటి మరియు జెనరిక్స్ దానిని ఎలా నిరోధిస్తాయి?
int) ను Reference Type (object) గా మార్చినప్పుడు స్టాక్ నుండి హీప్లోకి డేటా కాపీ అవ్వడాన్ని బాక్సింగ్ అంటారు. జెనరిక్స్ నిర్దిష్ట టైప్ తో నేరుగా పనిచేయడం వల్ల ఎటువంటి బాక్సింగ్ అవసరం లేకుండా నేరుగా ఎగ్జిక్యూట్ అవుతాయి.
❓ ప్రశ్న 5: HashSet<T> లో డూప్లికేట్ ఎలిమెంట్ చేర్చడానికి ప్రయత్నిస్తే ఏమి జరుగుతుంది?
set.Add(x) మెథడ్ నిశ్శబ్దంగా false ని రిటర్న్ చేస్తుంది.