Skip to content

Instantly share code, notes, and snippets.

@tshego3
Last active January 8, 2026 22:09
Show Gist options
  • Select an option

  • Save tshego3/a31180a764bc5ebf7f3272eb63da601c to your computer and use it in GitHub Desktop.

Select an option

Save tshego3/a31180a764bc5ebf7f3272eb63da601c to your computer and use it in GitHub Desktop.
C# Cheatsheet

C# Cheatsheet

Common Source Code
using System;
using System.Collections.Concurrent;

// Example 0: Concurrent Example
// In a multi-threaded environment, standard collections like Dictionary or List can crash or corrupt data if multiple threads access them at once. The System.Collections.Concurrent namespace provides thread-safe alternatives.
class Program
{
    static void Main()
    {
        // 1. Initialize a thread-safe dictionary
        ConcurrentDictionary<string, string> inventory = new ConcurrentDictionary<string, string>();

        string key = "Laptop";
        string value = "MacBook Pro";

        // 2. First attempt to add the item
        Console.WriteLine($"Attempting to add {key}...");
        
        if (inventory.TryAdd(key, value))
        {
            Console.WriteLine("Success: Item added to inventory.");
        }
        else
        {
            Console.WriteLine("Failure: Item already exists.");
        }

        // 3. Second attempt to add the EXACT same key
        Console.WriteLine($"\nAttempting to add {key} again...");
		// inventory.TryRemove(key, out _);

        if (inventory.TryAdd(key, "Dell XPS"))
        {
            Console.WriteLine("Success: Item added.");
        }
        else
        {
            // This block will run because "Laptop" is already a key
            Console.WriteLine("Failure: Could not add. The key already exists in the collection.");
        }

        // 4. Display the final count
        Console.WriteLine($"\nFinal Inventory Count: {inventory.Count}");
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

// Example 1: Combining collections using Concat (simulate spread operator)
class Example1
{
    public static void Run()
    {
        List<int> listA = new List<int> { 1, 2 };
        List<int> listB = new List<int> { 3, 4 };
        var combinedList = listA.Concat(listB).ToList();
        Console.WriteLine("Combined List using Concat (simulate spread operator): " + string.Join(", ", combinedList));
        // Output: 1, 2, 3, 4
    }
}

// Example 2: Updating a list using .Select with a return statement in a lambda block.
// Anonymous types are best for temporary or read-only projections. For mutable or updatable data (especially in collections), use named classes or record types with mutable properties.
class Example2
{
    public static void Run()
    {
        List<int> numbers = new List<int> { 10, 20, 30 };
        var mappedList = numbers.Select(num =>
        // var mappedList = numbers.Select((num, index) =>
        {
            // Using block syntax with an explicit return statement to create an anonymous object.
            return new { Original = num, Double = num * 2 };
        }).ToList();
        Console.WriteLine("\nMapped List using Select with a return key:");
        foreach (var item in mappedList)
        {
            Console.WriteLine($"Original: {item.Original}, Double: {item.Double}");
        }
        // Output: Anonymous objects printed for each number.
    }
}

// Example 3: Chained syntax "Where().All().ToList()"
class Example3
{
    public static void Run()
    {
        // Wrap the list into our chainable type to allow a chainable All.
        List<int> numberList = new List<int> { 2, 4, 6, 8 };
        // Note: The original example's `.ToList().All()` would result in a boolean for `filteredList`.
        // If you intended to keep the list and then check 'All' as a separate step,
        // you would assign the ToList() result to a variable first.
        // For demonstration purposes, I'm showing the boolean result as implied by original chain.
        bool allEven = numberList
            .Where(n => n > 0) // Filter: keep numbers > 0
            .All(n => n % 2 == 0); // Check: if all numbers are even

        Console.WriteLine("\nResult of Where().All() chain: " + allEven);
        // Output: Result of Where().All() chain: True
    }
}

// Example 4: Using a lambda with fallback logic
class Example4
{
    public static void Run(object mappedList)
    {
        string result = new Func<string>(() =>
        {
            try
            {
                // This line was commented out in the original, but uncommented for demonstration
                // throw new Exception();
                // Assuming mappedList is accessible and not null, otherwise "Alternate value" will be used.
                // In a real scenario, you'd pass mappedList or define it within this scope.
                return (mappedList as List<object>)?.FirstOrDefault()?.ToString() ?? "Alternate value";
            }
            catch
            {
                return "Alternate value";
            }
        })();
        Console.WriteLine($"\n{result}");
        // Output: Either "{ Original = 10, Double = 20 }" or "Alternate value" based on the state and exception.
    }
}

// Example 5: Manually Creating the Logger
public class MyClassService
{
    private readonly ILogger<MyClassService> _logger;

    public MyClassService(ILogger<MyClassService> logger)
    {
        _logger = logger;
        _logger.LogInformation("MyClassService initialized.");
    }

    public void DoSomething()
    {
        _logger.LogInformation("Doing something important.");
    }
}

class Example5
{
    public static void Run()
    {
        var loggerFactory = LoggerFactory.Create(builder =>
        {
            // Configure logging providers
            builder.AddConsole();
            // Optionally add more providers here (e.g., Debug, EventSource)
        });

        ILogger<MyClassService> logger = loggerFactory.CreateLogger<MyClassService>();
        // ILogger<MyClassService> logger = new LoggerFactory().CreateLogger<MyClassService>(); // This would create a new factory every time

        // Now you can pass this logger to your service
        var myClassService = new MyClassService(logger);
        myClassService.DoSomething();
    }
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Example1.Run();
            Example2.Run();
            // For Example 4, we need to pass a list (or simulate it)
            // Let's create a dummy list for Example 4 based on Example 2's output structure
            List<object> dummyMappedList = new List<object>
            {
                new { Original = 10, Double = 20 } // Simulating an item from mappedList
            };
            Example4.Run(dummyMappedList);
            Example3.Run(); // Changed order to make Example 3's output clearer after previous runs
            Example5.Run();
        }
        catch (Exception ex)
        {
            // 1. Catch the error
            Console.Error.WriteLine($"[LOG] An error occurred in PerformOperationWithLogging: {ex.Message}");
            // You might log more details, like ex.StackTrace, to a file or monitoring system here.

            // 2. Re-throw the original exception
            // IMPORTANT: Just 'throw;' preserves the original stack trace.
            // 'throw ex;' (BAD) would reset the stack trace to this point, losing origin info.
            throw;
        }

        Console.WriteLine("--- Demonstrating Alternatives and Why .Wait() Is Bad ---");

        var demo = new AsyncAlternatives();

        Console.WriteLine("\n--- 1. Fire and Forget (Ignoring the Task) ---");
        // Good for truly background, non-critical tasks where exceptions are handled internally.
        // Bad for: No completion notification, unobserved exceptions if not handled internally.
        demo.FireAndForgetCall();
        Console.WriteLine("Main thread continues immediately after FireAndForgetCall.");
        Task.Delay(500).Wait(); // Give async task a moment to start
        Console.WriteLine("--------------------------------------------\n");

        Console.WriteLine("--- 2. Task.Run() (Offloading to Thread Pool) ---");
        // Good for: Moving CPU-bound work off the main thread, fire-and-forget.
        // Bad for: Same unobserved exception issues as pure fire-and-forget, slight overhead.
        demo.TaskRunCall();
        Console.WriteLine("Main thread continues immediately after TaskRunCall.");
        Task.Delay(500).Wait(); // Give async task a moment to start
        Console.WriteLine("----------------------------------------------\n");

        Console.WriteLine("--- 3. ContinueWith (For Completion/Error Handling) ---");
        // Good for: Observing completion or handling exceptions from a fire-and-forget task without blocking.
        // Bad for: Can add complexity, less readable than async/await.
        demo.ContinueWithCall();
        Console.WriteLine("Main thread continues immediately after ContinueWithCall.");
        Task.Delay(3000).Wait(); // Give async task and continuation a moment to complete
        Console.WriteLine("-----------------------------------------------\n");

        Console.WriteLine("--- Why .Wait() Is Bad (and .Result too) ---");
        // .Wait() blocks the calling thread, leading to unresponsiveness and deadlocks.
        // It also wraps exceptions in AggregateException, making error handling clunky.
        try
        {
            Console.WriteLine("Attempting to use .Wait() - This will block the current thread.");
            demo.DoSomethingAsync().Wait(); // BAD: Blocks the Main thread
            Console.WriteLine("If this were a UI app, the UI would freeze here.");
            Console.WriteLine("If this were ASP.NET, a request thread would be tied up.");
        }
        catch (AggregateException ex)
        {
            Console.WriteLine($"Caught an AggregateException from .Wait(): {ex.InnerExceptions[0].Message}");
            // Better error handling: foreach (var inner in ex.InnerExceptions) { Console.WriteLine(inner.Message); }
        }
        // DEADLOCK POTENTIAL (common in UI/ASP.NET):
        // If DoSomethingAsync internally used await on a context that requires the current thread,
        // and that thread is blocked by .Wait(), it would deadlock.

        Console.WriteLine("\n--- Conclusion ---");
        Console.WriteLine("Prefer 'await' whenever possible to enable true asynchronous, non-blocking execution.");
        Console.WriteLine("Only use 'async void' for event handlers, with internal exception handling.");
        Console.WriteLine("For other 'void' methods, consider fire-and-forget with internal error handling,");
        Console.WriteLine("or 'ContinueWith' for observing completion/errors if not awaiting is an absolute must.");
    }

