22
DecTop 20 C# Features Every .NET Developer Must Know
Features of C#
C# is a widely used programming language developed by Microsoft .NET platform for building applications like desktop software, web services, and mobile apps. What makes it popular are the C# features that make coding easier, faster, and more secure. The features of C# help developers solve common coding challenges and write clean, efficient code. By understanding these C# features, you'll be able to create better programs and fully use the power of the C# language.
This C# tutorial provides complete guidance to understand the features of C#, including Generic, Partial Class, LINQ: Language Integrated Query, Dynamic Type, Async-Await in C#, and many more. By learning these features of C#, you can enhance your programming capability. If you are a beginner, this C# Developer Roadmap explains how to become a developer in the C# language.
Top 20 Features of C#
We will discuss here the top 20 essential features of C# that help you in your programming language. That is:
1. Generics
The idea of type parameters is brought to .NET via generics, enabling the design of classes and methods that postpone defining one or more types until the class or function is defined and created by client code. It is commonly used to create collection classes. The .NET class library contains several generic collection classes in the System.Collections.Generic namespace. You can create your generic interfaces, classes, methods, events, and delegates.
Example
using System;
namespace GenericExample
{
// Generic class
public class GenericBox<T>
{
private T _value;
public void SetValue(T value)
{
_value = value;
}
public T GetValue()
{
return _value;
}
}
// Class with a generic method
public class GenericMethodExample
{
public void PrintValue<T>(T value)
{
Console.WriteLine(value);
}
}
class Program
{
static void Main(string[] args)
{
// Using the generic class
GenericBox<int> intBox = new GenericBox<int>();
intBox.SetValue(123);
Console.WriteLine("Value in intBox: " + intBox.GetValue()); // Output: 123
GenericBox<string> strBox = new GenericBox<string>();
strBox.SetValue("Hello, Generics!");
Console.WriteLine("Value in strBox: " + strBox.GetValue()); // Output: Hello, Generics!
// Using the generic method
GenericMethodExample example = new GenericMethodExample();
example.PrintValue(42); // Output: 42
example.PrintValue("Generics in C#"); // Output: Generics in C#
}
}
}
Output
Value in intBox: 123
Value in strBox: Hello, Generics!
42
Generics in C#
2. Partial Class
This feature was introduced in C# 2.0. To split a class definition across multiple files, use the partial keyword modifier. When working with an automatically generated source, code can be added to the class without having to recreate the source file.
The partial keyword indicates that other parts of the class, structure, or interface can be defined in the namespace. All the parts must use the partial keyword. All the parts must be available at compile time to form the final type. All the parts must have the same accessibility, such as public, private, and so on.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Hello
{
public partial class Coords
{
private int x;
private int y;
public Coords(int x, int y)
{
this.x = x;
this.y = y;
}
}
public partial class Coords
{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}
class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
In the code above, one partial class definition contains the properties and constructors of the class Coords, while another partial class definition has the member PrintCoords.
Output
Coords: 10,15
3. LINQ: Language Integrated Query
It was introduced in the C# 3.0 version. Allows to query various data sources like C# collection, SQL, and XML-like data using common query syntax.
Example
LINQ can be used to query in-memory objects and collections. It provides a set of standard query operators that operate on collections implementing IEnumerable>
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// LINQ query to filter even numbers
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
foreach (var evenNumber in evenNumbers)
{
Console.WriteLine(evenNumber);
}
}
}
Output
2
4
4. Lambda Expressions
A C# Lambda Expression is a brief code block that takes two arguments and outputs one. The function in question is specified as anonymous or function-name-free. In C# 3.0, it was first shown. Provides a concise way to write inline expressions or anonymous methods. They are often used with LINQ queries or as a convenient way to define delegates or event handlers.
Syntax
(parameterList) => lambda body
Here,
- parameterList - list of input parameters
- => - a lambda operator
- lambda body - can be an expression or statement
Example- Simple Delegate Code Using Lambda Expression
using System;
class Program
{
static void Main()
{
// delegate using lambda expression
Func<int, int> square = num => num * num;
// calling square() delegate
Console.WriteLine(square(7));
}
}
In the above code, we don't need to define a separate method. We have replaced the pointer to the square() method with the lambda expression.
Output
49
5. Extension Methods
In C# 3.0, this functionality was added. In the event that the class's source code is unavailable or we are not authorized to make changes to it, it enables us to enhance its functionality in the future by adding new methods to the class without altering its source code.
Example
using System;
namespace ExtensionMethodsDemo
{
// Step 1: Create a static class for extension methods
public static class StringExtensions
{
// Step 2: Create a static method with 'this' keyword
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
}
class Program
{
static void Main(string[] args)
{
string testString = null;
// Using the extension method
if (testString.IsNullOrEmpty())
{
Console.WriteLine("The string is null or empty.");
}
else
{
Console.WriteLine("The string has a value.");
}
}
}
}
Output
The string is null or empty.
6. Dynamic Type
The Dynamic Type is introduced as part of C# 4 to write dynamic code in C#. It defers type checking from compile time to runtime. Method calls and property accesses are resolved at runtime, which can lead to performance overhead. It is advantageous when you want to avoid typecasting and interacting with dynamic languages like Pythonand JavaScript.
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
dynamic dynamicVar = 10;
Console.WriteLine(dynamicVar);
dynamicVar = "Hello, World!";
Console.WriteLine(dynamicVar);
dynamicVar = new List<int> { 1, 2, 3 };
Console.WriteLine(dynamicVar.Count);
}
}
Output
10
Hello, World!
3
7. Async/Await
Async and Await, introduced in C# 5.0, are the code markers that mark code positions from where the control should resume after completing a task. It helps to write asynchronous code, which is essential for non-blocking UI and server applications.
Example
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting the async operation...");
// Call the async method and wait for it to complete
await PerformLongOperation();
Console.WriteLine("Operation completed!");
}
// An async method that simulates a long-running operation
public static async Task PerformLongOperation()
{
Console.WriteLine("Performing a long operation...");
// Simulate a delay (e.g., long calculation, file processing, etc.)
await Task.Delay(3000); // Wait for 3 seconds
Console.WriteLine("Long operation finished!");
}
}
Output
Starting the async operation...
Performing a long operation...
Long operation finished!
Operation completed!
The code above calls from the Main function and does not rely on either Method1 or Method2. It is evident that there is no delay between Methods 1 and 2.
8. String Interpolation
It was first available in C# 6.0. It enables you to easily insert variables and expressions inside string literals. Compared to traditional string concatenation or the String. The format method provides a concise and more readable way to create formatted strings.
Example
using System;
class Program
{
static void Main()
{
string name = "ScholarHat";
int age = 2;
// String interpolation using $""
string message = $"Hello, my name is {name} and I am {age} years old.";
Console.WriteLine(message);
}
}
Output
Hello, my name is ScholarHat and I am 2 years old.
9. Expression-Bodied Members
Introduced in C# 6.0, expression-bodied members are a syntactic shortcut. With lambda-like syntax, they let you create succinct one-liner methods, properties, and other members. They can be used for methods, properties, indexers, and event accessors.
Example
using System;
class MyClass
{
// Expression-bodied method
public int Add(int x, int y) => x + y;
static void Main()
{
MyClass myObject = new MyClass();
int result = myObject.Add(5, 7);
Console.WriteLine(result);
}
}
Output
12
10. Auto-Property Initializers
In C# 6.0, they were first introduced. One technique to initialize the value of an auto-implemented property right within the property declaration is to use auto-property initializers. This makes the syntax for setting a property's default value simpler.
Using auto-property initializers saves space and eliminates the need to initialize properties in the constructor. They are a convenient approach to provide default values for properties.
Example
using System;
public class MyClass
{
// Auto-property initializer
public int MyProperty { get; set; } = 42;
static void Main()
{
MyClass myObject = new MyClass();
Console.WriteLine(myObject.MyProperty); // Output: 42
}
}
Output
42
11. Tuples and Deconstruction
Tuples allow the grouping of multiple values in a single object without creating a custom class. Deconstruction helps to unpack tuple values into separate variables.
Also Read: Tuples in Python with Examples - A Beginner Guide |
Example
using System;
class Program
{
static void Main(string[] args)
{
// Creating a tuple with multiple types
(int id, string name, double salary) employee = (1, "Ankita", 50000.75);
// Accessing tuple elements by position
Console.WriteLine($"ID: {employee.Item1}, Name: {employee.Item2}, Salary: {employee.Item3}");
// Deconstructing tuple into individual variables
(int id, string name, double salary) = employee;
Console.WriteLine($"ID: {id}, Name: {name}, Salary: {salary}");
}
}
Output
ID: 1, Name: Ankita, Salary: 50000.75
ID: 1, Name: Ankita, Salary: 50000.75
12. Pattern Matching
Introduced in C# 7.0, pattern matching is a feature that lets you verify the structure or form of a value directly in code. Streamlining the syntax for frequent type checks and extractions reduces boilerplate code and improves code readability.
Example of Type Pattern
using System;
class Program
{
static void Main(string[] args)
{
object obj1 = 42;
object obj2 = "Hello, World!";
object obj3 = new Point(5, 10);
object obj4 = null;
// Type Pattern
PrintObjectType(obj1);
PrintObjectType(obj2);
PrintObjectType(obj3);
PrintObjectType(obj4);
}
static void PrintObjectType(object obj)
{
// Using the 'is' operator with pattern matching
switch (obj)
{
case int i:
Console.WriteLine($"Integer: {i}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
case Point p when p.X > 0 && p.Y > 0:
Console.WriteLine($"Point in the positive quadrant: ({p.X}, {p.Y})");
break;
case Point p:
Console.WriteLine($"Point: ({p.X}, {p.Y})");
break;
case null:
Console.WriteLine("Null object.");
break;
default:
Console.WriteLine("Unknown type.");
break;
}
}
}
// Sample class for demonstrating property pattern matching
public class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Output
Integer: 42
String: Hello, World!
Point in the positive quadrant: (5, 10)
Null object.
13. Nullable Reference Types
Introduced in C# 8.0, Nullable Reference Types provide annotations to the type system that indicate whether or not a reference type can be null, enabling developers to construct more secure and reliable code. It enables you to explicitly state your intentions regarding nullability in the code, and it enables the compiler to flag possible null-reference problems with warnings.
Example
#nullable enable // Enable nullable reference types
using System;
class Program
{
static void Main(string[] args)
{
string? name = null; // Nullable reference type
string nonNullableName = "Alice"; // Non-nullable reference type
PrintName(name); // This may lead to a warning
PrintName(nonNullableName);
}
static void PrintName(string? name)
{
// Use the null-coalescing operator to provide a default value
Console.WriteLine(name ?? "Name is null");
}
}
Output
Name is null
Alice
14. Default Interface Methods
Introduced in C# 8.0, the methods allow you to provide a default implementation for methods in an interface. This feature helps maintain backward compatibility when introducing new methods to an interface without requiring all implementing classes to provide an implementation. It is useful when you want to extend existing interfaces without breaking existing implementations.
Example
using System;
public interface IShape
{
void Draw();
// Default method with an implementation (supported in C# 8 and above)
void Display()
{
Console.WriteLine("Default Display Implementation");
}
}
public class Circle : IShape
{
public void Draw()
{
Console.WriteLine("Drawing a circle");
}
// Circle will use the default Display implementation from the interface
}
public class Square : IShape
{
public void Draw()
{
Console.WriteLine("Drawing a square");
}
// Custom implementation for Display in Square class
public void Display()
{
Console.WriteLine("Custom Display Implementation for Square");
}
}
class Program
{
static void Main()
{
IShape circle = new Circle();
IShape square = new Square();
circle.Draw();
circle.Display(); // Calls the default implementation from the interface
square.Draw();
square.Display(); // Calls the custom implementation in Square class
}
}
Output
Drawing a circle
Default Display Implementation
Drawing a square
Custom Display Implementation for Square
15. Record Types
The record types feature was introduced in C# 9.0 to provide a concise way to declare immutable types. Records simplify the process of creating and working with immutable classes by automatically generating common methods like Equals, GetHashCode, and ToString. They are particularly useful for modeling data transfer objects (DTOs) and other types where immutability and value equality are essential.
We can't directly run a record-type feature application program. To run them, we should follow some required steps:
1. Create a New Console Application
- You can create a new console application to run the program using the .NET CLI (Command Line Interface).
- Open a terminal (or command prompt) and run the following commands:
dotnet new console -n RecordExample
cd RecordExample
2. Write the Code
Open the Program.cs file located in the RecordExample folder and replace the existing code with the following code that demonstrates the use of a record:
using System;
public record Student(string Name, int Age);
class Program
{
static void Main(string[] args)
{
var student1 = new Student("Ankita", 22);
var student2 = new Student("Ankita", 22);
Console.WriteLine($"Student 1: {student1.Name}, Age: {student1.Age}");
Console.WriteLine($"Student 2: {student2.Name}, Age: {student2.Age}");
// Check if the two records are equal
Console.WriteLine($"Are the two students equal? {student1 == student2}"); // Output: True
}
}
3. Run the Program
In the terminal (or command prompt), navigate to the folder where your project is located and run:
dotnet run
4. Expected Output
Student 1: Ankita, Age: 22
Student 2: Ankita, Age: 22
Are the two students equal? True
16. Top-Level Statements
Introduced in C# 9.0, it allows you to write simpler C# programs by omitting the traditional Main method and placing the program logic directly at the top level of the file. It simplifies the structure of simple programs by reducing boilerplate code.
using System;
Console.WriteLine("Hello, ScholarHat#!");
int result = Add(5, 7);
Console.WriteLine($"Result of addition: {result}");
// A simple function using top-level statement
int Add(int a, int b) => a + b;
Output
Hello, ScholarHat#!
Result of addition: 12
17. Global Using Directives
The global using directives feature was introduced in C# 10.0. It allows you to specify a set of directives that will be applied globally to all files in a project without the need to include them explicitly in every file. This can help reduce the boilerplate code in your files and provide a more consistent and simplified coding experience.
We can't directly run a record-type feature application program. To run them, we should follow some required steps:
1. Create a New Console Project
First, we will create a new console application by following the commands after open the Visual Studio.
dotnet new console -n GlobalUsingExample
cd GlobalUsingExample
2. Create a File for Global Using Directives
In the GlobalUsingExample folder, create a new file called GlobalUsings.cs (this file can have any name, but it’s typically named this way for clarity).
global using System;
global using System.Collections.Generic;
3. Write the Code
Now, open the Program.cs file and write the main logic without repeating the using System or other directives:
class Program
{
static void Main(string[] args)
{
var names = new List { "Ankita", "Amit", "Rahul" };
foreach (var name in names)
{
Console.WriteLine(name);
}
}
}
We use the 'dotnet run' command to run the program:
Expected Output
Ankita
Amit
Rahul
18. List Patterns
List patterns in C# are a type of pattern introduced in C# 9.0 to match elements of a list or array succinctly and expressively. They provide a concise way to destructure and match the elements of a list or array.
Example
using System;
using System.Collections.Generic;
// Define a Person class
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Define an Animal class
public class Animal
{
public string Species { get; set; }
}
// Main Program
public class Program
{
// Method to describe an object using pattern matching
public static string Describe(object obj)
{
return obj switch
{
int i => $"This is an integer: {i}",
string s when s.Length > 0 => $"This is a non-empty string: {s}",
string => "This is an empty string.",
Person { Age: 30, Name: "John" } => "This is John, 30 years old.",
Person p => $"This is {p.Name}, {p.Age} years old.",
Animal { Species: "Dog" } => "This is a Dog.",
null => "This is null.",
_ => "Unknown type"
};
}
// Main method
public static void Main(string[] args)
{
// Create instances of Person and Animal
var john = new Person { Name = "John", Age = 30 };
var dog = new Animal { Species = "Dog" };
// Test various cases
var results = new List<object>
{
42,
"Hello, world!",
" ",
john,
dog,
null,
3.14 // Unknown type
};
// Describe each object
foreach (var item in results)
{
Console.WriteLine(Describe(item));
}
}
}
Output
This is an integer: 42
This is a non-empty string: Hello, world!
This is a non-empty string:
This is John, 30 years old.
This is a Dog.
This is null.
Unknown type
19. 'required' Modifier
Whenever a class is declared with a property or field with the required keyword, the caller is forced to initialize in the object initializer scope. It was introduced in C# 11.
We can't directly run the required modifier program in our compiler. You have to run this on Visual Studio. You have to follow these steps:
1. Create a Console Application
First, we should create a console application by following these commands:
dotnet new console -n RequiredModifierExample
2. Navigate to the project folder
Second, we navigate to the folder by the command:
cd RequiredModifierExample
Write the Code
- Open the project folder using a code editor (like Visual Studio Code or any other text editor).
- Find the Program.cs file and replace the content with the following code that demonstrates the required modifier
public class Student
{
// Required properties
public required string Name { get; init; }
public required int Age { get; init; }
// Optional property with a default value
public string Course { get; set; } = "BCA";
}
class Program
{
static void Main(string[] args)
{
// Object initialization with required properties
var student = new Student
{
Name = "Ankita", // Required property
Age = 22 // Required property
// Course is optional and defaults to "BCA"
};
// Output the details of the student
Console.WriteLine($"Name: {student.Name}, Age: {student.Age}, Course: {student.Course}");
}
}
The possible output will be generated after building the project and running the above program.
Expected Output
Name: Ankita, Age: 22, Course: BCA
20. Collection Expressions
A collection expression is a terse syntax that, when evaluated, can be assigned to many different collection types. It contains a sequence of elements between [ and ] brackets. It can be converted to many different collection types.
Example
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
// Example 1: Initializing a list with values
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Console.WriteLine("Numbers List:");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
// Example 2: Initializing a dictionary with key-value pairs
Dictionary<string, int> ages = new Dictionary<string, int>
{
{ "Ankita", 25 },
{ "Rahul", 30 },
{ "Suman", 28 }
};
Console.WriteLine("\nAges Dictionary:");
foreach (var entry in ages)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
// Example 3: Filtering even numbers using LINQ
List<int> evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine("\nEven Numbers List:");
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
}
}
Output
Numbers List:
1
2
3
4
5
Ages Dictionary:
Ankita: 25
Rahul: 30
Suman: 28
Even Numbers List:
2
4
Read More: |
OOPs Interview Questions and Answers in C# |
Top 50 C# Interview Questions and Answers To Get Hired |
Conclusion
In conclusion, we have covered the Top 20 Features of C#. C# supports you with powerful features that make it a versatile and efficient programming language. Each C# feature helps you a lot in your programming language learning. Whether you’re working on small projects or large-scale applications, mastering these top 20 features in C# will help you write cleaner, more efficient, and maintainable code. To master C# programming language, Scholarhat provides you with the Full-Stack .NET Developer Certification Training Course and .NET Solution Architect Certification Training for better understanding.
FAQs
Take our Csharp skill challenge to evaluate yourself!
In less than 5 minutes, with our skill challenge, you can identify your knowledge gaps and strengths in a given skill.