.NET and C# remain dominant in Indian enterprise IT, powering banking, insurance, and ERP systems at IBM, Infosys, Cognizant, TCS, and Wipro. Whether you are a fresher targeting a .NET developer role or an experienced engineer moving into an ASP.NET Core position, this guide covers the C# fundamentals and framework knowledge that Indian interviewers test.
C# Fundamentals: OOP, Value Types, and Async/Await
C# object-oriented programming and language features are the foundation of every .NET interview in India.
Object-oriented programming in C#:
- Classes and objects: a class is the blueprint; an object is an instance. Class members: fields, properties, methods, constructors, events
- Properties: syntactic sugar over getter/setter methods: public string Name { get; set; } or public string Name { get; private set; }
- Inheritance: a class can inherit from one base class (single inheritance). Use : BaseClass
- Polymorphism: virtual methods in the base class can be overridden (override keyword) by derived classes; the correct method is called based on the runtime type
- Abstract class: cannot be instantiated; can have abstract methods (no implementation; derived classes must implement) and concrete methods
- Interface: a contract; defines method signatures without implementation (in C# versions before 8); a class implements an interface using : IMyInterface; supports multiple interfaces
- Sealed class: cannot be inherited: sealed class MyClass { }
- Static class: can only have static members; cannot be instantiated (used for utility/helper classes)
Value types vs reference types:
- Value types: stored on the stack (for local variables); copied when assigned or passed to a method. Examples: int, double, bool, char, struct, enum
- Reference types: stored on the heap; the variable holds a reference (pointer) to the object. Examples: class, string, array, delegate, interface
- Nullable value types: int? salary = null; — the ? suffix makes a value type nullable (wraps in Nullable<T>)
- Boxing: converting a value type to object (reference type) — allocates heap memory; avoid in performance-critical code
- Unboxing: converting the object back to a value type — must match the original type or throws InvalidCastException
Async/Await:
- async marks a method as asynchronous; it must return Task, Task<T>, or void (avoid async void except for event handlers)
- await suspends the current method until the awaited Task completes, without blocking the calling thread
- The await expression unwraps Task<T> and returns T
- Task.Run(() => heavyComputation()) — run CPU-bound work on the thread pool
- await Task.Delay(1000) — asynchronous delay (does not block the thread)
- Task.WhenAll(task1, task2) — await multiple tasks in parallel; throws if any task throws
- Task.WhenAny(task1, task2) — await until the first task completes
- ConfigureAwait(false): by default, await resumes on the original synchronisation context; ConfigureAwait(false) resumes on any thread pool thread (better for library code; avoids deadlocks in ASP.NET classic)
LINQ, Generics, and Exception Handling
LINQ is heavily tested in every .NET interview in India. Interviewers ask you to read, write, and optimise LINQ queries.
LINQ (Language Integrated Query):
- LINQ provides a unified query syntax for in-memory collections (IEnumerable<T>), databases (IQueryable<T> via EF Core), XML (LINQ to XML), and more
- Method syntax (most common in professional code): list.Where(x => x.Age > 25).OrderBy(x => x.Name).Select(x => x.Name).ToList()
- Query syntax (SQL-like): from x in list where x.Age > 25 orderby x.Name select x.Name
- Deferred execution: LINQ queries on IEnumerable<T> and IQueryable<T> are not executed until enumerated (e.g., ToList(), First(), foreach loop)
- Immediate execution methods: ToList(), ToArray(), Count(), First(), Single(), Any(), All(), Sum(), Average(), Min(), Max()
Common LINQ methods:
- Where(x => condition) — filter elements
- Select(x => projection) — transform elements
- SelectMany(x => x.Children) — flatten nested collections
- OrderBy(x => x.Name) / OrderByDescending(x => x.Age) — sort
- ThenBy(x => x.Department) — secondary sort
- GroupBy(x => x.Department) — group elements; returns IGrouping<TKey, TElement>
- Join(other, x => x.Id, y => y.UserId, (x, y) => new { x.Name, y.Order }) — join two collections
- Distinct() — remove duplicates
- Take(10) / Skip(20) — pagination
- First() / FirstOrDefault() — first element (FirstOrDefault returns null/default if empty; First throws)
- Single() / SingleOrDefault() — exactly one element (throws if more than one)
- Any(x => x.IsActive) — true if any element matches
- All(x => x.IsValid) — true if all elements match
- Aggregate(seed, (acc, x) => acc + x.Value) — general fold operation
Generics:
- Generic types: List<T>, Dictionary<TKey, TValue>, IEnumerable<T>
- Generic methods: T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; }
- Constraints: where T : class (reference type), where T : struct (value type), where T : new() (has parameterless constructor), where T : IDisposable
Exception handling:
- try { } catch (SpecificException ex) { } catch (Exception ex) { } finally { }
- Catch the most specific exception first; Exception is the base type and should be last
- finally always executes (even if an exception is thrown or return is called); use for cleanup
- throw; — rethrow the current exception preserving the stack trace (vs throw ex; which resets the stack trace)
- Custom exceptions: inherit from Exception: class PaymentException : Exception { public PaymentException(string message) : base(message) { } }
ASP.NET Core, Entity Framework Core, and Dependency Injection
ASP.NET Core and Entity Framework Core are the most tested framework topics at Indian .NET developer interviews.
.NET Core vs .NET Framework:
- .NET Framework: Windows-only; ships with Windows; not actively developed for new features (only security patches)
- .NET Core / .NET 5+: cross-platform (Windows, Linux, macOS); open-source; better performance; actively developed. Modern .NET development targets .NET 8 or .NET 9
- .NET Standard: a specification that both .NET Framework and .NET Core implemented; used for shared libraries that must run on both
ASP.NET Core:
- Program.cs (minimal hosting model in .NET 6+): configures services (dependency injection container) and the request pipeline (middleware)
- Middleware: components in the request pipeline; each middleware can handle the request and/or pass it to the next middleware. Built-in: UseRouting, UseAuthentication, UseAuthorization, UseStaticFiles
- Controller: a class with actions (methods) that handle HTTP requests; inherits from ControllerBase (Web API) or Controller (MVC with views)
- Routing: [Route("api/[controller]")] attribute on the controller; [HttpGet("{id}")] on the action
- Model binding: ASP.NET Core maps request data to action parameters: [FromBody] (request body), [FromQuery] (query string), [FromRoute] (route parameter), [FromHeader]
- Action result types: Ok(data), NotFound(), BadRequest(ModelState), CreatedAtAction(), NoContent()
- Model validation: [Required], [Range], [MaxLength] data annotations; check ModelState.IsValid in the action
Entity Framework Core:
- DbContext: the unit of work and connection to the database; derive from DbContext and add DbSet<T> properties for each entity
- DbSet<T>: represents a table; supports LINQ queries
- Migrations: code-first schema management; dotnet ef migrations add MigrationName creates a migration; dotnet ef database update applies it
- LINQ with EF Core: queries are translated to SQL; IQueryable<T> queries are sent to the database; call ToListAsync() to execute
- Include(): eager loading of related entities: db.Orders.Include(o => o.Customer).Include(o => o.Items).ToListAsync()
- Tracking vs no-tracking: by default EF Core tracks entities for change detection; use .AsNoTracking() for read-only queries (faster)
- SaveChangesAsync(): applies all pending changes to the database in a single transaction
Dependency Injection:
- Services are registered in Program.cs: builder.Services.AddScoped<IOrderService, OrderService>()
- AddScoped: one instance per HTTP request
- AddTransient: a new instance every time the service is requested
- AddSingleton: one instance for the lifetime of the application
- Constructor injection: declare dependencies in the constructor; the DI container resolves and injects them
- IServiceProvider.GetService<T>(): resolve a service from the container manually (avoid in application code; prefer constructor injection)
- Named options pattern: IOptions<T>, IOptionsSnapshot<T>, IOptionsMonitor<T> for injecting typed configuration
Frequently asked questions
Explore more