Did you know that if you're working with .NET Framework versions 4.7.2 up to 4.8.1 alongside Visual Studio 2022, you can upgrade to C# 12? By adding a few components via NuGet and changing the LangVersion key in your .csproj files to <LangVersion>12.0</LangVersion>, you're set for the shift.
For ASP.NET WebForms projects, a slight tweak in your Web.Config is needed to sync with Roslyn.
This shift brings a significant edge, keeping you and your team on the cutting edge with C# 12's latest features.
In this article, I'll showcase how C# 12 will enhance your programming capabilities. And for deployment? The process remains seamless using GitHub Actions or any other CI/CD platform.
Stay tuned to discover the transformative power of C# 12!
Let's do it!
Starting adding the last version of these packages from NuGet to your CS projects:
Upgrading from C# 7.3 to C# 12 brings many new features and changes that vastly increase the language's capabilities and development experience.
Modify your .csproj files by adding the LangVersion key like this.
<PropertyGroup>
<LangVersion>12.0</LangVersion>
What can you do now in C# 12?
Here's a rundown of the significant changes and additions between C# 7.3 and C# 12.
Literals
Literals are easy to declare.
var dataId = 1;
// C# 7.3
var text73 = $@"
{{data}}:{dataId}
";
var json73 = @"
{{
""name"": ""John"",
""age"": 30
}}
";
// C# 12
var text12 = $$"""
{data}:{{dataId}}
""";
var json12 = """
{
"name": "John",
"age": 30
}
""";
Return default value
Sample 1. Returning array values with [].
// C# 7.3
private List<string> Values73(bool retEmpty = true)
{
if (retEmpty) return new List<string>();
return new List<string> { "Test1", "Test2" };
}
// C# 12
private List<string> Values(bool retEmpty = true)
{
if (retEmpty) return [];
return ["Test1", "Test2"];
}
Sample 2. Returning empty array.
// C# 7.3
private List<string> Parameters73()
{
return new List<string>();
}
// C# 12.0
private List<string> Parameters()
{
return [];
}
Property and Tuple Pattern
Tuples efficiently pass parameters and serve as return values.
// C# 7.3
private string PointTuple73()
{
var pointX = 3; var pointY = 4;
if (pointX == 3 && pointY == 4) return "Origin";
if (pointX == pointY) return "On the diagonal";
return "Other point";
}
// C# 12
private string PointTuple12()
{
var point = (x: 3, y: 4);
var result = point switch
{
(0, 0) => "Origin",
var (x, y) when x == y => "On the diagonal",
_ => "Other point"
};
return result;
}
Switch expressions
The switch is more direct.
// C# 7.3
private string Direction73()
{
var direction = DefaultLocal73();
switch (direction)
{
case "north":
return "move up";
case "south":
return "move down";
default:
return "undefined";
}
}
// C# 12
private string Direction12()
{
var direction = DefaultLocal();
var movement = direction switch
{
"north" => "move up",
"south" => "move down",
_ => "undefined"
};
return movement;
// Local function
static string DefaultLocal() => "north";
}
Switch expression with type
Now, you can check the type of the objects.
// C# 7.3
private string Result73(object value)
{
if (value.GetType() == typeof(string)) return "String";
if (value.GetType() == typeof(int)) return "Int number";
return "Another type";
}
// C# 12
private string Result12(object value)
{
var resultado = value switch
{
int i => "Int number",
string s => "String",
_ => "Another type"
};
return resultado;
}
Nullable
To use Nullables, declare them at the top of the file .cs with #nullable enabled
.
// C# 7.3
private List<string> ParametersDefault73()
{
return null;
}
// C# 12.0
#nullable enable
...
private List<string>? ParametersDefault()
{
return null;
}
Record
Class defines behavior for objects, creating mutable instances. Record is a data container, inherently immutable, focusing on stored data, the best for Dto objects.
Here is a brief comparison.
Aspect |
Class |
Record |
Purpose and Semantics |
The traditional OOP construct focuses on object identity, which is best for complex scenarios with equal emphasis on behavior and data. |
Introduced for immutable data structures and value-based equality, it is ideal for data-centric models prioritizing immutability. |
Immutability |
Mutable, by default, can be made immutable (e.g., using read-only properties). |
Immutable by default, properties are init-only. |
Equality Comparison |
Reference-based equality (two objects are equal if they reference the exact memory location). |
Value-based equality (equal if all property values are equal). |
Memberwise Clone and Deconstruction |
No default support requires manual implementation. |
Supports memberwise cloning and deconstruction by default. |
Inheritance |
It fully supports inheritance, allowing a hierarchy of types. |
It supports inheritance but is best suited as leaf nodes due to immutability. Inherits from other record types. |
Syntax |
Defined using the "class" keyword. |
It is defined using the "record" keyword. |
Sample code
// C# 7.3 class
public class Person73
{
public int Id;
public string Name;
}
// C# 12 record
public record Person12()
{
public int Id;
public string Name;
}
public class MyClass()
{
private Person73 Person73()
{
var person = new Person73 { Id = 1, Name = "John" };
return person;
}
private Person12 Person12()
{
var person = new Person12 { Id = 1, Name = "John" };
return person;
}
}
Array Expressions
There is a significant change here, but C# 12 in .NET 4.x doesn't cover all possible uses in .NET 8 apps.
// C# 7.3
private void ExpressionsCollections73()
{
// Create an array:
int[] a = { 1, 2, 3, 4, 5, 6, 7, 8 };
// Create a list:
List<string> b = new List<string>() { "one", "two", "three" };
/*
// Has no similar in C# 7.3
// Create a span
Span<char> c = new Span<char>() { 'a', 'b', 'c', 'd', 'e', 'f', 'h', 'i' }; // Not works in C# 7.3
// Create a jagged 2D array:
int[][] twoD = { { 1, 2, 3 } { 4, 5, 6 }, { 7, 8, 9 } }; // Not works in C# 7.3
*/
// Create a jagged 2D array from variables:
int[] row0 = { 1, 2, 3 };
int[] row1 = { 4, 5, 6 };
int[] row2 = { 7, 8, 9 };
int[][] twoDFromVariables = { row0, row1, row2 };
}
// C# 12
private void ExpressionsCollections12()
{
// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];
// Create a list:
List<string> b = ["one", "two", "three"];
// Create a span
Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
// Create a jagged 2D array:
int[][] twoD = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
// Create a jagged 2D array from variables:
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[][] twoDFromVariables = [row0, row1, row2];
}
Sample from Microsoft Learn.
ASP.NET WebForms
To work with ASP.NET WebForms, replace the "LangVersion" in the Web.config file with "preview."
<system.codedom>
<compilers>
<compiler extension=".cs" language="c#;cs;csharp" warningLevel="4" compilerOptions="/langversion:preview /nowarn:1659;1699;1701;612;618" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<compiler extension=".vb" language="vb;vbs;visualbasic;vbscript" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008,40000,40008 /define:_MYTYPE=\"Web\" /optionInfer+" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</compilers>
</system.codedom>
Conclusion
Although the .NET Framework is no longer in use, we will continue to encounter its code for many years, if not decades. Meanwhile, we can take advantage of the most recent C# language features.