Top 20 C# Features Every .NET Developer Must Know in 2024

22 mrt. 2024
Advanced
722 Views
36 min read  

Top 20 C# Features Every .NET Developer Must Know in 2024: An Overview

C# is a modern, object-oriented programming language developed by Microsoft. It was first introduced in 2002 as part of Microsoft’s .NET framework. It is a simple, powerful, and type-safe programming language used to build desktop, web, game, and mobile applications. It supports both static and dynamic typing.

It is a very versatile programming language that is continuously evolving. Therefore, a .NET or a C# developer must remain up to date with the changes. Here is the list of the awesome features that you should know:

1. Generics

Generics introduces the concept of type parameters to .NET, which makes it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated 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.

This feature was introduced in C# 2.0.

Example


// Declare the generic class.
public class GenericList<T>
{
    public void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // Declare a list of type int.
        GenericList<int> list1 = new GenericList<int>();
        list1.Add(1);

        // Declare a list of type string.
        GenericList<string> list2 = new GenericList<string>();
        list2.Add("");

        // Declare a list of type ExampleClass.
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
        list3.Add(new ExampleClass());
    }
}
    

By using a generic type parameter T, you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations

Read More - C# Interview Questions For Freshers

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, struct, 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 above code, the fields and the constructor of the class, Coords, are declared in one partial class definition, and the member, PrintCoords, is declared in another partial class definition.

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.

  1. LINQ to Objects

    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 numbers = new List { 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
    
  2. LINQ to SQL

    LINQ to SQL allows querying relational databases using LINQ. It involves mapping database tables to C# classes and writing queries in a LINQ syntax.

    
    using System;
    using System.Linq;
    
    class Program
    {
        static void Main()
        {
            // Assume a DataContext and a 'Products' table mapping to a C# class
            using (var context = new MyDataContext())
            {
                var expensiveProducts = from product in context.Products
                                        where product.Price > 50
                                        select product;
    
                foreach (var product in expensiveProducts)
                {
                    Console.WriteLine($"{product.Name} - {product.Price}");
                }
            }
        }
    }
    

    Output

    ProductA - 75
    ProductB - 60
    ProductC - 55
    
  3. LINQ to XML

    LINQ to XML enables querying and manipulating XML data using LINQ. It allows you to express queries against XML documents naturally.

    
    using System;
    using System.Linq;
    using System.Xml.Linq;
    
    class Program
    {
        static void Main()
        {
            XElement root = XElement.Load("books.xml");
    
            var bookTitles = from book in root.Elements("book")
                             select book.Element("title").Value;
    
            foreach (var title in bookTitles)
            {
                Console.WriteLine(title);
            }
        }
    }
    

    Output

    Book Title 1
    Book Title 2
    Book Title 3
    

4. Lambda Expressions

C# Lambda Expression is a short block of code that accepts parameters and returns a value. It is defined as an anonymous function i.e. a function without a name. It was introduced in C# 3.0. 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

 List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

Example of Writing Easy and 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

This feature has been added in C# 3.0. It allows us to add new methods into a class without editing the source code of the class i.e. extending the functionality of a class in the future if the source code of the class is not available or we don’t have any permission to make changes to the class.

Example


using System;
namespace ExtensionMethods
{
    public class FirstClass
    {
        public int x = 200;
        public void Test1()
        {
            Console.WriteLine("Method One: " + this.x);
        }
        public void Test2()
        {
            Console.WriteLine("Method Two: " + this.x);
        }
    }
}

Let’s create a new class with the name NewClass.cs and then copy and paste the following code into it.


using System;
namespace ExtensionMethods
{
    public static class NewClass
    {
        public static void Test3(this OldClass O)
        {
            Console.WriteLine("Method Three");
        }
        public static void Test4(this OldClass O, int x)
        {
            Console.WriteLine("Method Four: " + x);
        }
        public static void Test5(this OldClass O)
        {
            Console.WriteLine("Method Five:" + O.x);
        }
    }
}

using System;
namespace ExtensionMethods
{
    class Program
    {
        static void Main(string[] args)
        {
            OldClass obj = new OldClass();
            obj.Test1();
            obj.Test2();
            //Calling Extension Methods
            obj.Test3();
            obj.Test4(20);
            obj.Test5();
            Console.ReadLine();
        }
    }
}

Output

Method One: 200
Method Two: 200
Method Three
Method Four: 20
Method Five: 200

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 Python, and 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


class Program
{
    static void Main(string[] args)
    {
        Method1();
        Method2();
        Console.ReadKey();
    }

    public static async Task Method1()
    {
        await Task.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine(" Method 1");
                // Do something
                Task.Delay(100).Wait();
            }
        });
    }


    public static void Method2()
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine(" Method 2");
            // Do something
           Task.Delay(100).Wait();
        }
    }
}

In the code above, Method1 and Method2 are not dependent on each other, and we call from the Main method. We can see that Method1 and Method2 are not waiting for each other.

8. String Interpolation

It was introduced in C# 6.0. It allows you to embed expressions and variables directly within string literals. It provides a concise and more readable way to create formatted strings compared to traditional string concatenation or the String.Format method.

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

Expression-bodied members are a syntactic shorthand introduced in C# 6.0. They allow you to write concise one-liner methods, properties, and other members using lambda-like syntax. 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

