C# Programming Fundamentals: Type Inference, Extension Methods, Delegates, Lambda Expressions, and LINQ
Type Inference with var
The var keyword enables type inference for local variables only, not for class members. Variables declared with var must be initialized during declaration and cannot be assigned null values. The var keyword doesn't represent a new data type but serves as syntactic sugar for variables whose types can be inferred at compile time. From the IL perspective, there's no difference between using var and explicit type declarations.
Anonymous Types
Anonymous types allow creating objects without defining a formal class structure:
var personData = new
{
FullName = "John Smith",
YearsOld = 30,
Department = "Engineering"
};
These types are particularly useful for temporary data structures and projections.
Extension Methods
Extension methods must be defined within static classes and are themselves static methods. When a class method conflicts with an extension method name, the class method takes precedence. The first parameter uses the this modifier to indicate which type receives the extension. This parameter represents the instance being extended. Extension methods support overloading but should be used judiciously.
They're especially valuible when extending sealed classes without modifying their source code.
Syntax Examples
public static int CalculateAverage(this int total)
{
return total / 5;
}
public static string FormatGreeting(this string userName)
{
return string.Format("Hello {0}, your average score is: ", userName);
}
public static string WelcomeStudent(this Student student)
{
return "Welcome: " + student.StudentName;
}
public static string WelcomeStudent(this Student student, int years)
{
return string.Format("Name: {0}, Age: {1}", student.StudentName, years);
}
Delegates
Delegates function as special data types that hold references to methods. They operate like function pointers, allowing method references to be stored in variables.
Implementation Steps
- Declare the delegate (typically outside class scope)
- Create methods matching the delegate signature
- Instantiate the delegate with a target method
- Invoke methods through the delegate
- Modify associated methods dynamically using
+=and-=operators
// Delegate declaration
public delegate int ArithmeticOperation(int x, int y);
static int Sum(int x, int y)
{
return x + y;
}
static int Difference(int x, int y)
{
return x - y;
}
static void Execute()
{
ArithmeticOperation calculator = new ArithmeticOperation(Sum);
int result = calculator(15, 25);
Console.WriteLine($"15+25={result}");
calculator -= Sum;
calculator += Difference;
result = calculator(15, 25);
Console.WriteLine($"15-25={result}");
}
Anonymous Methods
Anonymous methods provide inline function definitions without explicit naming:
static void Execute()
{
ArithmeticOperation calculator = delegate(int x, int y)
{
return x + y;
};
int result = calculator(15, 25);
Console.WriteLine($"15+25={result}");
}
Lambda Expressions
Lambda expressions use the => operator for concise functon definitions:
static void Execute()
{
ArithmeticOperation calculator = (int x, int y) => { return x + y; };
int result = calculator(15, 25);
Console.WriteLine($"15+25={result}");
}
static void SquareExample()
{
Func<int, int> square = x => x * x;
int result = square(15);
Console.WriteLine($"Square of x={result}");
}
When parameters are singular, parentheses can be omitted. For single-expression bodies, braces and return statements can be removed.
Language Integrated Query (LINQ)
LINQ variants include:
- LINQ to Objects: Object collection queries
- LINQ to XML: XML document queries
- LINQ to ADO.NET: Database queries
- LINQ to Entities: Modern database integration
Traditional vs LINQ Approach
Without LINQ:
static void TraditionalApproach()
{
int[] numbers = { 1, 7, 2, 6, 5, 4, 9, 13, 20 };
List<int> oddNumbers = new List<int>();
foreach (int item in numbers)
{
if (item % 2 != 0)
oddNumbers.Add(item);
}
oddNumbers.Sort();
oddNumbers.Reverse();
foreach (int item in oddNumbers)
{
Console.WriteLine(item);
}
}
With LINQ:
static void LinqApproach()
{
int[] numbers = { 1, 7, 2, 6, 5, 4, 9, 13, 20 };
var oddNumbers = from num in numbers
where num % 2 != 0
orderby num descending
select num;
foreach (int item in oddNumbers)
{
Console.WriteLine(item);
}
}
LINQ Standard Methods
Select Method
The Select() extension method transforms elements using a delegate:
static void TransformExample()
{
int[] numbers = { 1, 7, 2, 6, 5, 4, 9, 13, 20 };
var squares = numbers.Select(element => element * element);
foreach (int item in squares)
{
Console.WriteLine(item);
}
}
Where Method
The Where() method filters elements based on boolean conditions:
static void FilterExample()
{
int[] numbers = { 1, 7, 2, 6, 5, 4, 9, 13, 20 };
var evenSquares = numbers
.Where(element => element % 2 == 0)
.Select(x => x * x);
}
Ordering Methods
OrderBy() sorts ascending, OrderByDescending() sorts descending:
static void SortExample()
{
int[] numbers = { 1, 7, 2, 6, 5, 4, 9, 13, 20 };
var sortedResult = numbers
.Where(element => element % 2 == 0)
.Select(x => x * x)
.OrderBy(element => element);
}
static void StringSortExample()
{
string[] names = { "Alice", "Bob", "Charlie", "David" };
var orderedNames = names
.Where(element => element.Length == 2)
.OrderByDescending(element => element.Substring(0, 1));
}
GroupBy Method
Groups elements by specified criteria:
static void GroupExample()
{
string[] names = { "Smith", "Johnson", "Williams", "Brown" };
var groupedNames = names
.Where(element => element.Length == 5)
.GroupBy(element => element.Substring(0, 1));
foreach (var group in groupedNames)
{
Console.WriteLine($"Group Key: {group.Key}");
foreach (var item in group)
{
Console.WriteLine(item);
}
}
}
Query Execution
LINQ employs deferred execution - queries execute when enumerated, not when defined. Use aggregation methods like Count() to force immediate execution.
Query Syntax Variants
Method syntax with fluent interfaces:
var result = numbers
.Where(element => element % 2 == 0)
.Select(x => x * x)
.OrderByDescending(x => x);
Query syntax resmebling SQL:
var result = from num in numbers
where num % 2 == 0
orderby num descending
select num * num;
Advanced Query Operations
Aggregation Methods
Count, Max, Min, Average, and Sum provide statistical operations:
var count = students.Where(s => s.Id > 1010).Count();
var maxAge = students.Select(s => s.Age).Max();
var avgAge = students.Select(s => s.Age).Average();
Composite Sorting
ThenBy() provides secondary sorting criteria:
var sortedStudents = students
.OrderBy(s => s.Name)
.ThenBy(s => s.Age)
.ThenBy(s => s.Id);
Sequence Operations
Take(n) retrieves n elements, Skip(n) bypasses n elements:
var subset = numbers.Skip(2).Take(3);
var conditionalTake = numbers.TakeWhile(x => x % 2 != 0);
var conditionalSkip = numbers.SkipWhile(x => x < 5);
Generation Methods
Range() and Repeat() create sequences:
var sequence = Enumerable.Range(1, 10);
var repeated = Enumerable.Repeat("Hello", 5);
Distinct Values
Distinct() removes duplicate elements:
int[] numbers = { 1, 2, 2, 3, 3, 4 };
var unique = numbers.Distinct();