Skip to content

Commit

Permalink
Build and deploy VitePress documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sharpchen committed Nov 11, 2023
1 parent f7ff142 commit e03feac
Show file tree
Hide file tree
Showing 119 changed files with 17,463 additions and 0 deletions.
File renamed without changes.
File renamed without changes.
37 changes: 37 additions & 0 deletions assets/notes/Csharp Design Patterns/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/

# Visual Studio Code
.vscode

# Rider
.idea

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn

# Visual Studio 2015
.vs/
142 changes: 142 additions & 0 deletions assets/notes/Csharp Design Patterns/Creational/1.Singleton.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Singleton

## Concept

Generally, `Singleton` is the only instance exists during a `AppDomain` or a `Thread`.

## Basic Implementation

```csharp
class Singleton
{
private static readonly Singleton _instance = new();
public static Singleton Instance => _instance.Value;
private Singleton() { }

// ...elided
}
```

## Lazy Initialization

Lazy initialization ensures the instance won't be created until it were accessed, and `Lazy<T>` also ensures **thread safety**.
Pass a delegate to `Lazy<T>.Lazy(Func<T>)` to generate its value.

```csharp
class Singleton
{
private static readonly Lazy<Singleton> _instance = new(() => new());
public static Singleton Instance => _instance.Value;
private Singleton() { }

// ...elided
}
```

## `Singleton` is a **BAD** idea?

### Testablity issue

Singleton is hard to test, using Singleton of a type means hard coding it in your code base, not flexible enough.

- Use `interface` to abstract singleton usage to enhance your code.

### Dependency Injection

The best practice is using **Dependency Injection** to generate an singleton in runtime instead of coding on your own.
Each call to `container.Resolve<T>()` will return a shared instance of the specified type in the same thread. With this approach, we don't need to code our singleton on our own, but constructor of target type shall be `public`, or the **IOC** framework can not access it during the runtime.

- Following example uses `Autofac` to perform dependency injection.

```csharp
class OrdinaryClass
{
public OrdinaryClass() { }
// ...elided
}
public class DependencyInjectionTest
{
[Fact]
public void DependencyInjection()
{
ContainerBuilder builder = new();
builder.RegisterType<OrdinaryClass>().SingleInstance();
using var container = builder.Build();
Assert.True(container.Resolve<OrdinaryClass>() is not null);
}
}
```

## Mono-state Singleton

**Mono-state Singleton** allows you to access its constructor, but all state of this type are static, all instances shares the same states.
Not a good approach though.

```csharp
class MonoStateSingleton
{
private static int _state01 = 0x0;
private static int _state02 = 0x64;

public int State01 { get => _state01; set => _state01 = value; }
public int State02 { get => _state02; set => _state02 = value; }

// ...elided
}
```

## Thread Singleton

Use `ThreadLocal<T>` to make object thread safe and static for each thread.
Each thread has a different singleton

```csharp
class ThreadSingleton
{
private static readonly ThreadLocal<ThreadSingleton> _instance = new(() => new());
private int _threadAccessedCount;
public static ThreadSingleton Instance => _instance.Value!;
public int ThreadAccessedCount => ++_threadAccessedCount;
private ThreadSingleton() { }

//...elided
}
```

## Ambient Context

**Ambient Context** is similar to singleton pattern, one of its advantage is, it allows you to change the static value/state of the type during the procedure. And in some cases we don't have to preserve a parameter position for a method that access the AmbientContext type.
When `IDisposable` is implemented by `AmbientContext`, `using` statement will auto invoke `Dispose()`, this can be used to restore the state depending on how you implement the `Dispose()` method.