Auto-property initializers provide a way to initialize the value of an auto-implemented property directly within the property declaration. This simplifies the syntax for setting default values for properties. They were introduced in C# 6.0.

Auto-property initializers are a concise way to set default values for properties, and they are particularly useful for avoiding the need to initialize properties in the constructor.

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 and Deconstruction are features introduced in C# 7.0 that provide convenient ways to work with sets of values.

  1. Tuples

    Tuples allow you to group multiple values into a single object without the need to create a custom class or struct. One can use either named or unnamed tuples.

    • Unnamed Tuples
      
      var unnamedTuple = (1, "ScholarHat", 2);
      
      Console.WriteLine($"ID: {unnamedTuple.Item1}, Name: {unnamedTuple.Item2}, Age: {unnamedTuple.Item3}");
      
    • Named Tuples
      
      var namedTuple = (ID: 1, Name: "ScholarHat", Age: 2);
      
      Console.WriteLine($"ID: {namedTuple.ID}, Name: {namedTuple.Name}, Age: {namedTuple.Age}");
      
  2. Deconstruction

    Deconstruction allows you to split a tuple or any other object into its components.

    
    var person = (ID: 1, Name: "ScholarHat", Age: 2);
    
    // Deconstruction
    var (id, name, age) = person;
    
    Console.WriteLine($"ID: {id}, Name: {name}, Age: {age}");
    

12. Pattern Matching

Pattern matching is a feature introduced in C# 7.0 that allows you to check the shape or structure of a value directly in code. It simplifies the syntax for common type checks and extractions, making code more readable and reducing boilerplate code.

Example of Type Pattern


object obj = "Hello, C#";

if (obj is string str)
{
    Console.WriteLine($"Length of the string: {str.Length}");
}

Output

Length of the string: 10

13. Nullable Reference Types

Nullable Reference Types is a feature introduced in C# 8.0 to help developers write more robust and safe code by adding annotations to the type system to indicate whether a reference type can be null or not. It allows you to express the intent about nullability directly in the code, and the compiler can provide warnings for potential null-reference issues.

Enabling Nullable Reference Types


#nullable enable

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.


using System;

public interface IShape
{
    void Draw();

    // Default method with an implementation
    default void Display()
    {
        Console.WriteLine("Default Display Implementation");
    }
}

public class Circle : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a circle");
    }
}

public class Square : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a square");
    }

    // You can choose to override the default implementation if needed
    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

        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

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.


public record Person(string FirstName, string LastName, int Age);

class Program
{
    static void Main()
    {
        // Creating an instance of the record
        var person = new Person("ScholarHat", "DotNetTricks", 30);

        // Displaying the record properties
        Console.WriteLine($"Name: {person.FirstName} {person.LastName}, Age: {person.Age}");

        // Creating a new record with updated values
        var updatedPerson = person with { Age = 31 };

        // Displaying the updated record properties
        Console.WriteLine($"Updated Age: {updatedPerson.Age}");

        // Records provide value-based equality
        var anotherPerson = new Person("John", "Doe", 30);
        Console.WriteLine($"Are they equal? {person.Equals(anotherPerson)}");
    }
}

Output

Name: ScholarHat DotNetTricks, Age: 30
Updated Age: 31
Are they 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 using 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.

Example


// Global using directives specified in the project file
global using System;
global using System.Collections.Generic;
global using System.Linq;

// Code in any file of the project
class Program
{
    static void Main()
    {
        // You can use types from the specified global using directives directly
        List numbers = Enumerable.Range(1, 5).ToList();

        foreach (var number in numbers)
        {
            Console.WriteLine(number);
        }
    }
}

Output

1
2
3
4
5

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;

class Program
{
    static void Main()
    {
        object obj = new int[] { 1, 2, 3 };

        if (obj is int[] { Length: > 2, [0]: var firstElement, [^1]: var lastElement })
        {
            Console.WriteLine($"Array has more than 2 elements. First: {firstElement}, Last: {lastElement}");
        }
        else
        {
            Console.WriteLine("Array does not meet the pattern criteria.");
        }
    }
}

Output

Array has more than 2 elements. First: 1, Last: 3

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

Example


public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName) =>
        (FirstName, LastName) = (firstName, lastName);

    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    public int? Age { get; set; }
}

public class Student : Person
{
    public Student() : base()
    {
    }

    [SetsRequiredMembers]
    public Student(string firstName, string lastName) :
        base(firstName, lastName)
    {
    }

    public double GPA { get; set; }
}

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


Span weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
foreach (var day in weekDays)
{
    Console.WriteLine(day);
}

The above code declares a System.Span of string elements and initialize them to the days of the week.

Summary

Hence, we have beautifully listed out the top 20 C# features required to be known by aspiring as well as experienced C# or .NET developers. Go through the complete article thoroughly and make the most out of it. To become a Certified .NET Developer, consider our .NET Training.

FAQs

Q1. What is the use of partial keyword in C#?

The partial keyword indicates that other parts of the class, struct, or interface can be defined in the namespace.

Q2. What are Lambda Expressions in C#?

Lambda Function is defined as an anonymous function i.e. a function without a name.

Q3. What are Auto-Property Initializers?

Auto-property initializers provide a way to initialize the value of an auto-implemented property directly within the property declaration. This simplifies the syntax for setting default values for properties.
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 8th time in a row (2016-2023). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this