C Sharp: Difference between revisions
(7 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
=Installing Dotnet= | |||
This should be as simple as | |||
<syntaxhighlight lang="bash"> | |||
sudo snap install dotnet-sdk --classic | |||
</syntaxhighlight> | |||
But with dotnet 5 preview we get | |||
<syntaxhighlight lang="bash"> | |||
cannot snap-exec: cannot exec "/snap/dotnet-sdk/67/snap/command-chain/snapcraft-runner": permission denied | |||
</syntaxhighlight> | |||
To fix this we need to do this from https://github.com/dotnet/core/issues/4446 | |||
<syntaxhighlight lang="bash"> | |||
# Install the .NET 5 SDK as a snap package. | |||
# The snap package will automatically be mounted. | |||
snap install dotnet-sdk --channel=5.0/beta --classic | |||
# To find out where the snap package has been mounted, | |||
# list all block devices and look for /snap/dotnet-sdk/ | |||
# in the MOUNTPOINT column. Remember the name of the loop | |||
# device. | |||
lsblk | |||
# To get the actual path of the snap package file, run | |||
# the following command with the name of the loop device. | |||
# Replace XXX with the number of your loop device. | |||
# The path in the BACK-FILE column points to the snap package. | |||
losetup --list /dev/loopXXX | |||
# Create a folder where we're extracting the snap package into. | |||
mkdir dotnet-snap-fix | |||
cd dotnet-snap-fix | |||
# Extract the snap package into this directory. | |||
# Add the correct BACK-FILE path to your snap package here. | |||
sudo unsquashfs /var/lib/snapd/snaps/dotnet-sdk_67.snap | |||
# Change the permissions of the snap folder containing the runner | |||
# to be readable and executable. This will fix the permission problem. | |||
sudo chmod -R +rx ./squashfs-root/snap/ | |||
# Create a new snap package with the changed permissions. | |||
# Make sure to use the same file name as in the BACK-FILE path. | |||
sudo mksquashfs ./squashfs-root/ dotnet-sdk_67.snap -comp xz -all-root | |||
# Overwrite the old snap package with our new one. | |||
# Make sure the file name is correct (same as in BACK-FILE). | |||
sudo mv ./dotnet-sdk_67.snap /var/lib/snapd/snaps/ | |||
# Finally reboot your machine so the changes are detected. | |||
sudo reboot | |||
</syntaxhighlight> | |||
=C# 12.0= | |||
==Primary Constructor== | |||
The languages merge in my brain. But there goes. Basically you now can declare the parameters with the class. And have them referenced by the class without a constructor. Gosh C# seems verbose to me. So we used to do this | |||
<syntaxhighlight lang="cs"> | |||
namespace Example.Worker.Service | |||
{ | |||
public class Worker : BackgroundService | |||
{ | |||
private readonly ILogger<Worker> _logger; | |||
public Worker(ILogger<Worker> logger) | |||
{ | |||
_logger = logger; | |||
} | |||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |||
{ | |||
while (!stoppingToken.IsCancellationRequested) | |||
{ | |||
if (_logger.IsEnabled(LogLevel.Information)) | |||
{ | |||
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); | |||
} | |||
await Task.Delay(1000, stoppingToken); | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
Now we can do this | |||
<syntaxhighlight lang="cs"> | |||
namespace Example.Worker.Service | |||
{ | |||
public class Worker(ILogger<Worker> logger) : BackgroundService | |||
{ | |||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) | |||
{ | |||
while (!stoppingToken.IsCancellationRequested) | |||
{ | |||
if (logger.IsEnabled(LogLevel.Information)) | |||
{ | |||
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); | |||
} | |||
await Task.Delay(1000, stoppingToken); | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Spread Operator for C#== | |||
Like Typescript/Javascript there is now a spread operator. | |||
<syntaxhighlight lang="cs"> | |||
int[] row0 = [1, 2, 3]; | |||
int[] row1 = [4, 5, 6]; | |||
int[] row2 = [7, 8, 9]; | |||
int[] single = [.. row0, .. row1, .. row2]; | |||
</syntaxhighlight> | |||
=C# 10.0= | |||
==File Scoped Namespaces== | |||
Previously | |||
<syntaxhighlight lang="cs"> | |||
namespace FileScopedNamespace | |||
{ | |||
public class Customer | |||
{ | |||
// properties and methods | |||
} | |||
} | |||
</syntaxhighlight> | |||
Now | |||
<syntaxhighlight lang="cs"> | |||
namespace FileScopedNamespace; | |||
public class Customer | |||
{ | |||
// properties and methods | |||
} | |||
</syntaxhighlight> | |||
==Extended property patterns== | |||
<syntaxhighlight lang="cs"> | |||
// In 10 we can do this | |||
{ Prop1.Prop2: pattern } | |||
// Instead of | |||
{ Prop1: { Prop2: pattern } } | |||
</syntaxhighlight> | |||
=C# 9.0= | =C# 9.0= | ||
==Required== | ==Required== | ||
2019 16.8.2 and .NET 5.0 | 2019 16.8.2 and .NET 5.0 | ||
==Records and Properties== | |||
Records | |||
*Used to make read only struct (you can use struct too! | |||
*Equality is value based rather than referenced | |||
*Use init to allow old time initialize | |||
<syntaxhighlight lang="cs"> | |||
public record Person | |||
{ | |||
public required string FirstName { get; init; } | |||
public required string LastName { get; init; } | |||
}; | |||
</syntaxhighlight> | |||
We can clone immutable records using the clone oops with command | |||
<syntaxhighlight lang="cs"> | |||
var myThing2 = mything with { Name = "Derek"} | |||
</syntaxhighlight> | |||
Records support constructors, functions and properties too | |||
<syntaxhighlight lang="cs"> | |||
public record MyDto(String Name) { | |||
public string City {get; init;} | |||
public void SuperFunctionOopsMethod(} { | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Type not Required For New is implied== | ==Type not Required For New is implied== | ||
Like auto in c++ or var in c# we can now omit the new Keyword if the type can be implied | Like auto in c++ or var in c# we can now omit the new Keyword if the type can be implied | ||
Line 12: | Line 178: | ||
==Init for Properties== | ==Init for Properties== | ||
The init keyword on a property means that the value can only be set at initialisation time. | The init keyword on a property means that the value can only be set at initialisation time. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="cs"> | ||
class Thing { | class Thing { | ||
public int Id {get; init; } | public int Id {get; init; } | ||
Line 26: | Line 192: | ||
myThing.id = 55 | myThing.id = 55 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 00:11, 5 November 2024
Installing Dotnet
This should be as simple as
sudo snap install dotnet-sdk --classic
But with dotnet 5 preview we get
cannot snap-exec: cannot exec "/snap/dotnet-sdk/67/snap/command-chain/snapcraft-runner": permission denied
To fix this we need to do this from https://github.com/dotnet/core/issues/4446
# Install the .NET 5 SDK as a snap package.
# The snap package will automatically be mounted.
snap install dotnet-sdk --channel=5.0/beta --classic
# To find out where the snap package has been mounted,
# list all block devices and look for /snap/dotnet-sdk/
# in the MOUNTPOINT column. Remember the name of the loop
# device.
lsblk
# To get the actual path of the snap package file, run
# the following command with the name of the loop device.
# Replace XXX with the number of your loop device.
# The path in the BACK-FILE column points to the snap package.
losetup --list /dev/loopXXX
# Create a folder where we're extracting the snap package into.
mkdir dotnet-snap-fix
cd dotnet-snap-fix
# Extract the snap package into this directory.
# Add the correct BACK-FILE path to your snap package here.
sudo unsquashfs /var/lib/snapd/snaps/dotnet-sdk_67.snap
# Change the permissions of the snap folder containing the runner
# to be readable and executable. This will fix the permission problem.
sudo chmod -R +rx ./squashfs-root/snap/
# Create a new snap package with the changed permissions.
# Make sure to use the same file name as in the BACK-FILE path.
sudo mksquashfs ./squashfs-root/ dotnet-sdk_67.snap -comp xz -all-root
# Overwrite the old snap package with our new one.
# Make sure the file name is correct (same as in BACK-FILE).
sudo mv ./dotnet-sdk_67.snap /var/lib/snapd/snaps/
# Finally reboot your machine so the changes are detected.
sudo reboot
C# 12.0
Primary Constructor
The languages merge in my brain. But there goes. Basically you now can declare the parameters with the class. And have them referenced by the class without a constructor. Gosh C# seems verbose to me. So we used to do this
namespace Example.Worker.Service
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}
await Task.Delay(1000, stoppingToken);
}
}
}
}
Now we can do this
namespace Example.Worker.Service
{
public class Worker(ILogger<Worker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
}
await Task.Delay(1000, stoppingToken);
}
}
}
}
Spread Operator for C#
Like Typescript/Javascript there is now a spread operator.
int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [.. row0, .. row1, .. row2];
C# 10.0
File Scoped Namespaces
Previously
namespace FileScopedNamespace
{
public class Customer
{
// properties and methods
}
}
Now
namespace FileScopedNamespace;
public class Customer
{
// properties and methods
}
Extended property patterns
// In 10 we can do this
{ Prop1.Prop2: pattern }
// Instead of
{ Prop1: { Prop2: pattern } }
C# 9.0
Required
2019 16.8.2 and .NET 5.0
Records and Properties
Records
- Used to make read only struct (you can use struct too!
- Equality is value based rather than referenced
- Use init to allow old time initialize
public record Person
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
};
We can clone immutable records using the clone oops with command
var myThing2 = mything with { Name = "Derek"}
Records support constructors, functions and properties too
public record MyDto(String Name) {
public string City {get; init;}
public void SuperFunctionOopsMethod(} {
}
}
Type not Required For New is implied
Like auto in c++ or var in c# we can now omit the new Keyword if the type can be implied
public IainType ConvertToIain(String inParam1) {
// return new IainType(inParam1);
return new(inParam1);
}
Init for Properties
The init keyword on a property means that the value can only be set at initialisation time.
class Thing {
public int Id {get; init; }
public String Name {get; init; }
}
...
// Works
var myThing = new MyThing {
Id = 123,
Name = "No Name"
}
// No longer works
myThing.id = 55
Patterns and Expression
Finally switch with expression has arrived
var myBaseViewModel = type switch {
Type.Unknown => new BaseViewModel(),
Type.Sport => new SportViewModel(),
not null => throw new ArgumentException($"Unknown type {type}"),
_ => throw new ArgumentException($"Unknown type {type}")
};
In c# 9.0 the boolean expression is now a direct pattern called a relational pattern. The old approach
price += e,.numberOfDays switch {
var days when days < 3 => price * e.NumberOfDays,
var days when days >= 3 && days < 6 => 200,
var days when days >= 6 => 360
}
The new. Note the && become "and".The fancy words for this is conjunctive pattern.
price += e,.numberOfDays switch {
< 3 => price * e.NumberOfDays,
>= 3 and days < 6 => 200,
>= 6 => 360
}
Other Additions
- Covariant Return Types
- Minor enhancements for lambdas
- Main can be created without a class or namespace
C# 8.0 and .Net Core 3.0
Intro
Here is the history of C#
- 2.0 included generics
- 3.0 Added Linq with Lambda and anonymous types
- 4.0 Added Dynamics
- 5.0 Added Async and Await
- 6.0 Added Null propagating operator (targer?.IsActive ?? false;
- 7.0 Expressions, Span and Tuples
C# 8.0 brings
- Nullable Reference Types
- Pattern Matching
- Indices and Ranges
- Built-in Json Support
- Windows Desktop Support
- Build and Deploy Improvements
- Other Language Improvements
- Other .NET Core Platform Improvements
Nullable Reference Types
This allows you to specify a possibility of null. e.g.
class
{
public string? Title {get; set;}
public List<Comment> Title {get;} = new List<Comment>();
}
Bad code is highlighted a compile time with CS8618 Non-nullable property and via intellisence.
You and enable and restore this feature on a file basis using #nullable enable and #nullable restore
Pattern Matching
Deconstructors
Hardcoded unlike javascript
claas Test
{
public PropA {get; set;}
public PropB {get; set;}
public PropC {get; set;}
public PropD {get; set;}
Test(string a, string b, string c, string d)
{
PropA = a;
PropB = b;
PropC = c;
PropD = d;
}
void Deconstructor(
out string a,
out string b,
out string c,
out string d)
{
a = PropA;
b = PropB
c = PropC;
d = PropD;
}
}
Positional Pattern
public static bool TestHasCValue5(Test inTest)
{
return inTest is Test(_,_,"5",_);
}
Property Pattern
public class Employee
{
public string FirstName {get; set;}
public string LastName {get; set;}
public string Region {get; set;}
public Employee ReportsTo {get; set;}
}
public static bool IsUsBasedWithUkManager(object o)
{
return o is Employee e &&
e is { Region: "US", ReportsTo: {Region:" UK" }};
}
Switch Expression
Allows switching on types
var result = shape switch
{
Rectangle r => "{r.Length}",
Circle {Radius: 1} c => "Small Circle", // We can use LHS too!
Circle c => "{c.Radius}",
Triangle t => "{t.Side1}",
_ => "Default"
}
Tuple Pattern
public statis bool MakesBlue(Color c1, Color c2)
{
return (c1,c2) is (Color.Red, Color.Green) ||
(c2,c1) is (Color.Red, Color.Green);
}
Indices and Ranges
Index reference as location in the sequence
var numbers = new[] {0,1,2,3,4,5};
var number = numbers[1]; // number is 1
var numberFromRHS = numbers[^2]; // number is 4
Note ^1 is the last element
C# now supports the range expressions with dots.
var numbers = Enumerable.Range(1,10).ToArray();
var copy = numbers[0..^0];
var lastThreeItems = numbers[3^..];
Built-in Json Support
Adds support similar to NewtownSoft but does lack features. Just some key reminders in the code below. Google examples
// Reading as Tokens
var myBytes = File.ReadAllBytes("my.json");
var jsonSpan = myBytes.AsSpan();
var json = new Utf8JsonReader(jsonSpan);
while(json.Read()) // goes to next token
{
GetData(json);
}
// Parse
using var stream = File.OpenRead("my.json");
using var document = JsonDocument.Parse(stream);
var root = document.RootElement;
foreach(var prop in root.EnumerateObject())
{
if(prop.Value.ValueKind == JsonValueKind.Object)
...
}
// Writing
var outputOptions = new JsonWriteOptions {
Indented = true;
};
var buffer = new ArrayBufferWriter<byte>();
using var json = new Utf8JsonWriter(buffer, outputOptions);
json.WriteStartObject();
json.WritePropertyName("NAME");
json.WriteStringValue"Value");
...
json.WriteEndObject();
json.Flush();
// Deserialize
var serialOptions = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
};
var myTestObject = JsonSerializer.Deserialize<TestObject>(text,serialOptions);
// Serialize
JsonSerializer.Serialize(myTestObject, serialOptions);
Windows Desktop Support
- Only on Windows
- Supports WPF in Core
- Supports WinForms in Core
Build and Deploy Improvements
- Builds a platform application .exe and deps copied
- Can publish single file (not default)
- Trimmed only publishes used parts (not default)
- ReadyToRun Image, as Native and .Net Code (Difficult o understand what this is)
<TarghetFramework>3.0<TarghetFramework>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
Other Language Improvements
Supports Default Implementation on Interface
public interface IStarTrekWidget
{
string Captain {get; }
string WriteLog() // Not allowed prior to 8.0
{
Console.WriteLine("Captain's Log Stardate");
}
}
Using Fixed
Finally the language supports this
using var leakingResource = new LeakyResource();
Asyncronous Streams
When consuming an asynchronous stream, you need to specify the await keyword followed by the foreach keyword. You can call the method in the above example from the Main method as shown in the code snippet below.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Test
{
class Program
{
const int DELAY = 1000;
const int MIN = 1;
const int MAX = 10;
static async Task Main(string[] args)
{
await foreach (int number in GetData())
{
Console.WriteLine(number);
}
Console.ReadLine();
}
static async IAsyncEnumerable<int> GetData()
{
for (int i = MIN; i < MAX; i++)
{
yield return i;
await Task.Delay(DELAY);
}
}
}
}
Local Functions
You can add static to local function to disallow access to global, local values
public void Run()
{
var state = "42";
static string NotAllowed()
{
return state + "99";
}
}
Disposable ref Structs
You can now use the using keyword to destroy.
public unsafe ref struct UnmanagedHandle
{
private int* handle;
public UnmanagedHandle(int* handle) = this.handle = handle;
public int* GetHandle() {
return handle;
}
public void Dispose() {
ResourceManager.Free(this.handle);
}
}
Other .NET Core Platform Improvements
- Supports Intrinsics (SSE etc)
- Supports OpenSSL 1.1.1
- Floating point Support
- HTTP 2.0 protocol
- GPIO Support