- Early Binding: Type resolved at compile-time (faster). - Late Binding: Type resolved at runtime (e.g., `dynamic`). Example:
dynamic d = 10;
d = "Hello"; // Late binding
C#MemberwiseCloneObject Cloning
`MemberwiseClone()` creates a shallow copy of an object, copying fields but not nested objects. Example:
class MyClass {
public int X;
}
MyClass c1 = new MyClass { X = 10 };
MyClass c2 = (MyClass)c1.MemberwiseClone();
C#Preprocessor DirectivesConditional Compilation
Conditional directives (`#if`, `#endif`) include/exclude code based on symbols. Example:
#if DEBUG
Console.WriteLine("Debug mode");
#endif
C#LambdasDelegates
- Delegate: Type-safe function pointer. - Lambda: Concise syntax for creating delegate instances. Example:
delegate int D(int x);
D d = x => x * x;
C#Deferred ExecutionLINQ
Deferred execution delays query execution until results are needed, improving performance and enabling query composition. Example:
var numbers = new[] { 1, 2, 3 }.Where(n => n > 1); // Not executed yet
C#FinalizeDisposeResource Management
- Dispose: Deterministic cleanup for managed/unmanaged resources. - Finalize: Non-deterministic, for unmanaged resources only. Example:
class Resource : IDisposable {
public void Dispose() { }
~Resource() { }
}
C#Static ConstructorsClass Initialization
Static constructors initialize static members, called automatically before first use. Example:
class MyClass {
static MyClass() { }
}
C#IEnumerableIQueryableCollections
- IEnumerable: In-memory, executes locally. - IQueryable: Queryable, executes on the data source (e.g., database). Example:
IEnumerable e = new List();
IQueryable q = e.AsQueryable();
C#IQueryableICollectionIListIDictionaryInterfaces
- IQueryable: For queryable data sources. - ICollection: Basic collection operations. - IList: Indexed collection with add/remove. - IDictionary: Key-value pairs. Example:
IList list = new List();
IDictionary dict = new Dictionary();
C#IEqualityComparerCustom Equality
An `IEqualityComparer` defines custom equality and hash code logic for a type, used by collections like `Dictionary` or LINQ methods (e.g., `Distinct`, `GroupBy`).
Why Needed: - To compare objects based on specific properties (e.g., case-insensitive strings). - To customize equality for complex types without modifying their `Equals` or `GetHashCode`. - To optimize collection operations by providing domain-specific equality logic.
Implementation: - Implement `Equals(T x, T y)` to compare two objects for equality. - Implement `GetHashCode(T obj)` to return a consistent hash code for equal objects. - Ensure `GetHashCode` is stable and aligns with `Equals` to avoid collection errors.
Use Cases: - Case-insensitive key lookups in a dictionary. - Grouping objects by a subset of properties in LINQ queries. Example:
using System;
using System.Collections.Generic;
public class CaseInsensitiveComparer : IEqualityComparer
{
public bool Equals(string x, string y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.Equals(y, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(string obj)
{
if (obj == null) return 0;
return obj.ToLowerInvariant().GetHashCode();
}
}
class Program
{
static void Main()
{
var dict = new Dictionary(new CaseInsensitiveComparer())
{
{ "Key", "Value" },
{ "KEY", "NewValue" } // Overwrites due to case-insensitive comparison
};
Console.WriteLine(dict.Count); // 1
Console.WriteLine(dict["key"]); // NewValue
// LINQ Example
var names = new[] { "Alice", "alice", "Bob" };
var distinctNames = names.Distinct(new CaseInsensitiveComparer());
Console.WriteLine(string.Join(", ", distinctNames)); // Alice, Bob
}
}
C#Dependency Injection.NET CoreCustom Lifetime
.NET Core’s dependency injection (DI) is built into the framework via the `Microsoft.Extensions.DependencyInjection` package, providing a lightweight, extensible DI container.
How DI Works: - Services are registered in the `IServiceCollection` during application startup (e.g., in `Program.cs`). - Common lifetimes include: - Transient: New instance per request. - Scoped: Same instance within a scope (e.g., HTTP request). - Singleton: Same instance for the application’s lifetime. - The container resolves dependencies when constructing objects, injecting them via constructors, properties, or methods. - Middleware, controllers, and services can consume registered services via constructor injection.
Custom Service Lifetime: - Implement a custom lifetime by creating a class that implements `IServiceProvider` or extends `ServiceLifetime`. - Use a factory pattern or custom scope management to control instance creation and disposal. - Register the custom lifetime with a descriptor in `IServiceCollection`.
Use Cases: - Use DI for loose coupling, testability, and modularity. - Custom lifetimes are rare but useful for specialized scenarios like per-user caching or connection pooling. Example:
using Microsoft.Extensions.DependencyInjection;
using System;
public interface IMyService { string GetData(); }
public class MyService : IMyService, IDisposable
{
public string GetData() => "Service Data";
public void Dispose() => Console.WriteLine("Service Disposed");
}
// Custom Lifetime
public class CustomLifetime : IServiceLifetime
{
private readonly Func
C#Garbage CollectionPerformance Optimization
.NET’s garbage collector (GC) automatically reclaims memory for unused objects using a mark-and-sweep algorithm with generational collection.
How it Works: - The GC identifies live objects (reachable from roots like stack, static fields) and marks them. - Unmarked objects are swept, and memory is compacted to reduce fragmentation. - Generations: - Gen 0: Short-lived objects (e.g., locals). Collected frequently, fast due to small size. - Gen 1: Objects surviving Gen 0, a buffer between Gen 0 and Gen 2. - Gen 2: Long-lived objects (e.g., statics). Collected infrequently, slower due to size. - The GC triggers collections based on memory pressure or allocation thresholds, with higher generations requiring more time.
Strategies to Minimize GC Impact: - Reduce Allocations: Use value types (structs) for small data, Span, or object pooling for reusable objects. - Avoid Large Object Heap (LOH): Keep objects <85KB to avoid LOH, which is costly to collect. - Use Weak References: Allow GC to collect objects when memory is low. - Dispose Resources: Implement IDisposable for unmanaged resources and use `using` statements. - Profile GC: Use PerfView or dotMemory to monitor allocations and collections. - Tune GC: Configure GC modes (e.g., Server GC for high-throughput apps) via runtimeconfig.json.
Use Cases: Optimize for real-time systems, games, or high-throughput APIs where GC pauses are critical. Example:
using System;
using System.Buffers;
class Program
{
static void Main()
{
// Minimize allocations with Span
Span buffer = stackalloc byte[100];
buffer[0] = (byte)'A';
Console.WriteLine((char)buffer[0]); // A
// Object pooling
var pool = ArrayPool.Shared;
var pooledBuffer = pool.Rent(1024);
try
{
pooledBuffer[0] = (byte)'B';
Console.WriteLine((char)pooledBuffer[0]); // B
}
finally
{
pool.Return(pooledBuffer);
}
// Weak reference
var weakRef = new WeakReference(new object());
GC.Collect();
Console.WriteLine(weakRef.TryGetTarget(out var _)); // False (collected)
}
}
C#Async/AwaitInternal WorkingPitfalls
The async/await pattern in C# simplifies asynchronous programming by allowing developers to write asynchronous code that resembles synchronous code. Internally, it relies on a state machine generated by the compiler.
How it Works: - When a method is marked with `async`, the C# compiler transforms it into a state machine. This state machine manages the method’s execution, including suspensions and resumptions at `await` points. - The `await` keyword pauses the method’s execution until the awaited task completes, yielding control back to the caller. The state machine captures the method’s state (local variables, etc.) and schedules a continuation to resume execution. - The method returns a `Task` or `Task`, which represents the asynchronous operation. - The `SynchronizationContext` or `TaskScheduler` determines where continuations run (e.g., UI thread for GUI apps).
Common Pitfalls: - Deadlocks: Calling `.Result` or `.Wait()` on a `Task` in a context with a `SynchronizationContext` (e.g., UI thread) can cause deadlocks if the task needs the same context to complete. - Overhead: Async state machines add memory and CPU overhead, especially for small tasks. - Missing ConfigureAwait: Not using `ConfigureAwait(false)` in library code can lead to unnecessary context switching, impacting performance. - Swallowing Exceptions: Failing to handle exceptions in async methods can lead to unobserved task exceptions. - Thread Pool Starvation: Excessive use of `Task.Run` for CPU-bound work can exhaust ThreadPool threads.
Mitigation: - Use `ConfigureAwait(false)` in library code to avoid capturing the context. - Avoid blocking calls (`.Result`, `.Wait()`); use `await` consistently. - Handle exceptions with try-catch or `Task.ContinueWith` for fault tolerance. - Use `ValueTask` for high-performance scenarios with likely synchronous completion. - Monitor ThreadPool usage with diagnostics tools like PerfView. Example:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
try
{
string result = await DoWorkAsync().ConfigureAwait(false);
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
// Avoid: Deadlock-prone
// string badResult = DoWorkAsync().Result; // Can deadlock in UI context
}
static async Task DoWorkAsync()
{
await Task.Delay(1000).ConfigureAwait(false); // Simulate async work
return "Completed";
}
}
C#SpanMemoryHigh Performance
Span and Memory are modern .NET types designed for high-performance scenarios, particularly for reducing allocations and improving memory efficiency.
- Span: - Represents a contiguous region of memory (e.g., array segment, stack-allocated memory). - Provides a safe, bounds-checked view over memory without copying. - Role: Used for low-level, performance-critical operations like parsing strings, processing buffers, or working with stack-allocated memory. - Constraints: Cannot be used in async methods or stored in fields due to its stack-only nature (ref struct).
- Memory: - Represents a view over a memory region (e.g., array, string) that can be heap-allocated. - Supports slicing and can be used in async contexts or stored in objects. - Role: Used for scenarios requiring memory management across method boundaries, such as asynchronous I/O or long-lived buffers.
- Differences: - Span is stack-only (ref struct), while Memory is heap-friendly. - Span is more performant for short-lived operations; Memory is versatile for broader use. - Span can be created from Memory via `.Span` but not vice versa.
Use Cases: - Use Span for parsing or processing in tight loops (e.g., JSON parsing). - Use Memory for async I/O or passing memory across methods (e.g., network streams). Example:
using System;
using System.Buffers;
using System.Text;
class Program
{
static void Main()
{
// Span: Parse string without allocation
ReadOnlySpan text = "Hello, World!".AsSpan();
var commaIndex = text.IndexOf(',');
var firstPart = text.Slice(0, commaIndex);
Console.WriteLine(firstPart.ToString()); // Hello
// Memory: Async buffer
var buffer = new byte[1024].AsMemory();
ProcessBuffer(buffer);
}
static void ProcessBuffer(Memory buffer)
{
Span span = buffer.Span;
span[0] = (byte)'A';
Console.WriteLine(Encoding.ASCII.GetString(span.Slice(0, 1))); // A
}
}
C#IEnumerableIQueryableListPerformance
IEnumerable, IQueryable, and List are used for working with collections in C#, but they differ in performance, execution model, and use cases.
- IEnumerable: - Represents an in-memory sequence with deferred (lazy) execution. - Processed using LINQ-to-Objects, which evaluates queries eagerly when enumerated. - Performance: Suitable for small datasets but can be inefficient for complex queries due to in-memory processing. - Usage: Use for in-memory collections (e.g., lists, arrays) or simple transformations.
- IQueryable: - Represents a queryable data source (e.g., database) with deferred execution. - Queries are translated to provider-specific formats (e.g., SQL for EF Core), executed on the data source. - Performance: More efficient for remote data as it minimizes data transfer by pushing queries to the server. - Usage: Use for database queries or remote APIs to leverage server-side optimizations.
- List: - A concrete, in-memory collection with eager execution. - Stores data in a contiguous array, offering fast indexing and modification. - Performance: Fast for direct access and mutations but requires full materialization upfront. - Usage: Use when you need a mutable, in-memory collection or immediate data access.
Key Considerations: - IEnumerable is flexible but can lead to multiple enumerations, impacting performance. - IQueryable optimizes remote queries but requires a compatible provider. - List is best for small, static datasets requiring frequent modifications. Example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
class Program
{
static async Task Main()
{
// List: Eager, in-memory
var list = new List { 1, 2, 3, 4, 5 };
var evenList = list.Where(n => n % 2 == 0).ToList();
Console.WriteLine("List: " + string.Join(", ", evenList)); // 2, 4
// IEnumerable: Lazy, in-memory
IEnumerable numbers = new List { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
Console.WriteLine("IEnumerable: " + string.Join(", ", evenNumbers)); // 2, 4
// IQueryable: Lazy, remote
var dbContext = new MyDbContext();
IQueryable query = dbContext.Users.Where(u => u.Age > 18);
var adults = await query.ToListAsync();
Console.WriteLine("IQueryable: " + adults.Count);
}
}
class MyDbContext : DbContext
{
public DbSet Users { get; set; }
}
class User { public int Age { get; set; } }
C#Value TuplesAnonymous TypesTuples
Value tuples, introduced in C# 7.0, are lightweight, mutable structs for grouping multiple values without defining a class. They are part of the `System.ValueTuple` family.
- Value Tuples: - Syntax: `(int, string)` or `(int Id, string Name)` with named elements. - Mutable, stack-allocated (value type), and lightweight. - Support deconstruction and comparison operators. - Usage: Temporary data grouping, multiple return values from methods.
- Comparison: - Anonymous Types: - Immutable, heap-allocated, compiler-generated classes. - Used in LINQ queries or for ad-hoc data shaping. - Limited to method scope and lack named tuple-like syntax. - Drawback: Cannot be returned from methods or passed across assemblies. - Traditional Tuples (System.Tuple): - Immutable, heap-allocated reference types. - Verbose syntax (e.g., `Tuple.Create`). - Less performant due to heap allocation and lack of deconstruction.
Use Cases: - Use value tuples for multiple return values or lightweight data transfer. - Use anonymous types for LINQ projections within a method. - Avoid traditional tuples in modern C# due to performance and usability issues. Example:
using System;
class Program
{
static void Main()
{
// Value Tuple
(int Id, string Name) valueTuple = (1, "Alice");
Console.WriteLine($"Value Tuple: {valueTuple.Id}, {valueTuple.Name}");
valueTuple.Id = 2; // Mutable
var (id, name) = valueTuple; // Deconstruction
Console.WriteLine($"Deconstructed: {id}, {name}");
// Anonymous Type
var anon = new { Id = 1, Name = "Bob" };
Console.WriteLine($"Anonymous: {anon.Id}, {anon.Name}");
// anon.Id = 2; // Error: Immutable
// Traditional Tuple
var tradTuple = Tuple.Create(1, "Charlie");
Console.WriteLine($"Traditional: {tradTuple.Item1}, {tradTuple.Item2}");
}
static (int, string) GetUser() => (1, "Alice");
}
C#Record TypesClassStruct
Record types, introduced in C# 9.0, are specialized types for immutable data modeling, differing from classes and structs in behavior and use cases.
- Record Types: - Immutable by default (init-only properties), with value-based equality. - Can be reference (`record class`) or value types (`record struct`). - Support `with` expressions for non-destructive mutation and automatic `ToString`, `Equals`, and `GetHashCode` implementations. - Usage: Data transfer objects (DTOs), immutable domain models, or API responses.
- Class: - Reference types, mutable by default, with reference-based equality. - Support inheritance and complex behavior. - Usage: Modeling entities with behavior, stateful services, or objects requiring inheritance.
- Struct: - Value types, mutable by default, with value-based equality. - Lightweight, stack-allocated, no inheritance. - Usage: Small, performance-critical data structures (e.g., coordinates, counters).
Scenarios: - Use records for immutable data (e.g., configuration, DTOs). - Use classes for complex objects with behavior (e.g., services, controllers). - Use structs for small, lightweight data (e.g., Point, DateOnly). Example:
using System;
// Record
public record Person(string Name, int Age)
{
public Person WithNewAge(int newAge) => this with { Age = newAge };
}
// Class
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public void Promote() => Console.WriteLine($"{Name} promoted");
}
// Struct
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y) => (X, Y) = (x, y);
}
class Program
{
static void Main()
{
// Record: Immutable, value equality
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine(p1 == p2); // True
var p3 = p1.WithNewAge(31);
Console.WriteLine(p3.Age); // 31
// Class: Mutable, reference equality
var e1 = new Employee { Name = "Bob", Age = 40 };
var e2 = new Employee { Name = "Bob", Age = 40 };
Console.WriteLine(e1 == e2); // False
e1.Promote(); // Bob promoted
// Struct: Lightweight, value equality
var point1 = new Point(1, 2);
var point2 = new Point(1, 2);
Console.WriteLine(point1.Equals(point2)); // True
}
}
C#Thread SafetyShared Resources
Writing thread-safe code in C# ensures correct behavior in multithreaded environments, particularly when accessing shared resources.
Best Practices: - Minimize Shared State: Prefer immutable objects or local variables to avoid synchronization. - Use Synchronization Primitives: - `lock` for simple critical sections. - `Monitor` for conditional locking or signaling. - `SemaphoreSlim` for resource pools. - `Interlocked` for atomic operations (e.g., increment). - Leverage Concurrent Collections: Use `ConcurrentDictionary`, `ConcurrentQueue`, etc., for thread-safe collections. - Avoid Deadlocks: Acquire locks in a consistent order and use timeouts (e.g., `Monitor.TryEnter`). - Use Task-Based Async: Prefer `async/await` over manual threading for I/O-bound work. - Immutable Data: Use records or `ImmutableList` for thread-safe data sharing. - Test Thoroughly: Stress-test with multiple threads to detect race conditions.
Handling Shared Resources: - Protect shared resources with locks or concurrent collections. - Use `Interlocked` for simple atomic updates. - Monitor with diagnostics tools like Concurrency Visualizer to detect issues.
Use Cases: - Shared caches, counters, or queues in high-concurrency APIs. - Parallel processing of data with shared state. Example:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class Program
{
private static int _counter = 0;
private static readonly object _lock = new object();
private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary();
static async Task Main()
{
// Lock for shared resource
var tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() =>
{
lock (_lock)
{
_counter++;
Console.WriteLine($"Counter: {_counter}");
}
});
}
await Task.WhenAll(tasks);
// Concurrent collection
tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() =>
{
_cache.TryAdd($"key{i}", i);
Console.WriteLine($"Added key{i}");
});
}
await Task.WhenAll(tasks);
Console.WriteLine($"Cache count: {_cache.Count}");
// Interlocked for atomic operations
_counter = 0;
tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() => Interlocked.Increment(ref _counter));
}
await Task.WhenAll(tasks);
Console.WriteLine($"Interlocked Counter: {_counter}"); // 5
}
}
C#CovarianceContravarianceGenerics
Covariance and contravariance enable flexible type conversions in generic interfaces, enhancing polymorphism.
- Covariance: Allows a generic interface to be used with a more derived type than specified (e.g., `IEnumerable` can be treated as `IEnumerable`). Denoted with `out` in C#. - Contravariance: Allows a generic interface to be used with a less derived type (e.g., `IComparer` can be used as `IComparer`). Denoted with `in`.
Constraints: - Covariance applies to output positions (return types), requiring `out`. - Contravariance applies to input positions (parameters), requiring `in`. - Only interfaces and delegates support variance.
Real-World Use Case: - Covariance: Using `IEnumerable` as `IEnumerable` in a pet store app to process all animals. - Contravariance: Using an `IComparer` to compare `Dog` objects, allowing a general comparer for specific types. Example:
using System;
using System.Collections.Generic;
public class Animal { }
public class Dog : Animal { }
// Covariant interface
public interface IReader { T Read(); }
// Contravariant interface
public interface IWriter { void Write(T item); }
public class AnimalReader : IReader
{
public Animal Read() => new Animal();
}
public class DogWriter : IWriter
{
public void Write(Dog item) => Console.WriteLine("Wrote dog");
}
class Program
{
static void Main()
{
// Covariance
IReader dogReader = new AnimalReader(); // AnimalReader produces Animals, safe for Dogs
Animal animal = dogReader.Read(); // Safe
// Contravariance
IWriter animalWriter = new DogWriter(); // DogWriter can write Dogs, safe for Animals
animalWriter.Write(new Dog()); // Wrote dog
// Real-world: IEnumerable (covariant)
IEnumerable dogs = new List { new Dog() };
IEnumerable animals = dogs; // Covariant
Console.WriteLine(animals.Count()); // 1
}
}