Unit 3: Advanced C# Programming - Subjective Questions
CSE253 — .Net Programming • Practice Questions with Detailed Answers
20 questions
Define LINQ in C#. Explain its purpose, major features, and advantages over traditional collection-processing techniques.
Language Integrated Query (LINQ) is a feature of C# that provides a consistent syntax for querying different data sources such as collections, databases, XML documents, and other objects.
Purpose:
- Enables querying data using C# syntax.
- Reduces the need for separate query languages for different data sources.
- Improves readability and maintainability.
- Provides compile-time type checking and IntelliSense support.
Important features:
- Query expressions and method syntax.
- Filtering using
Where(). - Projection using
Select(). - Sorting using
OrderBy()andThenBy(). - Grouping using
GroupBy(). - Aggregation using methods such as
Count(),Sum(),Average(),Min(), andMax(). - Deferred execution for many query operations.
Compared with traditional loops, LINQ makes data-processing logic more declarative because the programmer specifies what data is required rather than describing every processing step.
Explain the difference between LINQ query syntax and LINQ method syntax with suitable C# examples.
LINQ supports two equivalent styles of writing queries.
Query syntax:
csharp
var result = from number in numbers
where number % 2 == 0
orderby number
select number;
Method syntax:
csharp
var result = numbers
.Where(number => number % 2 == 0)
.OrderBy(number => number);
Differences:
- Query syntax resembles SQL and can be easier to read for complex queries.
- Method syntax uses extension methods and lambda expressions.
- Some operations, such as
Count(),Sum(), andFirst(), are available directly through method syntax. - The C# compiler translates query syntax into method calls.
- Both forms generally produce the same result and can be combined in one query.
Method syntax is often preferred when queries contain many chained operations, while query syntax can be clearer for joins and grouped queries.
Describe how LINQ can be used with collections. Write a C# example that filters, transforms, and sorts a collection of objects.
LINQ to Collections is used with in-memory data sources such as arrays, lists, and other types implementing IEnumerable<T>. It allows developers to perform filtering, projection, and sorting without writing multiple loops.
class Student
{
public string Name { get; set; }
public int Marks { get; set; }
}
List<Student> students = new List<Student>
{
new Student { Name = "Anita", Marks = 82 },
new Student { Name = "Bharat", Marks = 65 },
new Student { Name = "Chetan", Marks = 91 }
};
var result = students
.Where(student => student.Marks >= 70)
.OrderByDescending(student => student.Marks)
.Select(student => new
{
student.Name,
Grade = student.Marks >= 85 ? "A" : "B"
});In this example:
Where()selects students with marks of at least 70.OrderByDescending()sorts them from highest to lowest marks.Select()creates a new projected object containing the name and grade.
The query executes when it is enumerated, for example by a foreach loop or ToList().
Explain deferred execution and immediate execution in LINQ. Discuss their effects with an example.
In deferred execution, a LINQ query is defined but not executed immediately. It executes when the result is enumerated, such as during a foreach loop.
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1);
numbers.Add(4);
foreach (int number in query)
{
Console.WriteLine(number);
}The output includes 2, 3, and 4 because the query executes after the new value is added.
Immediate execution forces the query to run immediately and stores its result:
var result = numbers.Where(n => n > 1).ToList();Common immediate-execution methods include ToList(), ToArray(), Count(), First(), Single(), Sum(), and Average().
Advantages of deferred execution:
- Avoids unnecessary processing.
- Uses current source data when enumerated.
- Supports efficient query composition.
Advantages of immediate execution:
- Creates a stable snapshot of the results.
- Prevents repeated execution.
- Can avoid exceptions caused by changes to the source during enumeration.
What is a delegate in C#? Explain its declaration, instantiation, invocation, and use as a callback with an example.
A delegate is a type-safe reference to a method. It defines the method signature that a referenced method must follow. Delegates are useful for callbacks, event handling, and passing methods as parameters.
delegate int Operation(int first, int second);
class Calculator
{
public static int Add(int a, int b)
{
return a + b;
}
}
Operation operation = Calculator.Add;
int result = operation(10, 5);In this example:
Operationis a delegate type.- The delegate accepts two integers and returns an integer.
Calculator.Addmatches the delegate signature.operation(10, 5)invokes the referenced method.
A delegate can also be passed to another method:
static void Execute(Operation operation)
{
Console.WriteLine(operation(4, 3));
}Delegates provide flexibility because the receiving method can execute different behaviors without knowing the exact implementation in advance. Built-in generic delegates include Action, Func<T>, and Predicate<T>.
Distinguish between delegates and events in C#. Explain how events are declared and handled using a suitable example.
Delegates and events are related but serve different purposes.
Delegates:
- Store references to methods.
- Can be invoked by any code that has access to them.
- Can be passed as parameters and returned from methods.
- Are suitable for callbacks and strategy selection.
Events:
- Provide a controlled notification mechanism.
- Are based on delegates.
- Can be raised only from within the class that declares them.
- External classes can subscribe or unsubscribe, but cannot directly raise the event.
class Alarm
{
public event EventHandler AlarmRaised;
public void Trigger()
{
AlarmRaised?.Invoke(this, EventArgs.Empty);
}
}
class Program
{
static void Main()
{
Alarm alarm = new Alarm();
alarm.AlarmRaised += OnAlarmRaised;
alarm.Trigger();
alarm.AlarmRaised -= OnAlarmRaised;
}
static void OnAlarmRaised(object sender, EventArgs e)
{
Console.WriteLine("Alarm received");
}
}Events improve encapsulation by ensuring that subscribers receive notifications without being able to trigger the event themselves.
Define an interface in C#. Explain how interfaces support abstraction, multiple inheritance of behavior contracts, and loose coupling.
An interface is a contract that specifies members a class or structure must implement. It commonly contains method, property, event, and indexer declarations.
interface IPrintable
{
void Print();
}
class Invoice : IPrintable
{
public void Print()
{
Console.WriteLine("Printing invoice");
}
}Benefits of interfaces:
- Abstraction: Clients depend on required behavior rather than implementation details.
- Loose coupling: A class can use an interface without depending on a specific concrete class.
- Multiple contracts: A class can implement multiple interfaces, which provides a form of multiple inheritance of behavior contracts.
- Testability: Mock or fake implementations can be supplied during testing.
- Polymorphism: Different classes can be used through the same interface reference.
class Report : IPrintable
{
public void Print() { }
}
IPrintable document = new Report();
document.Print();The client code depends only on IPrintable, so the concrete implementation can be replaced without changing the client.
Explain generics in C#. Discuss generic classes, methods, type safety, and performance benefits with examples.
Generics allow classes, methods, interfaces, and delegates to work with a type specified by the caller. They provide reusable code while preserving compile-time type safety.
Generic class:
csharp
class Box<T>
{
public T Value { get; set; }
public Box(T value)
{
Value = value;
}
}
Box<int> integerBox = new Box<int>(10);
Box<string> textBox = new Box<string>("Hello");
Generic method:
csharp
static void Display<T>(T value)
{
Console.WriteLine(value);
}
Advantages:
- The same implementation works with different data types.
- Type errors are detected at compile time.
- Generic code reduces casting.
- Value types can be used without unnecessary boxing and unboxing.
- It improves performance and clarity compared with non-generic collections.
Common generic types include List<T>, Dictionary<TKey, TValue>, Queue<T>, and Stack<T>. Constraints such as where T : class or where T : new() can restrict acceptable type arguments.
Compare synchronous and asynchronous programming in C#. Explain the role of Task, async, and await.
In synchronous programming, operations execute sequentially. A long-running operation blocks the current thread until it finishes. In asynchronous programming, the program can continue other work while waiting for an input/output operation to complete.
Task represents an operation that may complete in the future. async identifies a method that performs asynchronous work, and await asynchronously waits for a task without blocking the current thread.
public async Task<string> DownloadDataAsync(string url)
{
using HttpClient client = new HttpClient();
string data = await client.GetStringAsync(url);
return data;
}Important points:
Taskrepresents a non-generic asynchronous operation.Task<T>represents an operation that returns a value of typeT.- An
asyncmethod can returnTask,Task<T>, orValueTask<T>in appropriate cases. awaitsuspends the method until completion and then resumes it.- Asynchrony is especially useful for network, file, database, and user-interface operations.
Asynchronous programming improves responsiveness and scalability, but it does not automatically make CPU-bound work faster.
Write and explain an asynchronous C# method that performs two independent operations concurrently and returns both results.
Independent asynchronous operations can be started before awaiting either one. This allows their waiting periods to overlap.
public async Task<(string User, string Orders)> LoadDataAsync()
{
Task<string> userTask = GetUserAsync();
Task<string> ordersTask = GetOrdersAsync();
await Task.WhenAll(userTask, ordersTask);
return (userTask.Result, ordersTask.Result);
}A safer alternative is to await the tasks after Task.WhenAll():
public async Task<(string User, string Orders)> LoadDataAsync()
{
Task<string> userTask = GetUserAsync();
Task<string> ordersTask = GetOrdersAsync();
await Task.WhenAll(userTask, ordersTask);
return (await userTask, await ordersTask);
}Explanation:
- Both tasks begin before the method waits for completion.
Task.WhenAll()completes when every supplied task completes.- The method returns a tuple containing both results.
- If a task fails, the combined task becomes faulted and the exception must be handled by the caller.
This approach is more efficient than awaiting the first operation completely before starting the second when the operations are independent.
Explain exception handling and cancellation in asynchronous C# programming. Show how CancellationToken can be used.
Asynchronous operations should handle exceptions and cancellation explicitly. Exceptions from an awaited task are rethrown at the await expression and can be handled with try-catch.
public async Task ProcessAsync(CancellationToken token)
{
try
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(500, token);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation cancelled");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}Cancellation principles:
- A
CancellationTokenSourcecreates and controls cancellation. - Its token is passed to the asynchronous operation.
- The operation checks the token periodically or passes it to cancellable APIs.
ThrowIfCancellationRequested()stops execution by throwingOperationCanceledException.- Cancellation is cooperative; it does not forcibly terminate a thread.
Correct cancellation prevents wasted work and allows applications to respond to user requests, timeouts, and shutdown signals.
State the Single Responsibility Principle (SRP). Identify an SRP violation and show how it can be refactored in C#.
The Single Responsibility Principle (SRP) states that a class should have one responsibility and only one reason to change. A responsibility represents a closely related area of functionality owned by the class.
An SRP violation might place calculation, persistence, and presentation in one class:
class Invoice
{
public decimal CalculateTotal() { return 0; }
public void SaveToDatabase() { }
public void Print() { }
}This class may change because the billing rules, database technology, or printing format changes.
A refactored design separates these responsibilities:
class Invoice
{
public decimal CalculateTotal() { return 0; }
}
class InvoiceRepository
{
public void Save(Invoice invoice) { }
}
class InvoicePrinter
{
public void Print(Invoice invoice) { }
}Benefits:
- Classes become easier to understand and test.
- Changes have a smaller impact.
- Reuse improves because each class has a focused purpose.
- Dependencies can be replaced independently.
Explain the Open/Closed Principle (OCP) with a C# example. How does polymorphism help a design remain open for extension but closed for modification?
The Open/Closed Principle states that software entities should be open for extension but closed for modification. New behavior should be added without repeatedly changing stable, existing code.
A design based on a type-checking conditional is difficult to extend:
class DiscountCalculator
{
public decimal Calculate(string customerType, decimal amount)
{
if (customerType == "Regular") return amount * 0.95m;
if (customerType == "Premium") return amount * 0.90m;
return amount;
}
}Adding every new customer type requires modifying the method. Polymorphism provides a better design:
interface IDiscountPolicy
{
decimal Apply(decimal amount);
}
class PremiumDiscount : IDiscountPolicy
{
public decimal Apply(decimal amount) => amount * 0.90m;
}The calculator can depend on IDiscountPolicy, and new policies can be added as new classes. Existing calculator code remains unchanged. This reduces regression risk and separates each variation into its own implementation.
Define the Liskov Substitution Principle (LSP). Explain how an incorrect inheritance relationship can violate it.
The Liskov Substitution Principle states that objects of a derived class must be usable wherever objects of the base class are expected without changing the correctness of the program.
A classic violation occurs when a derived class cannot support behavior promised by its base class:
class Bird
{
public virtual void Fly() { }
}
class Penguin : Bird
{
public override void Fly()
{
throw new NotSupportedException();
}
}Code expecting every Bird to fly will fail when given a Penguin. The inheritance relationship incorrectly assumes that flying is common to all birds.
A better design separates capabilities:
class Bird { }
interface IFlyingBird
{
void Fly();
}
class Eagle : Bird, IFlyingBird
{
public void Fly() { }
}
class Penguin : Bird { }LSP violations often appear through unexpected exceptions, ignored methods, invalid return values, or strengthened preconditions. Correct abstractions ensure that derived types preserve the behavioral contract of their base types.
What is the Interface Segregation Principle (ISP)? Demonstrate how a large interface can be divided into smaller, client-specific interfaces.
The Interface Segregation Principle states that clients should not be forced to depend on methods they do not use. It encourages small, focused interfaces instead of one large interface containing unrelated operations.
A problematic interface might be:
interface IMachine
{
void Print();
void Scan();
void Fax();
}A simple printer would be forced to implement scanning and faxing. The interface can be segregated:
interface IPrinter
{
void Print();
}
interface IScanner
{
void Scan();
}
interface IFax
{
void Fax();
}
class BasicPrinter : IPrinter
{
public void Print() { }
}
class OfficeMachine : IPrinter, IScanner, IFax
{
public void Print() { }
public void Scan() { }
public void Fax() { }
}Benefits:
- Classes implement only relevant behavior.
- Interfaces are easier to understand and test.
- Changes to one capability affect fewer clients.
- The design becomes more flexible and composable.
Explain the Dependency Inversion Principle (DIP). Show how dependency injection can remove a high-level module's dependency on a concrete class.
The Dependency Inversion Principle has two parts:
- High-level modules should not depend directly on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
A tightly coupled design directly creates a concrete repository:
class OrderService
{
private readonly SqlOrderRepository repository = new SqlOrderRepository();
}This makes testing and replacement difficult. Applying DIP introduces an abstraction:
interface IOrderRepository
{
void Save(Order order);
}
class SqlOrderRepository : IOrderRepository
{
public void Save(Order order) { }
}
class OrderService
{
private readonly IOrderRepository repository;
public OrderService(IOrderRepository repository)
{
this.repository = repository;
}
public void PlaceOrder(Order order)
{
repository.Save(order);
}
}The repository is supplied through constructor injection. OrderService depends on IOrderRepository, so a database implementation, in-memory fake, or mock can be supplied without modifying the service.
Compare the five SOLID principles and explain how they collectively improve the design of C# applications.
SOLID is a group of object-oriented design principles that promote maintainable and extensible software.
- Single Responsibility Principle: A class should have one responsibility and one reason to change.
- Open/Closed Principle: Existing code should be extendable without frequent modification.
- Liskov Substitution Principle: Derived objects must preserve the contracts of their base types.
- Interface Segregation Principle: Clients should depend only on focused interfaces relevant to them.
- Dependency Inversion Principle: High-level code and low-level code should depend on abstractions.
Collective benefits:
- Reduced coupling between components.
- Higher cohesion within classes and modules.
- Easier unit testing through replaceable dependencies.
- Safer extension of existing behavior.
- Smaller and more understandable interfaces.
- Fewer unintended effects when requirements change.
SOLID principles are guidelines rather than rigid rules. Applying them requires balancing flexibility with simplicity so that the design does not become unnecessarily complicated.
Explain lambda expressions in C# and describe their relationship with delegates and LINQ.
A lambda expression is an anonymous function that can be assigned to a delegate or expression tree. Its basic form is (parameters) => expression.
Func<int, int> square = number => number * number;
Console.WriteLine(square(5));A statement-bodied lambda can contain multiple statements:
Action<string> display = message =>
{
Console.WriteLine(message);
};Lambda expressions are closely related to delegates because a lambda can be converted to a compatible delegate type such as Func<T>, Action<T>, or a custom delegate.
LINQ uses lambdas to express conditions and projections:
var names = students
.Where(student => student.Marks >= 60)
.Select(student => student.Name);Here, the lambda passed to Where() defines a predicate, and the lambda passed to Select() defines a transformation. Lambdas make LINQ queries concise and support functional-style operations such as filtering, mapping, and sorting.
Describe grouping, joining, and aggregation in LINQ. Provide examples using collections.
LINQ provides operators for organizing related data and calculating summary values.
Grouping:
csharp
var groups = students.GroupBy(student => student.Department);
This creates groups in which each group contains students from the same department.
Aggregation:
csharp
int total = students.Sum(student => student.Marks);
double average = students.Average(student => student.Marks);
int count = students.Count(student => student.Marks >= 50);
Aggregation operators calculate a single result from a collection.
Joining:
csharp
var result = from student in students
join department in departments
on student.DepartmentId equals department.Id
select new
{
student.Name,
DepartmentName = department.Name
};
A join combines related elements from two sequences using matching key values.
Other useful operators include GroupJoin(), Aggregate(), Min(), Max(), and LongCount(). Grouping and joining are useful for reports, while aggregation is useful for totals, averages, counts, and other summaries.
Differentiate between IEnumerable<T> and IQueryable<T> in the context of LINQ. When is each one appropriate?
IEnumerable<T> and IQueryable<T> both support LINQ queries, but they are intended for different execution environments.
IEnumerable<T>:
- Used primarily for in-memory collections.
- Uses delegates and compiled .NET code to evaluate operations.
- Suitable for arrays, lists, and objects already loaded into memory.
- Usually performs filtering after the data has been retrieved.
IQueryable<T>:
- Used for remote data sources such as relational databases.
- Builds an expression tree representing the query.
- A provider can translate the expression tree into a source-specific language such as SQL.
- Allows filtering and projection to occur at the data source, reducing transferred data.
Example:
IEnumerable<Student> memoryQuery = studentList
.Where(student => student.Marks > 70);
IQueryable<Student> databaseQuery = dbContext.Students
.Where(student => student.Marks > 70);Use IEnumerable<T> when the source is in memory and IQueryable<T> when the provider can efficiently execute the query remotely. Provider limitations and translation behavior should be considered with IQueryable<T>.
Define LINQ in C#. Explain its purpose, major features, and advantages over traditional collection-processing techniques.
Language Integrated Query (LINQ) is a feature of C# that provides a consistent syntax for querying different data sources such as collections, databases, XML documents, and other objects.
Purpose:
- Enables querying data using C# syntax.
- Reduces the need for separate query languages for different data sources.
- Improves readability and maintainability.
- Provides compile-time type checking and IntelliSense support.
Important features:
- Query expressions and method syntax.
- Filtering using
Where(). - Projection using
Select(). - Sorting using
OrderBy()andThenBy(). - Grouping using
GroupBy(). - Aggregation using methods such as
Count(),Sum(),Average(),Min(), andMax(). - Deferred execution for many query operations.
Compared with traditional loops, LINQ makes data-processing logic more declarative because the programmer specifies what data is required rather than describing every processing step.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →