.NET Framework versions 1.0 to 4.8 are Microsofts .NET implementation. It was succeeded by .NET 5.0 in 2020. It was for Windows only.
.NET Core versions 1.0 to 3.1 are the open source implementation of .NET supported by Microsoft. It was renamed to just .NET in 2020. It was for Windows, Linux and macOS.
.NET version 5.0 and later are the common open source and Microsoft implementation of .NET from 2020 an onwards. It is based on .NET Core 3.1 - not on .NET Framework 4.8. It is for Windows, Linux and macOS.
.NET Framework and .NET Core does not consist of exactly the same components.
I have not worked enough with .NET 5.0 to have verified everything below - it is just based on what I have read on the internet, so there may be some inaccuracies.
Core (portable):
Core (Windows only):
Core (new):
Extension package - not part of .NET Core but available via NuGet:
FX only:
BinaryFormatter is highly deprecated in .NET Core.
.NET Framework is integrated in Windows limiting the ability to have different versions side by side. .NET Core is just stuff in a directory tree making it much easier to have different versions side by side. .NET Core does not have GAC either.
When using Visual Studio the difference between .NET Framework and .NET Core is almost invisible. You pick the correct template when you create the solution and then Visual Studio handles the rest. But using command line tools are very different.
In .NET Framework one could build with the command line compiler:
csc something.cs
That is practicall impossible with .NET Core - instead one need to use the provided tools and create a project.
Create console project in current directory:
dotnet new console
Add NuGet package:
dotnet add package Xxx.Yyy.Zzz
Build project:
dotnet build
Run project:
dotnet run
Generate deployable bundle including an EXE file:
dotnet publish
.NET Framework used XML config files (app.config -> xxx.exe.config for console applications, web.config for ASP.NET applications).
app.exe.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="Key#1" value="Value #1" />
<add key="Key#2" value="Value #2" />
<add key="Key#3" value="Value #3" />
</appSettings>
</configuration>
app.cs:
using System;
using System.Configuration;
public class App
{
public static void Main(string[] args)
{
Console.WriteLine(ConfigurationManager.AppSettings["Key#1"]);
Console.WriteLine(ConfigurationManager.AppSettings["Key#2"]);
Console.WriteLine(ConfigurationManager.AppSettings["Key#3"]);
}
}
.NET Core is much more flexible.
The standard and recommended is JSON config files (appsetting.json and appsettings.xxxx.json).
appsettings.json:
{
"AppSettings" : {
"Key#1" : "Value #1",
"Key#2" : "Value #2",
"Key#3" : "Value #3"
}
}
app.cs:
using System;
// dotnet add package Microsoft.Extensions.Configuration
// dotnet add package Microsoft.Extensions.Configuration.Json
using Microsoft.Extensions.Configuration;
public class App
{
public static void Main(string[] args)
{
IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true);
IConfiguration config = builder.Build();
Console.WriteLine(config["AppSettings:Key#1"]);
Console.WriteLine(config["AppSettings:Key#2"]);
Console.WriteLine(config["AppSettings:Key#3"]);
}
}
But it is also possible to use the same XML file as in .NET Framework.
app.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="Key#1" value="Value #1" />
<add key="Key#2" value="Value #2" />
<add key="Key#3" value="Value #3" />
</appSettings>
</configuration>
app.cs:
using System;
// dotnet add package System.Configuration.ConfigurationManager
using System.Configuration;
public class App
{
public static void Main(string[] args)
{
Console.WriteLine(ConfigurationManager.AppSettings["Key#1"]);
Console.WriteLine(ConfigurationManager.AppSettings["Key#2"]);
Console.WriteLine(ConfigurationManager.AppSettings["Key#3"]);
}
}
There are lots of other new stuff in .NET Core.
For a description of the new .NET Core System.Text.Json namespace see code here.
For a description of .NET Core DI see Core/MS Extension code here.
There are lots of other changes around the framework. Just as an example see the notes in Web Service - Standalone article.
Version numbers are a bit complicated.
.NET framework | .NET runtime | C# language/compiler | Visual Studio IDE | Release date |
---|---|---|---|---|
1.0 | 1.0 | 1.0 | 2002 | February 13th 2002 |
1.1 | 1.1 | 1.2 | 2003 | April 24th 2003 |
2.0 | 2.0 | 2.0 | 2005 | November 7th 2005 |
3.5 | 2.0 | 3.0 | 2008 | November 19th 2007 |
4.0 | 4.0 | 4.0 | 2010 | April 12th 2010 |
4.5 | 4.0 | 5.0 | 2012 | August 15th 2012 |
4.5.1 | 4.0 | 5.0 | 2013 | October 17th 2013 |
4.6 | 4.0 | 6.0 | 2015 | July 20th 2015 |
4.6.2 | 4.0 | 7.0 | 2017 | March 7th 2017 (framework August 2nd 2016) |
4.6.2 | 4.0 | 7.1 | 2017 update 3 | August 14th 2017 |
4.7.1 | 4.0 | 7.2 | 2017 update 5 | November 11th 2017 (framework October 17th 2017) |
4.7.2 | 4.0 | 7.3 | 2017 update 7 | May 7th 2018 (framework April 30th 2018) |
4.8 | 4.0 | 8.0 | 2019 update 3 | September 19th 2019 (framework April 18th 2019) |
.NET core | C# language | Visual Studio IDE | Release date | LTS |
---|---|---|---|---|
1.0 | 6.0 | 2015 update 3 | June 27th 2016 | No |
1.1 | 6.0 | 2017 | November 16th 2016 | No |
2.0 | 7.1 | 2017 update 3 | August 14th 2017 | No |
2.1 | 7.3 | 2017 update 7 | May 30th 2018 | Yes |
2.2 | 7.3 | 2019 | December 4th 2018 | No |
3.0 | 8.0 | 2019 update 3 | September 23rd 2019 | No |
3.1 | 8.0 | 2019 update 4 | December 3rd 2019 | Yes |
.NET | C# language | Visual Studio IDE | Release date | LTS |
---|---|---|---|---|
5.0 | 9.0 | 2019 update 8 | November 10th 2020 | No |
Actual new features in .NET 5.0 and C# 9.0 compared to .NET FX 4.8 / .NET Core 3.1 and C# 8.0.
By far the biggest change is the change from .NET FX to .NET Core (see above).
The second biggest change is the addition of record type family.
The remaining changes are minor.
C# 9.0 adds init only properties.
Syntax:
public type propertyname { get; init; }
C# 8.0 code:
using System;
public class Data1
{
public int Iv { get; set; }
public string Sv { get; set; }
}
public class Data2
{
public int Iv { get; }
public string Sv { get; }
public Data2(int xiv, string xsv) // necessary since there are no setters
{
Iv = xiv;
Sv = xsv;
}
}
public class IOP8
{
public static void Main(string[] args)
{
Data1 o1 = new Data1 { Iv = 123, Sv = "ABC" };
// possible to change o1 here
Console.WriteLine($"{o1.Iv} {o1.Sv}");
Data2 o2 = new Data2(123, "ABC");
Console.WriteLine($"{o2.Iv} {o2.Sv}");
}
}
C# 9.0 code:
using System;
public class Data
{
public int Iv { get; init; }
public string Sv { get; init; }
}
public class IOP9
{
public static void Main(string[] args)
{
Data o = new Data { Iv = 123, Sv = "ABC" };
// *NOT* possible to change o1 here
Console.WriteLine($"{o.Iv} {o.Sv}");
}
}
Maybe not a super important feature but certainly useful as it does fill a requirement that are frequently occuring.
C# 9.0 adds relational operator patterns to the switch statement.
Operators:
C# 8.0 code:
using System;
public class ROP8
{
public static void Test(double v)
{
string s;
if(v < 0.0)
{
s = "v < 0";
}
else if(0.0 <= v && v <= 1.0)
{
s = "0 <= v <= 1";
}
else if(1.0 < v)
{
s = "1 < v";
}
else
{
s = "WTF?";
}
Console.WriteLine(s);
}
public static void Main(string[] args)
{
Test(0.5);
}
}
C# 9.0 code:
using System;
public class ROP9
{
public static void Test(double v)
{
string s = v switch {
< 0.0 => "v < 0",
>= 0.0 and <= 1.0 => "0 <= v <= 1",
> 1.0 => "1 < v",
_ => "WTF?"
};
Console.WriteLine(s);
}
public static void Main(string[] args)
{
Test(0.5);
}
}
I suspect this feature will see some usage even though I do not like it - it is so short that I think readability is suffering.
C# 9.0 makes it optional to supply type name in new if the type name can be inferred from the target.
Syntax:
sometype somevariable = new(arg1,arg2,arg3);
is equivalent to:
sometype somevariable = new sometype(arg1,arg2,arg3);
C# 8.0 code:
using System;
public class X
{
public string S { get; set; }
public X(string s)
{
this.S = s;
}
}
public class TTN8
{
public static void Main(string[] args)
{
X o = new X("ABC");
Console.WriteLine($"{o.S}");
}
}
C# 9.0 code:
using System;
public class X
{
public string S { get; set; }
public X(string s)
{
this.S = s;
}
}
public class TTN9
{
public static void Main(string[] args)
{
X o = new("ABC");
Console.WriteLine($"{o.S}");
}
}
I think this feature is totally unnecessary. Adding the type name explicit is little work and make the code more readable.
C# 9.0 makes it optional to supply class declaration and Main method declaration for main program.
C# 8.0 code:
using System;
public class HW8
{
public static void Main(string[] args)
{
Console.WriteLine("Hello world!");
}
}
C# 9.0 code:
using System;
Console.WriteLine("Hello world!");
I think this feature is totally unnecessary. C# is not a scripting language and adding those few extra lines to the main program is really nothing.
C# 9.0 adds a record class type.
C# record class is very similar to Kotlin data class, Scala case class and Java 14 record.
A C# record class is a reference type with some value style semantics and the possibility for very compact declaration.
C# 8.0 code:
using System;
public class Data
{
public int Iv { get; set; }
public string Sv { get; set; }
}
public class Rec8
{
public static void Main(string[] args)
{
Data o = new Data { Iv = 123, Sv = "ABC" };
Console.WriteLine($"{o.Iv} {o.Sv}");
}
}
C# 9.0 code:
using System;
public record class Data(int Iv, string Sv);
public class Rec9
{
public static void Main(string[] args)
{
Data o = new Data(123, "ABC");
Console.WriteLine($"{o.Iv} {o.Sv}");
}
}
To explore the differences between struct, class and record see this code:
using System;
public struct Data1
{
public int Iv;
public string Sv;
}
public class Data2
{
public int Iv { get; set; }
public string Sv { get; set; }
}
public record class Data3
{
public int Iv { get; set; }
public string Sv { get; set; }
}
public class RecDet
{
public static void Main(string[] args)
{
Console.WriteLine("**** struct ****");
Data1 s1 = new Data1();
s1.Iv = 123;
s1.Sv = "ABC";
Console.WriteLine("Value type: " + typeof(Data1).IsValueType);
Data1 s2 = new Data1();
s2.Iv = 123;
s2.Sv = "ABC";
Console.WriteLine("Equals: " + s1.Equals(s2));
Console.WriteLine("Same hash code: " + (s1.GetHashCode() == s2.GetHashCode()));
Data1 s3 = s1;
s1.Iv = 0;
Console.WriteLine($"After assignment update: {s1.Iv} {s3.Iv}");
Console.WriteLine("**** class ****");
Data2 c1 = new Data2();
c1.Iv = 123;
c1.Sv = "ABC";
Data2 c2 = new Data2();
c2.Iv = 123;
c2.Sv = "ABC";
Console.WriteLine("Equals: " + c1.Equals(c2));
Console.WriteLine("Same hash code: " + (c1.GetHashCode() == c2.GetHashCode()));
Data2 c3 = c1;
c1.Iv = 0;
Console.WriteLine($"After assignment update: {c1.Iv} {c3.Iv}");
Console.WriteLine("**** record ****");
Data3 r1 = new Data3();
r1.Iv = 123;
r1.Sv = "ABC";
Console.WriteLine("Value type: " + typeof(Data3).IsValueType);
Data3 r2 = new Data3();
r2.Iv = 123;
r2.Sv = "ABC";
Console.WriteLine("Equals: " + r1.Equals(r2));
Console.WriteLine("Same hash code: " + (r1.GetHashCode() == r2.GetHashCode()));
Data3 r3 = r1;
r1.Iv = 0;
Console.WriteLine($"After assignment update: {r1.Iv} {r3.Iv}");
}
}
Output:
**** struct **** Value type: True Equals: True Same hash code: True After assignment update: 0 123 **** class **** Equals: False Same hash code: False After assignment update: 0 0 **** record **** Value type: False Equals: True Same hash code: True After assignment update: 0 0
C# 9.0 also has a with expression that allows constructing a record instance from another record instance with one or more properties changed:
using System;
public record class Data(int Iv, double Xv, string Sv)
{
public override string ToString()
{
return $"[{Iv},{Xv},{Sv}]";
}
}
public class WE9
{
public static void Main(string[] args)
{
Data o1 = new Data(123, 123.456, "ABC");
Console.WriteLine(o1);
Data o2 = o1 with { Sv = "DEF" };
Console.WriteLine(o2);
}
}
This feature is popular in other languages, so I expect it to become popular in C# as well.
C# 9.0 adds two new integer types nint and nuint.
They are signed and unsigned int of size 32 bit in 32 bit .NET and size 64 bit in 64 bit .NET.
I think this feature is irrelvant for most .NET developers.
See .NET 6.0 and C# 10.0 New Features.
Version | Date | Description |
---|---|---|
1.0 | November 15th 2020 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj