Category: .NET Core

RSCG – ServiceScan.SourceGenerator

RSCG – ServiceScan.SourceGenerator
 
 

name ServiceScan.SourceGenerator
nuget https://www.nuget.org/packages/ServiceScan.SourceGenerator/
link https://github.com/Dreamescaper/ServiceScan.SourceGenerator
author Oleksandr Liakhevych

Generating service collection / DI registration

 

This is how you can use ServiceScan.SourceGenerator .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="ServiceScan.SourceGenerator" Version="1.1.2">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
    </ItemGroup>
</Project>

The code that you will use is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
using InjectDemo;
using Microsoft.Extensions.DependencyInjection;
var sc=new ServiceCollection();
sc.AddMyServices();
var sp=sc.BuildServiceProvider();
var con = sp.GetService(typeof(Database)) as IDatabase;
ArgumentNullException.ThrowIfNull(con);
con.Open();
 
 
 
public static partial class MyServiceProvider
{
    [ServiceScan.SourceGenerator.GenerateServiceRegistrations(AssignableTo = typeof(Database),AsSelf =true, Lifetime = ServiceLifetime.Scoped)]
 
    [ServiceScan.SourceGenerator.GenerateServiceRegistrations(AssignableTo = typeof(IDatabase), Lifetime = ServiceLifetime.Scoped)]
    public static partial IServiceCollection AddMyServices(this IServiceCollection services)
    ;
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
namespace InjectDemo;
 
partial class Database : IDatabase
{
    private readonly IDatabase con;
 
    public Database(IDatabase con)
    {
        this.con = con;
    }
    public void Open()
    {
        Console.WriteLine($"open from database");
        con.Open();
    }
 
}
01
02
03
04
05
06
07
08
09
10
namespace InjectDemo;
 
public partial class DatabaseCon:IDatabase
{
    public string? Connection { get; set; }
    public void Open()
    {
        Console.WriteLine("open from database con" );
    }
}

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#nullable enable
 
using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
 
namespace ServiceScan.SourceGenerator;
 
[Conditional("CODE_ANALYSIS")]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
internal class GenerateServiceRegistrationsAttribute : Attribute
{
    /// <summary>
    /// Set the assembly containing the given type as the source of types to register.
    /// If not specified, the assembly containing the method with this attribute will be used.
    /// </summary>
    public Type? FromAssemblyOf { get; set; }
 
    /// <summary>
    /// Set the type that the registered types must be assignable to.
    /// Types will be registered with this type as the service type,
    /// unless <see cref="AsImplementedInterfaces"/> or <see cref="AsSelf"/> is set.
    /// </summary>
    public Type? AssignableTo { get; set; }
 
    /// <summary>
    /// Set the lifetime of the registered services.
    /// <see cref="ServiceLifetime.Transient"/> is used if not specified.
    /// </summary>
    public ServiceLifetime Lifetime { get; set; }
 
    /// <summary>
    /// If set to true, types will be registered as implemented interfaces instead of their actual type.
    /// </summary>
    public bool AsImplementedInterfaces { get; set; }
 
    /// <summary>
    /// If set to true, types will be registered with their actual type.
    /// It can be combined with <see cref="AsImplementedInterfaces"/>, in that case implemeted interfaces will be
    /// "forwarded" to "self" implementation.
    /// </summary>
    public bool AsSelf { get; set; }
 
    /// <summary>
    /// Set this value to filter the types to register by their full name.
    /// You can use '*' wildcards.
    /// You can also use ',' to separate multiple filters.
    /// </summary>
    /// <example>Namespace.With.Services.*</example>
    /// <example>*Service,*Factory</example>
    public string? TypeNameFilter { get; set; }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
using Microsoft.Extensions.DependencyInjection;
 
 
 
public static partial class MyServiceProvider
{
    public static partial IServiceCollection AddMyServices(this IServiceCollection services)
    {
        return services
            .AddScoped<InjectDemo.Database, InjectDemo.Database>()
            .AddScoped<InjectDemo.IDatabase, InjectDemo.Database>()
            .AddScoped<InjectDemo.IDatabase, InjectDemo.DatabaseCon>();
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/ServiceScan.SourceGenerator

RSCG – ThisAssembly.Strings

RSCG – ThisAssembly.Strings
 
 

name ThisAssembly.Strings
nuget https://www.nuget.org/packages/ThisAssembly.Strings/
link https://github.com/devlooped/ThisAssembly
author Daniel Cazzulino

generating code from resx files

 

This is how you can use ThisAssembly.Strings .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="ThisAssembly.Strings" Version="1.4.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
 
  <ItemGroup>
    <Compile Update="Demo.Designer.cs">
      <DesignTime>True</DesignTime>
      <AutoGen>True</AutoGen>
      <DependentUpon>Demo.resx</DependentUpon>
    </Compile>
  </ItemGroup>
 
  <ItemGroup>
    <EmbeddedResource Update="Demo.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Demo.Designer.cs</LastGenOutput>
    </EmbeddedResource>
  </ItemGroup>
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
</Project>

The code that you will use is

1
Console.WriteLine(ThisAssembly.Strings.PersonName("Andrei Ignat"));
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema
     
    Version 2.0
     
    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.
     
    Example:
     
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
                 
    There are any number of "resheader" rows that contain simple
    name/value pairs.
     
    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.
     
    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:
     
    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.
     
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
     
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
 
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <data name="PersonName" xml:space="preserve">
    <value>The person name is {0}</value>
    <comment>the person name</comment>
  </data>
</root>

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     ThisAssembly.Strings: 1.4.3
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Globalization;
 
partial class ThisAssembly
{
    public static partial class Strings
    {
        /// <summary>
        /// the person name
        /// </summary>
        public static string PersonName(object arg0) => string.Format(CultureInfo.CurrentCulture, Strings.GetResourceManager("StringsDemo.Demo").GetString("PersonName"), arg0);
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Collections.Concurrent;
using System.Resources;
using System.Runtime.CompilerServices;
 
/// <summary>
/// Provides access to the current assembly information as pure constants,
///  without requiring reflection.
/// </summary>
partial class ThisAssembly
{
    /// <summary>
    /// Access the strings provided by resource files in the project.
    /// </summary>
    [CompilerGenerated]
    public static partial class Strings
    {
        static ConcurrentDictionary<string, ResourceManager> resourceManagers = new ConcurrentDictionary<string, ResourceManager>();
 
        static ResourceManager GetResourceManager(string resourceName)
            => resourceManagers.GetOrAdd(resourceName, name => new ResourceManager(name, typeof(Strings).Assembly));
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly.Strings

NetPackageAnalyzer- idea –part 1

I wanted to have a tool that can analyze a C# solution with all the dependencies ( project relations, packages, commits)

It generates a graph of the dependencies for

  • the packages of your solution ( and see when the major version of a package differs between projects)

  • the projects of your solution.
  • the project dependencies of each project.

Along with the graph, it also generates

  • a report for the commits of the solution per years.

  • a report for outdated/deprecated packages per solution.
  • building blocks for the solution – you can start from here to understand the solution

If you want to see how it looks like, you can see how he analyses himself at Analyzer

RSCG – ActorSrcGen

 
 

name ActorSrcGen
nuget https://www.nuget.org/packages/ActorSrcGen/
link https://github.com/aabs/ActorSrcGen
author Andrew Matthews

Generating source code for actors in C#.

 

This is how you can use ActorSrcGen .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="ActorSrcGen" Version="1.1.2" />
    <PackageReference Include="ActorSrcGen.Abstractions" Version="1.1.2" />
    <PackageReference Include="Gridsum.DataflowEx" Version="2.0.0" />
  </ItemGroup>
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
</Project>

The code that you will use is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// See https://aka.ms/new-console-template for more information
using ActorDemo;
using Gridsum.DataflowEx;
 
Person person = new Person { Name = "Andrei Ignat" };
 
DayWorkflow dayAndreiIgnat = new ();
var input = dayAndreiIgnat.InputBlock;
//async
await dayAndreiIgnat.SendAsync(person);
//sync
while (dayAndreiIgnat.Call(person))
{
    await Task.Delay(100);
}
 
Console.WriteLine("Done");
Console.ReadLine();
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using ActorSrcGen;
using System.Diagnostics.Metrics;
 
namespace ActorDemo;
[Actor]
partial class DayWorkflow
{
    [FirstStep("StartDay")]
    //[Receiver]
    [NextStep(nameof(WashFace))]
    [NextStep(nameof(LogMessage))]
    public async Task<Person> StartDay(Person p)
    {
        await Task.Delay(1000 );
        return p;
    }
 
     
 
    [Step]
    [NextStep(nameof(LogMessage))]
    [NextStep(nameof(Eat))]
    public async Task<Person> WashFace(Person p)
    {
        await Task.Delay(1000);
        return p;
    }
     
 
 
    [Step]
    [NextStep(nameof(LogMessage))]
    [NextStep(nameof(Sleep))]
    public async Task<Person> Eat(Person p)
    {
        await Task.Delay(1000);
        return p;
    }
     
 
    [NextStep(nameof(LogMessage))]
    public async Task<int> Sleep(Person p)
    {
        await Task.Delay(1000);
        return p.Name.Length;
    }
 
    [LastStep]
    public void LogMessage(Person x)
    {
        Console.WriteLine("Incoming Message: " + x?.Name);
    }
 
 
}
1
2
3
4
5
namespace ActorDemo;
public class Person
{
    public string Name { get; set; }
}

 

The code that is generated is

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Generated on 2024-06-02
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS0108 // hides inherited member.
 
using ActorSrcGen;
using System.Diagnostics.Metrics;
namespace ActorDemo;
using System.Threading.Tasks.Dataflow;
using Gridsum.DataflowEx;
public partial class DayWorkflow : Dataflow<Person>, IActor<Person>
{
    public DayWorkflow() : base(DataflowOptions.Default)
    {
        _LogMessage = new ActionBlock<Person>(        (Person x) => {
            try
            {
                LogMessage(x);
            }catch{}
        },
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_LogMessage);
        _Eat = new TransformManyBlock<Person,Person>(       async (Person x) => {
           var result = new List<Person>();
           try
           {
               var newValue = await Eat(x);
               result.Add(newValue);
           }catch{}
           return result;
       },
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_Eat);
        _EatBC = new BroadcastBlock<Person>(    (Person x) => x,
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_EatBC);
        _WashFace = new TransformManyBlock<Person,Person>(       async (Person x) => {
           var result = new List<Person>();
           try
           {
               var newValue = await WashFace(x);
               result.Add(newValue);
           }catch{}
           return result;
       },
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_WashFace);
        _WashFaceBC = new BroadcastBlock<Person>(    (Person x) => x,
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_WashFaceBC);
        _StartDay = new TransformManyBlock<Person,Person>(       async (Person x) => {
           var result = new List<Person>();
           try
           {
               var newValue = await StartDay(x);
               result.Add(newValue);
           }catch{}
           return result;
       },
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_StartDay);
        _StartDayBC = new BroadcastBlock<Person>(    (Person x) => x,
            new ExecutionDataflowBlockOptions() {
                BoundedCapacity = 5,
                MaxDegreeOfParallelism = 8
        });
        RegisterChild(_StartDayBC);
        _Eat.LinkTo(_EatBC, new DataflowLinkOptions { PropagateCompletion = true });
        _EatBC.LinkTo(_LogMessage, new DataflowLinkOptions { PropagateCompletion = true });
        _WashFace.LinkTo(_WashFaceBC, new DataflowLinkOptions { PropagateCompletion = true });
        _WashFaceBC.LinkTo(_LogMessage, new DataflowLinkOptions { PropagateCompletion = true });
        _WashFaceBC.LinkTo(_Eat, new DataflowLinkOptions { PropagateCompletion = true });
        _StartDay.LinkTo(_StartDayBC, new DataflowLinkOptions { PropagateCompletion = true });
        _StartDayBC.LinkTo(_LogMessage, new DataflowLinkOptions { PropagateCompletion = true });
        _StartDayBC.LinkTo(_WashFace, new DataflowLinkOptions { PropagateCompletion = true });
    }
 
    ActionBlock<Person> _LogMessage;
 
    TransformManyBlock<Person,Person> _Eat;
 
    BroadcastBlock<Person> _EatBC;
 
    TransformManyBlock<Person,Person> _WashFace;
 
    BroadcastBlock<Person> _WashFaceBC;
 
    TransformManyBlock<Person,Person> _StartDay;
 
    BroadcastBlock<Person> _StartDayBC;
    public override ITargetBlock<Person> InputBlock { get => _StartDay; }
    public bool Call(Person input)
        => InputBlock.Post(input);
 
    public async Task<bool> Cast(Person input)
        => await InputBlock.SendAsync(input);
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/ActorSrcGen

Pattern: AbstractFactory

Description

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

Example in .NET :

AbstractFactory

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
using Microsoft.Data.SqlClient;
using System.Data.Common;
 
namespace AbstractFactory;
internal class AbstractFactoryDemo
{
    public static void Demo()
    {
        //create DbConnection factory by using the instance of SqlConnection
        DbConnection connection = new SqlConnection();
        //create DbCommand instance by using the instance of SqlConnection
        DbCommand command = connection.CreateCommand();
        //really, the DBCommand is a SqlCommand
        SqlCommand? sqlCommand = command as SqlCommand;
        //check if the DbCommand is a SqlCommand
        Console.WriteLine($"DbCommand is SqlCommand: {sqlCommand != null}");
    }
}

Learn More

Source Code for Microsoft implementation of AbstractFactory

SourceCode DbConnection

Learn More

Wikipedia

}

Homework

Imagine you want to produce loggers. You have a logger that logs to a file and a logger that logs to a console and a Nothing Logger – a logger that does nothing. Implement an abstract factory that will allow you to create a logger factory that will create a logger that logs to a file or to a console or nothing.

Pattern: Flyweight

Description

Flyweight pattern is used to reduce the memory and resource usage for complex models containing a large number of similar objects.

Example in .NET :

Flyweight

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
using System.Text;
 
namespace Flyweight;
internal class FlyweightDemo
{
    public static void Demo()
    {
        var str = "Andrei Ignat";
        var str2 = string.Intern(str);
        var str3 = new StringBuilder("Andrei").Append(" Ignat").ToString();
        Console.WriteLine($"str == str2: Value {str==str2} Reference {Object.ReferenceEquals(str,str2)}");
        Console.WriteLine($"str == str3: Value {str==str3} Reference {Object.ReferenceEquals(str,str3)}");
 
    }
}

Learn More

Source Code for Microsoft implementation of Flyweight

SourceCode String.Intern

Learn More

Wikipedia

}

Homework

Make an exchange rate system. The symbol and names of the currency are the same for all the currencies. The exchange rate is different for each currency. Implement a flyweight that will allow you to create a currency with a symbol and a name and to get the exchange rate for the currency.

Pattern: Observer

Description

Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Example in .NET :

Observer

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.ComponentModel;
using System.Runtime.CompilerServices;
 
namespace Observer;
 
/// <summary>
/// INotifyPropertyChanged is an interface that provides a mechanism for the object to notify clients that a property value has changed.
/// </summary>
public class Person: INotifyPropertyChanged
{
    private string name=string.Empty;
    public string Name
    {
        get => name;
        set
        {
            if (name == value) return;
            name = value;
            OnPropertyChanged();
        }
    }
 
    public event PropertyChangedEventHandler? PropertyChanged;
 
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
internal class ObserverDemo
{
    public static void Demo()
    {
        Person person = new ();
        //subscribe to the event to observe the changes
        person.PropertyChanged += (sender, args) =>
        {
            var p = sender as Person;
            Console.WriteLine($"Property {args.PropertyName} changed to {p?.Name}");
        };
        person.Name = "Andrei Ignat" ;
    }
}

Learn More

Source Code for Microsoft implementation of Observer

SourceCode INotifyPropertyChanged

Learn More

Wikipedia

}

Homework

Imagine you have a logger that logs to a file and to a console. Implement an observable logger that will allow you to subscribe to the logger and to be notified when the logger logs a message.

RSCG – MSTest

name MSTest
nuget https://www.nuget.org/packages/MSTest.SourceGeneration/
link https://github.com/microsoft/testfx
author Microsoft

AOP for MSTest

 

This is how you can use MSTest .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- file: UnitTestProject1.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
 
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
 
        <OutputType>exe</OutputType>
        <PublishAot>true</PublishAot>
    </PropertyGroup>
 
    <ItemGroup>
        <!--
      Experimental MSTest Engine & source generator,
      close sourced, licensed the same as our extensions
      with Microsoft Testing Platform Tools license.
    -->
        <PackageReference Include="MSTest.Engine" Version="1.0.0-alpha.24163.4" />
        <PackageReference Include="MSTest.SourceGeneration" Version="1.0.0-alpha.24163.4" />
 
        <PackageReference Include="Microsoft.CodeCoverage.MSBuild" Version="17.10.4" />
        <PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.10.4" />
 
        <PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="1.0.2" />
        <PackageReference Include="Microsoft.Testing.Platform.MSBuild" Version="1.0.2" />
        <PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
        <PackageReference Include="MSTest.Analyzers" Version="3.2.2" />
 
    </ItemGroup>
 
    <ItemGroup>
        <ProjectReference Include="..\MyImportantClass\MyImportantClass.csproj" />
    </ItemGroup>
 
    <ItemGroup>
        <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
    </ItemGroup>
 
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
 
</Project>

The code that you will use is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// file: UnitTest1.cs
using MyImportantClass;
 
namespace DemoTest;
 
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(3, new Class1().Add(1, 2));
    }
 
    [TestMethod]
    [DataRow(1, 2)]
    [DataRow(100, -97)]
    public void TestMethod2(int left, int right)
    {
        Assert.AreEqual(3, new Class1().Add(left, right));
    }
}

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by Microsoft Testing Framework Generator.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace DemoTest
{
    using Threading = global::System.Threading;
    using ColGen = global::System.Collections.Generic;
    using CA = global::System.Diagnostics.CodeAnalysis;
    using Sys = global::System;
    using Msg = global::Microsoft.Testing.Platform.Extensions.Messages;
    using MSTF = global::Microsoft.Testing.Framework;
 
    [CA::ExcludeFromCodeCoverage]
    public static class DemoTest_UnitTest1
    {
        public static readonly MSTF::TestNode TestNode = new MSTF::TestNode
        {
            StableUid = "DemoTest.DemoTest.UnitTest1",
            DisplayName = "UnitTest1",
            Properties = new Msg::IProperty[1]
            {
                new Msg::TestFileLocationProperty(@"D:\gth\RSCG_Examples\v2\rscg_examples\MSTest\src\DemoTest\UnitTest1.cs", new(new(6, -1), new(22, -1))),
            },
            Tests = new MSTF::TestNode[]
            {
                new MSTF::InternalUnsafeActionTestNode
                {
                    StableUid = "DemoTest.DemoTest.UnitTest1.TestMethod1()",
                    DisplayName = "TestMethod1",
                    Properties = new Msg::IProperty[2]
                    {
                        new Msg::TestMethodIdentifierProperty(
                            "DemoTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                            "DemoTest",
                            "UnitTest1",
                            "TestMethod1",
                            Sys::Array.Empty<string>(),
                            "System.Void"),
                        new Msg::TestFileLocationProperty(@"D:\gth\RSCG_Examples\v2\rscg_examples\MSTest\src\DemoTest\UnitTest1.cs", new(new(9, -1), new(13, -1))),
                    },
                    Body = static testExecutionContext =>
                    {
                        var instance = new UnitTest1();
                        try
                        {
                            instance.TestMethod1();
                        }
                        catch (global::System.Exception ex)
                        {
                            testExecutionContext.ReportException(ex, null);
                        }
                    },
                },
                new MSTF::InternalUnsafeActionParameterizedTestNode<MSTF::InternalUnsafeTestArgumentsEntry<(int left, int right)>>
                {
                    StableUid = "DemoTest.DemoTest.UnitTest1.TestMethod2(int, int)",
                    DisplayName = "TestMethod2",
                    Properties = new Msg::IProperty[2]
                    {
                        new Msg::TestMethodIdentifierProperty(
                            "DemoTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                            "DemoTest",
                            "UnitTest1",
                            "TestMethod2",
                            new string[2]
                            {
                                "System.Int32",
                                "System.Int32",
                            },
                            "System.Void"),
                        new Msg::TestFileLocationProperty(@"D:\gth\RSCG_Examples\v2\rscg_examples\MSTest\src\DemoTest\UnitTest1.cs", new(new(15, -1), new(21, -1))),
                    },
                    GetArguments = static () => new MSTF::InternalUnsafeTestArgumentsEntry<(int left, int right)>[]
                    {
                        new MSTF::InternalUnsafeTestArgumentsEntry<(int left, int right)>((1, 2), "left: 1, right: 2"),
                        new MSTF::InternalUnsafeTestArgumentsEntry<(int left, int right)>((100, -97), "left: 100, right: -97"),
                    },
                    Body = static (testExecutionContext, data) =>
                    {
                        var instance = new UnitTest1();
                        try
                        {
                            instance.TestMethod2(data.Arguments.left, data.Arguments.right);
                        }
                        catch (global::System.Exception ex)
                        {
                            testExecutionContext.ReportException(ex, null);
                        }
                    },
                },
            },
        };
    }
}
01
02
03
04
05
06
07
08
09
10
11
namespace Microsoft.Testing.Framework.SourceGeneration
{
    public static class SourceGeneratedTestingPlatformBuilderHook
    {
        public static void AddExtensions(Microsoft.Testing.Platform.Builder.ITestApplicationBuilder testApplicationBuilder, string[] _)
        {
            testApplicationBuilder.AddTestFramework(new Microsoft.Testing.Framework.Configurations.TestFrameworkConfiguration(System.Environment.ProcessorCount),
                new DemoTest.SourceGeneratedTestNodesBuilder());
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by Microsoft Testing Framework Generator.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace DemoTest
{
    using DemoTest;
    using ColGen = global::System.Collections.Generic;
    using CA = global::System.Diagnostics.CodeAnalysis;
    using Sys = global::System;
    using Tasks = global::System.Threading.Tasks;
    using Msg = global::Microsoft.Testing.Platform.Extensions.Messages;
    using MSTF = global::Microsoft.Testing.Framework;
    using Cap = global::Microsoft.Testing.Platform.Capabilities.TestFramework;
    using TrxReport = global::Microsoft.Testing.Extensions.TrxReport.Abstractions;
 
    [CA::ExcludeFromCodeCoverage]
    public sealed class SourceGeneratedTestNodesBuilder : MSTF::ITestNodesBuilder
    {
        private sealed class ClassCapabilities : TrxReport::ITrxReportCapability
        {
            bool TrxReport::ITrxReportCapability.IsSupported { get; } = true;
            void TrxReport::ITrxReportCapability.Enable() {}
        }
 
        public ColGen::IReadOnlyCollection<Cap::ITestFrameworkCapability> Capabilities { get; } = new Cap::ITestFrameworkCapability[1] { new ClassCapabilities() };
 
        public Tasks::Task<MSTF::TestNode[]> BuildAsync(MSTF::ITestSessionContext testSessionContext)
        {
            ColGen::List<MSTF::TestNode> namespace1Tests = new();
            namespace1Tests.Add(DemoTest_UnitTest1.TestNode);
 
            MSTF::TestNode root = new MSTF::TestNode
            {
                StableUid = "DemoTest",
                DisplayName = "DemoTest",
                Properties = Sys::Array.Empty<Msg::IProperty>(),
                Tests = new MSTF::TestNode[]
                {
                    new MSTF::TestNode
                    {
                        StableUid = "DemoTest.DemoTest",
                        DisplayName = "DemoTest",
                        Properties = Sys::Array.Empty<Msg::IProperty>(),
                        Tests = namespace1Tests.ToArray(),
                    },
                },
            };
 
            return Tasks::Task.FromResult(new MSTF::TestNode[1] { root });
        }
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/MSTest

RSCG – Minerals.AutoMixins

 
 

name Minerals.AutoMixins
nuget https://www.nuget.org/packages/Minerals.AutoMixins/
link https://github.com/SzymonHalucha/Minerals.AutoMixins
author Szymon Halucha

Generate Mixin from another classes

 

This is how you can use Minerals.AutoMixins .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="Minerals.AutoMixins" Version="0.2.1" />
  </ItemGroup>
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
</Project>

The code that you will use is

1
2
3
4
using DemoMixin;
 
Person person = new Person { Name = "Andrei Ignat" };
person.LogName();
1
2
3
4
5
6
7
namespace DemoMixin;
[Minerals.AutoMixins.AddMixin(typeof(LogData))]
internal partial class Person
{
    public string Name { get; set; }
    public void LogName() => Log(Name);
}
1
2
3
4
5
6
namespace DemoMixin;
[Minerals.AutoMixins.GenerateMixin]
internal class LogData
{
    public void Log(string data) => Console.WriteLine(data);
}

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// <auto-generated>
// This code was generated by a tool.
// Name: Minerals.AutoMixins
// Version: 0.2.1+6c5634e46b130effbe00bd9d3f94459f1dbb2e85
// </auto-generated>
 
namespace DemoMixin
{
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Runtime.CompilerServices.CompilerGenerated]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    internal partial class Person
    {
        // MixinType: LogData
        public void Log(string data) => Console.WriteLine(data);
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// <auto-generated>
// This code was generated by a tool.
// Name: Minerals.AutoMixins
// Version: 0.2.1+6c5634e46b130effbe00bd9d3f94459f1dbb2e85
// </auto-generated>
#pragma warning disable CS9113
namespace Minerals.AutoMixins
{
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Runtime.CompilerServices.CompilerGenerated]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
    public sealed class AddMixinAttribute : global::System.Attribute
    {
        public AddMixinAttribute(params global::System.Type[] mixins)
        {
        }
    }
}
#pragma warning restore CS9113
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// <auto-generated>
// This code was generated by a tool.
// Name: Minerals.AutoMixins
// Version: 0.2.1+6c5634e46b130effbe00bd9d3f94459f1dbb2e85
// </auto-generated>
namespace Minerals.AutoMixins
{
    [global::System.Diagnostics.DebuggerNonUserCode]
    [global::System.Runtime.CompilerServices.CompilerGenerated]
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
    public sealed class GenerateMixinAttribute : global::System.Attribute
    {
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Minerals.AutoMixins

RSCG – ThisClass

 
 

name ThisClass
nuget https://www.nuget.org/packages/ThisClass/
link https://github.com/trympet/ThisClass
author Trym Lund Flogard

Generate full class name from class

 

This is how you can use ThisClass .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<Project Sdk="Microsoft.NET.Sdk">
 
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
 
    <ItemGroup>
        <PackageReference Include="ThisClass" Version="1.5.11" />
    </ItemGroup>
    <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
 
</Project>

The code that you will use is

1
2
3
4
5
6
using DemoClass;
 
Person person = new Person();
person.Name = "Andrei Ignat";
Console.WriteLine(person.Name);
Console.WriteLine(Person.ThisClass.FullName);
1
2
3
4
5
6
7
8
namespace DemoClass;
[ThisClass]
internal partial class Person
{
    public string Name { get; set; }
 
    public string ClassName => ThisClass.FullName;
}

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// <auto-generated/>
#pragma warning disable CS0108, CS1591, CS1573, CS0465, CS0649, CS8019, CS1570, CS1584, CS1658, CS0436, CS8981
#nullable enable
namespace DemoClass;
partial class Person
{
    public static partial class ThisClass
    {
        /// <summary>
        /// Gets the fully qualified name of the parent class, including the namespace but not the assembly.
        /// </summary>
        public const string FullName = "DemoClass.Person";
    }
}
1
2
3
4
5
6
7
// <auto-generated/>
#pragma warning disable CS1591,CS1573,CS0465,CS0649,CS8019,CS1570,CS1584,CS1658,CS0436,CS8981
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)]
sealed partial class ThisClassAttribute : Attribute
{
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisClass

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.