C# 12 is a major release that includes a number of new features, such as primary constructors, collection expressions, and inline arrays. These features make C# even more expressive and powerful, and they will help developers write more concise and maintainable code.
New Features added in C#12 are:
- Primary Constructors
- Collection Expression
- Default Parameters in Lambda Expression
- Alias for any type
Primary constructors
Primary constructors are a new feature in C# 12 that allows you to initialize fields directly in the class declaration. This makes it easier to write code that is readable and maintainable.
Definition: A primary constructor is a constructor that does not have any access modifiers and does not throw any exceptions.
Before Primary Constructor:
public class Person
{
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string Name { get; set; }
public int Age { get; set; }
}
After using Primary Constructor:
public class Person(string name, int age)
{
public string Name=> name;
public int Age => age;
}
How to declare a primary constructor?
public class Primary(string name, int age)
{
public string Name=> name;
public int Age => age;
}
var obj = new Primary("Rakesh", 20);
Console.WriteLine({obj.Name} {obj.Age});
Output: Rakesh 20
Console.WriteLine({obj.name}{obj.age});
// throws error as they are not in scope
var obj1 = new Primary();
//throws error because didn't define default constructor
Does a parameterless constructor get created by default if we define a primary constructor in a class? WHY?
No, The compiler does not create a default parameterless constructor. You must explicitly define it if needed.
- Primary Constructor: A primary constructor is a special kind of constructor that directly initializes properties and fields within the class definition.
- Default Parameterless Constructor: This is a constructor that takes no arguments and is typically used to create objects with default values
When creating the object of a class(with a primary constructor) typically involves providing values for its parameters, it’s still possible to define a parameterless constructor for the class. In such cases, default values can be assigned within the constructor using the this
keyword.
public class Customer(string name){
public Customer():this("Cristle")
{
}
public string Name=> name;
}
var obj = new Customer();
// works fine
Console.WriteLine({obj.Name});
Output: Cristle
var obj1 = new Customer("Rakesh");
Console.WriteLine({obj.Name});
Output: Rakesh
Collection Expression:
These are the compact syntaxes for creating collections like arrays, spans, and types with collection initializers like List<T>
. These can be written within square brackets []
with elements separated by commas and can include the spread operator ..
to easily insert elements from other existing collections.
Before Collection Expression:
using System.Collections.Immutable
var arrA = new int[]{1,2,3,4};
int[] numA = {1,2,3,4};
List<int> list = new List<int>{1,2,3,4};
Span<int> s = stackalloc int[4]{1,2,3,4,5};
ImmutableArray<int> i = ImmutableArray.Create(1,2,3,4,5);
After using Collection Expression:
using System.Collections.Immutable
int[] arrA = [1,2,3,4];
List<int> list = [1,2,3,4];
Span<int> s = [1,2,3,4,5];
ImmutableArray<int> i = [1,2,3,4,5];
Can we assign a Collection expression to the var type variable?
No, because the compiler will not be able to understand which type of collection expression it has to assign as var doesn’t specify the type.
var a = [1,2,3];
// error: no target type for collection expression
List<int< list = [1,2,4];
//create a List<int>
What is a spread operator?
The spread operator ..
is a concise syntax for expanding elements from one collection into another within collection expressions. It allows you to seamlessly merge elements from multiple collections, creating new collections with combined content.
int[] numbers1 = {1, 2, 3};
int[] numbers2 = {4, 5, 6};
int[] combined = [..numbers1, ..numbers2]; // {1, 2, 3, 4, 5, 6}
List<string> names1 = [ "Alice", "Bob"];
List<string> names2 =["Charlie", "David"];
List<string> allNames = [..names1, ..names2,"John"]; // {"Alice", "Bob", "Charlie", "David","John"}
Advantages of Collection Expression:
- Improved readability: Makes code more concise and clear, especially when initializing long lists of elements.
- Reduced verbosity: Eliminates the need for repeated keywords like
new
and type names. - Spread operator versatility: Allows merging elements from multiple collections seamlessly.
Default Parameters in Lambda Expression
While C# 12 doesn’t directly support default parameters within lambda expressions themselves, it introduces a feature that indirectly addresses this need:
Default Parameters in Methods and Constructors:
- C# 12 allows you to assign default values to parameters within methods and constructors.
- Lambda expressions can leverage these methods or constructors, effectively using those default values when parameters are omitted.
Before using the lambda optional parameter:
void Save(string message, string path)
{
if(!File.Exists)
{
Console.WriteLine($"{message}{path}")
}
}
//creating lambda expression
var lambda = (string msg, string path)=>Save(msg,path);
//Optimizing the above line of code
var lambda = Save;
//But if I want to set the default value for path then
//I have to create a new lambda expression for it
var lambda2 = (string msg)=>Save(msg, "Default.txt");
//Now depending upon parameters, I have to call the different lambda expression
lambda("This is first lambda call", "passingValue.txt");
lambda2("This is lamv=bda call with default parameter");
You can see how we are creating multiple lambda expressions based on the values we have to pass in the lambda expression, to resolve this, C#12 introduced the lambda optional parameter feature.
After using the lambda optional parameter:
void Save(string message, string path)
{
if(!File.Exists)
{
Console.WriteLine($"{message}{path}")
}
}
//creating lambda expression
var lambda = (string msg, string path="Default.txt")=>Save(msg,path);
//Now we can call the same lambda expression and pass different number of parameters
lambda("Passing both parameters","secondParameter.txt");
lambda("Passing single parameter");
Output:
Passing both parameters secondParameter.txt
Passing single parameter Default.txt
By using default parameters in methods and constructors, you can create more adaptable and concise lambda expressions in C# 12, enhancing code readability and maintainability.
A lambda expression can also accept param array type parameter:
//passing of params in the method parameter works fine in both C#11 & C#12
void Process(params int[] values)
{
foreach(var v in values)
Console.WriteLine(v);
}
var processCalling = (params int[] val)=> Process(val);
//above line of code throws error in C#11
// Works fine in C#12 as it can accept params int[] in lambda expression
processCalling([1,2,4]);
Output:
1
2
4
Alias for Any Type
It allows you to create aliases for virtually any type, not just named types. This adds flexibility and clarity to your code, especially when dealing with complex types or long generic constructs.
Declaring an Alias:
Use the using alias = type
syntax, where alias
is your desired alias name and type
is the actual type you want to alias.
- You can create aliases for:
- Tuple types
- Pointer types
- Array types
- Generic types
- Other complex types
Using the Alias:
- Employ the alias as if it were the original type in variable declarations, function parameters, return types, and more.
// Alias for a generic type
using Dictionary = System.Collections.Generic.Dictionary<string, int>;
using NumberArray = int[];
Dictionary myDictionary = new Dictionary();
NumberArray numbers = new NumberArray { 1, 2, 3 };
We can also use it directly to call delegates in C#
using ComparisionDelegate = System.Func<double,double>;
ComparisionDelegate cd = (value, threshold)=> value>=threshold;
Console.WriteLine(cd(3.5,5.0));
Output: false
Below is the example for Tuple using aliases
using Coordinates = (int X, int Y); // Alias for a tuple with two integer elements
// Creating a tuple using the alias:
Coordinates myPosition = (10, 20); // Equivalent to (int X, int Y) myPosition = (10, 20);
// Accessing tuple elements:
Console.WriteLine($"X: {myPosition.X}, Y: {myPosition.Y}");
// Passing the tuple to a function:
void PrintCoordinates(Coordinates coords)
{
Console.WriteLine($"Coordinates: ({coords.X}, {coords.Y})");
}
PrintCoordinates(myPosition);
Advantages of using Alias type:
- Improved Readability: Aliases make code more readable by shortening complex type names and creating meaningful abstractions.
- Easier Handling of Tuples and Other Types: It simplifies working with tuples, pointers, and other types that might have verbose names or syntax.
- Consistency and Maintainability: Aliases can enforce consistent naming conventions across your codebase, enhancing maintainability.
By effectively using “alias any type” in C# 12, you can write cleaner, more expressive, and easier-to-understand code, especially when dealing with complex type structures.
Overall, C# 12 features offer significant benefits for developers seeking to:
- Write more concise, expressive, and readable code.
- Increase code flexibility and maintainability.
- Handle complex types and collections more efficiently.
- Stay updated with modern C# language advancements.
Conclusion
By embracing these features, you can create cleaner, more maintainable, and adaptable C# applications, leading to improved developer productivity and code quality.
Have questions or insights to share? Feel free to leave comments, engage with the community, and explore related articles on data management and .NET development.
Stay Connected
Don’t miss out on more informative articles and tutorials. Subscribe to our blog, and join the conversation. Your feedback and suggestions are always welcome as we continue to explore the world of .NET development together.