    class AsyncAlternatives
    {
        // A simulated async task for demonstrations
        public async Task DoSomethingAsync()
        {
            Console.WriteLine("    [Async] DoSomethingAsync started.");
            await Task.Delay(1000); // Simulate network call or I/O
            // Uncomment the line below to see exception handling in action for different scenarios
            // throw new InvalidOperationException("Simulated error in async task!");
            Console.WriteLine("    [Async] DoSomethingAsync finished.");
        }

        // 1. Fire and Forget - Calls async method and discards the Task using _ =. Silent failures.
        public void FireAndForgetCall()
        {
            Console.WriteLine("    Calling DoSomethingAsync() and discarding the Task.");
            _ = DoSomethingAsync(); // Use '_' discard operator for clarity
        }

        // 2. Task.Run() - Wraps async method in Task.Run to run on thread pool. Adds thread overhead.
        public void TaskRunCall()
        {
            Console.WriteLine("    Wrapping DoSomethingAsync() in Task.Run().");
            _ = Task.Run(async () => await DoSomethingAsync());
        }

        // 3. ContinueWith - Attaches continuation to the Task for post-completion logic. Manual error handling.
        public void ContinueWithCall()
        {
            Console.WriteLine("    Calling DoSomethingAsync() and attaching a ContinueWith.");
            DoSomethingAsync().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                    foreach (var ex in task.Exception.InnerExceptions)
                    {
                        Console.Error.WriteLine($"    [ContinueWith Error] Async task failed: {ex.Message}");
                    }
                }
                else if (task.IsCompletedSuccessfully)
                {
                    Console.WriteLine("    [ContinueWith Success] Async task completed successfully.");
                }
                // Add else if (task.IsCanceled) for cancellation handling
            }, TaskScheduler.Default); // Ensures continuation runs on a thread pool thread
        }
        
        // 4. Blocking Call with .GetAwaiter().GetResult() - Blocks the thread until the async method completes and unwraps exceptions. Can cause deadlocks, blocks thread and not suitable for UI.
        public void BlockingCall()
        {
            Console.WriteLine("Blocking on DoSomethingAsync() using .GetAwaiter().GetResult().");
            try
            {
                DoSomethingAsync().GetAwaiter().GetResult();
                Console.WriteLine("BlockingCall] Completed without exception.");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"[BlockingCall Error] Exception: {ex.Message}");
            }
        }
    }
}
Batch Queue Functionality
Batch Queue - Single Thread
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class Program
{
    public class BatchQueue<T>
    {
        private readonly Queue<T> _queue;
        private readonly int _batchSize;

        public BatchQueue(IEnumerable<T> items, int batchSize)
        {
            _queue = new Queue<T>(items);
            _batchSize = batchSize;
        }

        // Dequeue the next batch
        public List<T> GetNextBatch()
        {
            var batch = new List<T>(_batchSize);
            while (batch.Count < _batchSize && _queue.Count > 0)
            {
                batch.Add(_queue.Dequeue());
            }
            return batch;
        }

        public bool HasItems => _queue.Count > 0;
    }

    public static async Task<List<int>> xyzAsync(List<int> batch)
    {
        Console.WriteLine($"Processing batch of {batch.Count} items...");
        await Task.Delay(200); // simulate some async work
        foreach (int item in batch)
        {
            Console.WriteLine($"Batch item: {item}");
        }
        return batch; // could return transformed or filtered results
    }

    public static async Task Main()
    {
        var items = Enumerable.Range(1, 4000).ToList();
        var queue = new BatchQueue<int>(items, 100);
        var processedItems = new List<int>();

        while (queue.HasItems)
        {
            var batch = queue.GetNextBatch();
            var processedBatch = await xyzAsync(batch);

            processedItems.AddRange(processedBatch); // Save processed results
        }

        Console.WriteLine($"All {processedItems.Count} items processed successfully!");
    }
}
Batch Queue - Multiple Thread
// For .NET 6+, use Parallel.ForEachAsync Method: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-9.0
await Parallel.ForEachAsync(
    Enumerable.Range(1, 4000).Chunk(100),
    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
    async (batch, _) => await xyzAsync(batch.ToList())
);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public class BatchQueue<T>
    {
        private readonly Queue<T> _queue;
        private readonly int _batchSize;

        public BatchQueue(IEnumerable<T> items, int batchSize)
        {
            _queue = new Queue<T>(items);
            _batchSize = batchSize;
        }

        // Dequeue the next batch (thread-safe)
        public List<T> GetNextBatch()
        {
            lock (_queue)
            {
                var batch = new List<T>(_batchSize);
                while (batch.Count < _batchSize && _queue.Count > 0)
                {
                    batch.Add(_queue.Dequeue());
                }
                return batch;
            }
        }

        public bool HasItems
        {
            get { lock (_queue) return _queue.Count > 0; }
        }
    }

    public static async Task<List<int>> xyzAsync(List<int> batch)
    {
        Console.WriteLine($"Processing batch of {batch.Count} items on thread {Thread.CurrentThread.ManagedThreadId}...");
        await Task.Delay(200); // This simulates I/O (network, disk, or database) async work
        foreach (int item in batch)
        {
            Console.WriteLine($"Batch item: {item}");
        }
        return batch;
    }

    public static async Task Main()
    {
        var items = Enumerable.Range(1, 4000).ToList();
        var queue = new BatchQueue<int>(items, 100);
        var processedItems = new List<int>();
		
		// CPU-bound tasks: Limit concurrent tasks to the number of CPU cores to avoid overloading the processor (e.g., for compression, image processing, or encryption).
		// I/O-bound tasks: You can run many more concurrent tasks than cores (2×–10×) since tasks spend much of their time waiting on I/O operations like network, disk, or database access.
        bool isCpuBound = false; // set based on your workload
		int maxConcurrency = 0;
		#if DEBUG 
			maxConcurrency = Environment.ProcessorCount;
		#elif RELEASE
			maxConcurrency = Math.Max(1, Environment.ProcessorCount * (isCpuBound ? 1 : Environment.ProcessorCount));
		#endif
		
        var tasks = new List<Task<List<int>>>();

        while (queue.HasItems)
        {
            // Launch up to N batches in parallel
            while (tasks.Count < maxConcurrency && queue.HasItems)
            {
                var batch = queue.GetNextBatch();
                tasks.Add(xyzAsync(batch));
            }

            // Wait for at least one to finish
            var finished = await Task.WhenAny(tasks);
            tasks.Remove(finished);
            processedItems.AddRange(await finished);
        }

        // Finish remaining batches
        processedItems.AddRange((await Task.WhenAll(tasks)).SelectMany(x => x));

        Console.WriteLine($"All {processedItems.Count} items processed successfully!");
    }
}
Pass by Value vs Pass by Reference
using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        // Initialize variables
        int a = 10;      // Used for Pass by Value
        int b = 10;      // Used for Pass by ref
        int c;           // Used for Pass by out (no need to initialize)
        List<int> numbers = new List<int> { 1, 2, 3 };  // List passed by reference

        Console.WriteLine("Before:");
        Console.WriteLine($"a (Pass by Value)     = {a}");
        Console.WriteLine($"b (Pass by ref)       = {b}");
        Console.WriteLine($"c (Pass by out)       = (uninitialized)");
        Console.WriteLine($"numbers (Pass by ref) = {string.Join(", ", numbers)}");

        // Call methods
        PassByValue(a);
        PassByRef(ref b);
        PassByOut(out c);
        ModifyList(numbers); // Pass by reference - modify list items

        Console.WriteLine("\nAfter:");
        Console.WriteLine($"a (Pass by Value)     = {a}");   // Still 10
        Console.WriteLine($"b (Pass by ref)       = {b}");   // Changed to 20
        Console.WriteLine($"c (Pass by out)       = {c}");   // Set to 30
		Console.WriteLine($"numbers (Pass by ref) = {string.Join(", ", numbers)}"); // Changed
		
		PassListByRef(ref numbers);
		Console.WriteLine($"numbers (Pass by ref, after) = {string.Join(", ", numbers)}"); // Changed
		
		PassListItemByRef(ref numbers);
		Console.WriteLine($"numbers (Pass by ref, final) = {string.Join(", ", numbers)}"); // Changed
    }

    static void PassByValue(int x)
    {
        x = 100;
    }

    static void PassByRef(ref int y)
    {
        y = 20;
    }
	
	static void PassListByRef(ref List<int> y)
    {
        for (int i = 0; i < y.Count; i++)
        {
            y[i] *= 100;  // Modify each item in the list
        }
    }
	
	static void PassListItemByRef(ref List<int> y)
    {
        y[2] *= 1000;
    }

    static void PassByOut(out int z)
    {
        z = 30;
    }

    // List and ObservableCollection are a reference type; changes to its elements affect the original list
    static void ModifyList(List<int> list)
	// static void ModifyList(ObservableCollection<int> list)
    {
        for (int i = 0; i < list.Count; i++) // foreach is a read-only copy of each element. So trying to modify it won't affect the original list.
        {
            list[i] *= 10;  // Modify each item in the list
        }
    }
}
Inheritance and Extension Methods
  • Anonymous & Tuple Types
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

public class Animal
{
	public string Name { get; set; }
	public int Age { get; set; }
	public void DisplayInfo()
	{
		Console.WriteLine($"Name: {Name}, Age: {Age}");
	}
}

public class Dog : Animal
{
	public string Breed { get; set; }
	public bool IsVaccinated { get; set; }
	public void Bark()
	{
		Console.WriteLine($"{Name} is barking.");
	}
}

public static class AnimalExtensions
{
	public static void PrintInfo(this Animal animal)
	{
		Console.WriteLine($"Animal Info - Name: {animal.Name}, Age: {animal.Age}");
	}
}

public class Program
{
	private static string GetJsonValue<T1, T2>(T1 _paramOne, T2 _paramTwo)
	{
		Dictionary<string, object> model = new Dictionary<string, object>();
		model.Add("_paramOne", _paramOne);
		model.Add("_paramTwo", _paramTwo);
		
		// Anonymous type (read-only) is simplest when your payload shape is fixed and known at compile time.
		// var model = new
		// {
		// 	_paramOne = _paramOne,
		// 	_paramTwo = _paramTwo
		// };
		// JSON representation
		// {
		//   "_paramOne": "",
		//   "_paramTwo": ""
		// }

		// Tuple gives you named slots without a class.
		// var model = Tuple.Create(_paramOne, _paramTwo);
		// var model = (
		//     _paramOne : _paramOne,
		//     _paramTwo : _paramTwo
		// );
		// JSON representation (Tuples lose their custom field names during JSON serialization because those names aren’t preserved in metadata—they’re just syntactic sugar.)
		// {
		//   "Item1": "",
		//   "Item2": ""
		// }
		
		return JsonConvert.SerializeObject(model);
	}