```csharp
class AmbientContext : IDisposable
{
private static int _currentState;
public static int CurrentState { get => _currentState; private set => _currentState = value; }

private static readonly ThreadLocal<AmbientContext> _context = new(() => new());
public static AmbientContext CurrentContext { get => _context.Value!; set => _context.Value = value; }
public AmbientContext(int state = 250) => _currentState = state;
public void Dispose() => _currentState = 250;

//...elided
}

public class AmbientContextTest
{
[Fact]
public void Test()
{
AmbientContext.CurrentContext = new AmbientContext();
DoSomethingUsingAmbientContext();
using (var context = new AmbientContext(500))
{
Assert.Equal(500, AmbientContext.CurrentState);
}
Assert.Equal(250, AmbientContext.CurrentState);

static void DoSomethingUsingAmbientContext() =>
Console.WriteLine($"state is {AmbientContext.CurrentState}");
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Autofac" Version="7.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.001.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpDesignPatternsDemo", "CSharpDesignPatternsDemo.csproj", "{3718B8A4-4E51-4967-B9E0-0742EE586D15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3718B8A4-4E51-4967-B9E0-0742EE586D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3718B8A4-4E51-4967-B9E0-0742EE586D15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3718B8A4-4E51-4967-B9E0-0742EE586D15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3718B8A4-4E51-4967-B9E0-0742EE586D15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C5A099B1-B1CB-4E56-BB61-3F90AD020550}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CsharpDesignPatternsDemo.Creational.Singleton;
class Singleton
{
private static readonly Lazy<Singleton> _instance = new(() => new());
public static Singleton Instance => _instance.Value;
private Singleton() { }

// ...elided
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Xunit;
using Autofac;

namespace CsharpDesignPatternsDemo.Creational.Singleton;
class OrdinaryClass
{
public OrdinaryClass() { }
// ...elided
}
public class DependencyInjectionTest
{
[Fact]
public void DependencyInjection()
{
ContainerBuilder builder = new();
builder.RegisterType<OrdinaryClass>().SingleInstance();
using var container = builder.Build();
Assert.True(container.Resolve<OrdinaryClass>() is not null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Xunit;

namespace CsharpDesignPatternsDemo.Creational.Singleton;

class MonoStateSingleton
{
private static int _state01 = 0x0;
private static int _state02 = 0x64;

public int State01 { get => _state01; set => _state01 = value; }
public int State02 { get => _state02; set => _state02 = value; }

// ...elided
}

public class MonoStateSingletonTest
{
[Fact]
public void Test()
{
var a = new MonoStateSingleton();
var b = new MonoStateSingleton();
Assert.True(a.State01 == b.State01 && a.State02 == b.State02);
a.State01 = 0xFA;
a.State02 = 0b1_1111_0100;
Assert.True(a.State01 == b.State01 && a.State02 == b.State02);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace CsharpDesignPatternsDemo.Creational.Singleton;

using Xunit;

class ThreadSingleton
{
private static readonly ThreadLocal<ThreadSingleton> _instance = new(() => new());
private int _threadAccessedCount;
public static ThreadSingleton Instance => _instance.Value!;
public int ThreadAccessedCount => ++_threadAccessedCount;
private ThreadSingleton() { }

// ...elided
}

public class ThreadSingletonTest
{
[Fact]
public async Task Test()
{
var func = () =>
{
var managedThreadId = Environment.CurrentManagedThreadId;
var singletonId = ThreadSingleton.Instance.ThreadAccessedCount;
Console.WriteLine($"Message from thread{managedThreadId} where singleton id is {singletonId}");
return (managedThreadId, singletonId);
};
var t1 = Task.Run(func);
var t2 = Task.Run(func);
var a = await Task.WhenAll(t1, t2);
Assert.True(a[0].managedThreadId != a[1].managedThreadId && a[0].singletonId == a[1].singletonId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Xunit;
namespace CsharpDesignPatternsDemo.Creational.Singleton;

class AmbientContext : IDisposable
{
private static int _currentState;
public static int CurrentState { get => _currentState; private set => _currentState = value; }

private static readonly ThreadLocal<AmbientContext> _context = new(() => new());
public static AmbientContext CurrentContext { get => _context.Value!; set => _context.Value = value; }
public AmbientContext(int state = 250) => _currentState = state;
public void Dispose() => _currentState = 250;

//...elided
}


public class AmbientContextTest
{
[Fact]
public void Test()
{
AmbientContext.CurrentContext = new AmbientContext();
DoSomethingUsingAmbientContext();
using (var context = new AmbientContext(500))
{
Assert.Equal(500, AmbientContext.CurrentState);
}
Assert.Equal(250, AmbientContext.CurrentState);

static void DoSomethingUsingAmbientContext() =>
Console.WriteLine($"state is {AmbientContext.CurrentState}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading

0 comments on commit e03feac

Please sign in to comment.