C Sharp
SOLID Principles
Introduction
- SRP Single Responsibility Principle
- OCP Open Closed Principle
- LSP Liskov Substitution Principle
- ISP Interface Segregation Principle
- DIP Dependency Inversion Principle
SRP Single Responsibility Principle
Each software module should have one and only one reason to change
Cohesion
This refers to what a class or module can do. When a class has low cohesion, it means that it does a great variety of actions and in that, the class is unfocused on what it should do. High cohesion means that the class is focused on what it should be doing as it contains only methods relating to the intention of the class.
Coupling
This refers to how related or dependent two classes or modules are towards each other. For low coupled classes, a major change in one class has a low impact on the other. High coupling in a system makes it difficult to maintain since a change in one class will have impact on other classes as well. This could result in one change flowing through a system as an oil slick, sometimes even requiring a full overhaul to fully implement.
Examples of Responsibilities
In software, some examples of responsibilities that may need to be separated are the following:
- Notification
- Error handling
- Logging
- Formatting
- Parsing
- Persistence
- Validation
OCP Open Closed Principle
Software Entities (classes, modules, function etc.) should be open for extension, but closed for modification
Typical approaches to OCP is
- Parameters
- Inheritance
- Composition/Injection
A very simple example of this concept would be an old photo camera that does not allow changing of lenses. It is not open for extension and thus violates the OCP. Most modern SLR cameras have interchangeable lenses and by that extending the functionality to achieve different photographic results with different lenses. Moreover, changing a lens does not modify the camera's basic functionality to take photo's.
- Solve the problem first using concrete code
- Identify the kinds of changes the app is likely to continue to need
- Modify the code to be extensible
LSP Liskov Substitution Principle
Subtypes must be substitutable for their base types
- Subtypes must be substitutable for their base types
- Ensure the base type invariants are enforced
- Check for, Type checking, null checking and Not Implemented Exception
When this Liskov substitution principle is violated, it tends to result in a lot of extra conditional logic scattered throughout the application. This duplicate, scattered code becomes a breeding ground for bugs as the application grows. A common code smell that is often an indication of a LSP violation is the presence of type checking code within a code block that should be polymorphic.
ISP Interface Segregation Principle
DIP Dependency Inversion Principle
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