	public static void Main()
	{
		Dog d1 = new Dog
		{
			Name = "Buddy",
			Age = 3,
			Breed = "Golden Retriever",
			IsVaccinated = true
		};
		var d2 = new
		{
			Name = "Danger",
			Age = 4,
			Breed = "Pit Bull",
			IsVaccinated = false
		};
		d1.DisplayInfo();
		d1.Bark();
		d1.PrintInfo(); // Using the extension method
		Console.WriteLine(GetJsonValue(d1, d2));
	}
}
Null & Populated Property Inspector
using System;
using System.Collections.Generic;
using System.Reflection;
					
public class Program
{
	public static void Main()
	{
		try
		{
			PersonDto person = new PersonDto(birthDate: DateTime.Now, email: ""){ Name = "User"};
			var (nullProps, populatedProps) = DtoChecker.CheckDtoProperties(person, checkNonRequired: true);

			Console.WriteLine("Null/Empty Properties: " + string.Join(", ", nullProps));
			Console.WriteLine("Populated Properties: " + string.Join(", ", populatedProps));
		}
		catch (Exception ex)
		{
			Console.WriteLine($"Error checking DTO: {ex.Message}");
		}
	}
}

public class PersonDto
{
	public required string Name { get; set; }
	public int Age { get; set; }
	public DateTime? BirthDate { get; set; }
	public string? Email { get; set; }
	public List<string> Contacts { get; set; }

	public PersonDto(string name = "User", int age = 0, DateTime? birthDate = null, string email = "user@example.com", List<string> contacts = default)
	{
		Name = name;
		Age = age;
		BirthDate = birthDate;
		Email = email;
		Contacts = contacts;
	}
}

public class DtoChecker
{
	public static (List<string> NullProps, List<string> PopulatedProps) 
		CheckDtoProperties(object dto, bool checkNonRequired = true)
	{
		var nullProps = new List<string>();
		var populatedProps = new List<string>();

		try
		{
			if (dto == null)
				throw new ArgumentNullException(nameof(dto), "DTO object cannot be null.");

			var props = dto.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

			foreach (var prop in props)
			{
				var type = prop.PropertyType;
				var value = prop.GetValue(dto);

				if (!checkNonRequired && IsNullable(type))
					continue;

				if (IsEmpty(value, type))
					nullProps.Add(prop.Name);
				else
					populatedProps.Add(prop.Name);
			}
		}
		catch (Exception ex)
		{
			Console.WriteLine($"Error checking DTO: {ex.Message}");
		}

		return (nullProps, populatedProps);
	}

	private static bool IsNullable(Type type) =>
		!type.IsValueType || Nullable.GetUnderlyingType(type) != null;

	private static bool IsEmpty(object value, Type type)
	{
		if (value == null) return true;
		if (type == typeof(string) && string.IsNullOrWhiteSpace((string)value)) return true;
		if (type.IsValueType && Activator.CreateInstance(type)?.Equals(value) == true) return true;
		return false;
	}
}
Difference between Controller and ApiController
  • Straightforward difference:

    • ApiController → used in Web API, returns JSON/XML (for APIs only).
    • Controller → used in ASP.NET MVC, returns Views or data (for web pages).
  • ApiController (Web API):

    using System.Threading.Tasks;
    using System.Web.Http;
    
    public class ProductsController : ApiController
    {
        [HttpPost]
        public async Task<IHttpActionResult> AddProduct(Product product)
        {
            await Task.Delay(100); // simulate async work
            return Ok(new { message = "Product added", product });
        }
    }
    
    public class Product { public string Name { get; set; } }
    curl -X POST https://localhost:5001/api/products \
        -H "Content-Type: application/json" \
        -d '{"name":"Book"}'
  • Controller (MVC):

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System.IO;
    using System.Web.Mvc; // Classic ASP.NET MVC
    // using Microsoft.AspNetCore.Mvc; // ASP.NET MVC (.NET Core)
    using System.Threading.Tasks;
    
    public class ProductsController : Controller
    {
        [HttpPost]
        [Route("products/add")] // Optional - Reachable Externally
        public async Task<ActionResult> AddProduct() // Classic ASP.NET MVC
        {
            using (StreamReader reader = new StreamReader(Request.InputStream))
            {
                string body = await reader.ReadToEndAsync();
                Product product = JsonConvert.DeserializeObject<Product>(body);
                return Json(new { message = "Product added", product });
            }
        }
        // public async Task<IActionResult> AddProduct([FromBody]Product product) // ASP.NET MVC (.NET Core)
        // {
        //     await Task.Delay(100);
        //     return Json(new { message = "Product added", product });
        // }
    }
    
    public class Product { public string Name { get; set; } }
    curl -X POST https://localhost:5001/products/add \
        -H "Content-Type: application/json" \
        -d '{"name":"Book"}'
  • In short:

    • ApiController → Pure API (Web API, returns JSON).
    • Controller → MVC (can return Views or JSON).
Asynchronous Property Accessor in a Sealed Class
// https://blog.stephencleary.com/2013/01/async-oop-3-properties.html
using System;
using System.Threading.Tasks;
					
public class Program
{
	public static async Task Main()
	{
		Console.WriteLine(await new MyClass().MyProperty);
		Console.WriteLine(await MyClass.MyParamProperty());
		Console.WriteLine(await MyClass.MyParamProperty(3000));
	}
}

public sealed class MyClass
{
    private async Task<int> GetMyProperty()
    {
        await Task.Delay(1000);
        return 1000;
    }
    public Task<int> MyProperty
    {
        get { return GetMyProperty(); }
    }
	public static async Task<int> MyParamProperty(Nullable<int> param = null)
    {
		await Task.Delay(1000);

        if (param.HasValue == false)
		{
			// For Non-Async Method
			// return Task.FromResult<int>(13);
			return 2000;
		}
		else
		{
			return param.Value;
		}
    }
}
Best Practice: Avoiding "Collection was modified" Exceptions
  • The Issue
    • A common runtime error in C# is System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

    • This occurs when you attempt to add, remove, or modify items in a collection (like a List<T>) while you are currently iterating through it with a foreach loop.

      • Example of Problematic Code

        foreach (var item in myCollection)
        {
            if (item.ShouldBeRemoved)
            {
                // ERROR: This modifies the collection directly causing the iterator to become invalid
                myCollection.Remove(item); 
            }
            
            // OR modifying via reference in a way that updates the underlying structure
            if (item.NeedsUpdate)
            {
                // If this method internally modifies 'myCollection', it will also throw
                ProcessAndSave(item, myCollection);
            }
        
            // ERROR: Replacing an object in the collection also invalidates the enumerator
            // int index = myCollection.IndexOf(item);
            // myCollection[index] = modifiedItem; // Throws InvalidOperationException
        }
      • The Solution

        • To safely modify a collection while iterating, iterate over a snapshot or copy of the collection. The simplest way to do this is by using .ToList().

        • The Fix

          // .ToList() creates a separate copy solely for the loop to iterate through
          foreach (var item in myCollection.ToList())
          {
              if (item.ShouldBeRemoved)
              {
                  // Safe: We are modifying 'myCollection', but iterating over the temporary list copy
                  myCollection.Remove(item); 
              }
          }
        • Alternative: Using a for loop

          • You can also use a standard for loop, but you must be careful with indices.

          • Option 1: Iterate Backwards (Recommended for Removals

            • When removing items, it is safest to iterate backwards (from Count - 1 down to 0). This ensures that removing an item does not shift the indices of the items you haven't processed yet.
            for (int i = myCollection.Count - 1; i >= 0; i--)
            {
                var item = myCollection[i];
                if (item.ShouldBeRemoved)
                {
                    myCollection.RemoveAt(i); // Safe and efficient
                }
            }
          • Option 2: Iterate Forwards (Careful Approach

            • If you traverse forward and remove an item, you must decrement the index i so you don't skip the next element. This is more error-prone.
            for (int i = 0; i < myCollection.Count; i++)
            {
                var item = myCollection[i];
                if (item.ShouldBeRemoved)
                {
                    myCollection.RemoveAt(i);
                    i--; // CRITICAL: Adjust index to account for the shift
                }
            }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment