Introduction in Roslyn Code Generators & Building Your Own Search Assistant

Azi la 19:30 avem o noua intilnire ADCES

Presentation 1 : Introduction in Roslyn Code Generators
Description:
Presenter : Ignat Andrei, http://msprogrammer.serviciipeweb.ro/
Presentation 2: # Building Your Own Search Assistant: A Hands-On Guide to Internet-Connected AI Tools
Description:
Ever marveled at Microsoft’s Copilot knack for discussing up-to-the-minute topics, despite being trained on data up to only September 2021? Curious about the magic that goes on under the hood?
Join Vlad as he peels back the curtain on the innovative techniques used to create a search assistant that can converse about current events. This interactive session will guide you through the entire process, exploring methods like fine-tuning, retrieval augmented generation, and tackling challenges such as slow model inference and context window limitations.
Expect a live demonstration of a simple yet effective application and walk away with the knowledge to craft your very own assistant using just Python, pandas, and the Azure OpenAI APIs. Don’t miss the chance to move from concept to code in this practical deep dive!

Presenter: Vlad Iliescu, https://vladiliescu.net/

Va astept aici: https://www.meetup.com/bucharest-a-d-c-e-s-meetup/events/298422420/

Comparing EFCore Database Providers-part-1

IDName
1 Part 1
2 Part 2
3 Part 3

I wanted to see if there are any differences in EFCore database providers listed at 

https://learn.microsoft.com/en-us/ef/core/providers/?tabs=dotnet-core-cli

I want to test the capabilities for each one within a standard choice of tables , in order to know the capabilities

I choose only those that have a version for current STS / LTS , whatever it is current.

( I am particularly interested in SqlServer vs Sqlite )

Problem 1: Conflicting namespaces

For MySql – there are 2 providers , Pomelo.EntityFrameworkCore.MySql  and MySql.EntityFrameworkCore  . Both have the same namespace and class for .UseMySql  ( and other stuff)

So how to do it ? Fortunately, nuget supports alias .

So the code in csproj is

<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" Aliases="PomeloEFMySql"  />
<PackageReference Include="MySqlConnector" Version="2.2.5" Aliases="MySqlConnect" />

<PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.5" Aliases="MySqlEFOracle" />
<PackageReference Include="MySql.Data" Version="8.1.0" Aliases="OracleMySql"/>
		

And the code in global.cs

extern alias OracleMySql;

extern alias PomeloEFMySql;
extern alias MySqlConnect;

global using MySqlCNBOracle = MySqlEFOracle.Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions;
global using MySqlOracle = OracleMySql.MySql.Data.MySqlClient;
global using MySqlEF = MySqlEFOracle::Microsoft.EntityFrameworkCore;
 
global using PomeloCN= MySqlConnect::MySqlConnector;
global using PomeloEF = PomeloEFMySql::Microsoft.EntityFrameworkCore;
global using PomeloMySqlCNB =PomeloEFMySql::Microsoft.EntityFrameworkCore.MySqlDbContextOptionsBuilderExtensions;

And the code to use it

case EFCoreProvider.Pomelo_EntityFrameworkCore_MySql:

    var serverVersion = PomeloEF.ServerVersion.AutoDetect(con);
    StepExecution.Current.Comment("version " + serverVersion.ToString());
    PomeloMySqlCNB.UseMySql(builder,con, serverVersion)
        .EnableSensitiveDataLogging()
        .EnableDetailedErrors();
    
    break;
case EFCoreProvider.MySql_EntityFrameworkCore:
    MySqlCNBOracle.UseMySQL(builder,con);
    break;

You can find the results at https://github.com/ignatandrei/TestEFCoreDatabaseProviders and https://ignatandrei.github.io/TestEFCoreDatabaseProviders/

Problem 2 : Conflict on container ports

When a container is started for a test it works on a port ( 1433 for SqlServer). When a new test arrives ( with new tables ) , it cannot be on the same port . So the docker containers must be disposed when the test finishes. Also , the tests must be done in serial, not in paralel.

For parallelism, it is simple ( LightBDD + XUnit)

[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: ClassCollectionBehavior(AllowTestParallelization = false)]

For disposing, can use IScenarioTearDown (LightBDD) or IAsyncLifetime (XUnit)

RSCG – Farskeptic.AutoCompose

RSCG – Farskeptic.AutoCompose
 
 

name Farskeptic.AutoCompose
nuget https://www.nuget.org/packages/Farskeptic.AutoCompose/
link https://github.com/farskeptic/AutoCompose
author farskeptic/jmagel

Generating decorators for classes that implements interfaces.

 

This is how you can use Farskeptic.AutoCompose .

The code that you start with is


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Farskeptic.AutoCompose" Version="1.0.1" />
  </ItemGroup>
	<PropertyGroup>
		<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
		<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
	</PropertyGroup>
</Project>


The code that you will use is


using Decorator;

ICoffee c = new Coffee();
c = new CoffeeWithLogging(c);
await c.Prepare();
var ingredients = c.GetIngredients();


namespace Decorator;

internal class Coffee : ICoffee
{
    public string? Name { get; set; }
    public async Task<bool> Prepare()
    {
        Console.WriteLine("start prepare coffee");
        await Task.Delay(1000);
        Console.WriteLine("finish prepare coffee");
        return true;
    }
    public string[] GetIngredients() => new[] { "water", "coffee" };

}




namespace Decorator;
internal interface ICoffee
{
    Task<bool> Prepare();

    string[] GetIngredients();
}




using AutoCompose.Generator.Attributes;

namespace Decorator;

[AutoCompose(typeof(ICoffee), nameof(_cof))]
internal partial class CoffeeWithLogging : ICoffee
{
    protected ICoffee _cof;

    public CoffeeWithLogging(ICoffee cof)
    {
        this._cof = cof;
    }
    public string[] GetIngredients()
    {
        Console.WriteLine("CoffeeWithLogging.GetIngredients");
        return this._cof.GetIngredients();
    }
}

 

The code that is generated is

// <auto-generated> 
// WARNING: THIS CODE IS AUTO-GENERATED AT COMPILE-TIME.  ANY CHANGES WILL BE OVERWRITTEN ON NEXT COMPILE.
// </auto-generated> 



namespace Decorator
{
    internal partial class CoffeeWithLogging
    {


        public virtual Task<bool> Prepare()
        {
            return _cof.Prepare();
        }


    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Farskeptic.AutoCompose

RSCG – TypeUtilities

RSCG – TypeUtilities
 
 

name TypeUtilities
nuget https://www.nuget.org/packages/TypeUtilities/
link https://github.com/DragonsLord/TypeUtilities
author Yevhenii Serdiuk

Pick/Omit for classes ( also have some mapping )

 

This is how you can use TypeUtilities .

The code that you start with is


<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="TypeUtilities" Version="0.0.1" />
	</ItemGroup>
</Project>


The code that you will use is


using UtilDemo;

var p=new PersonFull();
p.FirstName="Andrei";
p.LastName="Ignat";
//Person1 p1=(Person1)p ;
//Person2 p2=(Person2)p ;
Person1 p1 = new();
p1.FirstName = p.FirstName;

Person2 p2=new();
p2.LastName = p.LastName;


Console.WriteLine(p1.FirstName);
Console.WriteLine(p2.LastName);


using TypeUtilities;
using static TypeUtilities.Abstractions.MemberDeclarationFormats;
using TypeUtilities.Abstractions;

namespace UtilDemo;
public class PersonFull
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public int Salary { get; set; }

}

[Map(typeof(PersonFull),
      MemberDeclarationFormat = $"{Tokens.Accessibility} string Mapped{Tokens.Name}{Tokens.Accessors}",
      MemberKindSelection = MemberKindFlags.AnyProperty
    )]
[Omit(typeof(PersonFull), nameof(PersonFull.Salary))]
public partial class Person2
{
    
}

[Pick(typeof(PersonFull), nameof(PersonFull.FirstName), nameof(PersonFull.LastName))]
public partial class Person1
{
    
}



 

The code that is generated is

namespace UtilDemo;

public partial class Person1
{
	public string? FirstName { get; set; }
	public string? LastName { get; set; }
}

namespace UtilDemo;

public partial class Person2
{
	public string MappedFirstName { get; set; }
	public string MappedLastName { get; set; }
	public string MappedSalary { get; set; }
}

namespace UtilDemo;

public partial class Person2
{
	public string? FirstName { get; set; }
	public string? LastName { get; set; }
}

Code and pdf at

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

RSCG – LinqGen.Generator

RSCG – LinqGen.Generator
 
 

name LinqGen.Generator
nuget https://www.nuget.org/packages/LinqGen.Generator/
https://www.nuget.org/packages/LinqGen/
link https://github.com/cathei/LinqGen
author Maxwell Keonwoo Kang

No-alloc for Linq operations

 

This is how you can use LinqGen.Generator .

The code that you start with is


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="LinqGen" Version="0.3.1" />
    <PackageReference Include="LinqGen.Generator" Version="0.3.1" />
  </ItemGroup>
	<PropertyGroup>
		<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
		<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
	</PropertyGroup>
</Project>


The code that you will use is


using Cathei.LinqGen;
int[] a= [1,2,3];
var s = a
    .Select(x => x * x)
    .Where(it => it < 8)
    .Sum()
;

var result = a.Gen()
                  .Select(x => x * x)
                  .Where(it => it < 8)
                  .Sum();

Console.WriteLine(s == result);

 

The code that is generated is

// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
    // Non-exported Enumerable should consider anonymous type, thus it will be internal
    internal struct Gen_wRtaM3 : IInternalStub<int>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Gen_wRtaM3(int[] source_wRtaM3) : this()
        {
            this.source_wRtaM3 = source_wRtaM3;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Count() => this.source_wRtaM3.Length;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Select_6q5z23 Select(Func<int, int> selector_6q5z23) => new Select_6q5z23(this, selector_6q5z23);
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal int[] source_wRtaM3;
    }
}

namespace Cathei.LinqGen
{
    // Extension class needs to be internal to prevent ambiguous resolution
    internal static partial class LinqGenExtensions_Gen_wRtaM3
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Gen_wRtaM3 Gen(this int[] source_wRtaM3) => new Gen_wRtaM3(source_wRtaM3);
    }
}
// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
    // Non-exported Enumerable should consider anonymous type, thus it will be internal
    internal struct Select_6q5z23 : IInternalStub<int>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Select_6q5z23(in Gen_wRtaM3 source, Func<int, int> selector_6q5z23) : this()
        {
            this.source_wRtaM3 = source.source_wRtaM3;
            this.selector_6q5z23 = selector_6q5z23;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Count() => this.source_wRtaM3.Length;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Where_kc5pa1 Where(Func<int, bool> predicate_kc5pa1) => new Where_kc5pa1(this, predicate_kc5pa1);
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal int[] source_wRtaM3;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, int> selector_6q5z23;
    }
}

namespace Cathei.LinqGen
{
}
// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
    // Non-exported Enumerable should consider anonymous type, thus it will be internal
    internal struct Select_KZ5014 : IInternalStub<int>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Select_KZ5014(in Where_04jjO source, Func<int, int> selector_KZ5014) : this()
        {
            this.source_wRtaM3 = source.source_wRtaM3;
            this.predicate_04jjO = source.predicate_04jjO;
            this.selector_KZ5014 = selector_KZ5014;
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        internal int[] source_wRtaM3;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, bool> predicate_04jjO;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, int> selector_KZ5014;
    }
}

namespace Cathei.LinqGen
{
    // Extension class needs to be internal to prevent ambiguous resolution
    internal static partial class LinqGenExtensions_Select_KZ5014
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Sum(this Select_KZ5014 source)
        {
            int index_wRtaM3 = default;
            index_wRtaM3 = -1;
            int result_5a0zT4 = default;
            while (true)
            {
                if ((uint)++index_wRtaM3 >= (uint)source.source_wRtaM3.Length)
                    break;
                var current_wRtaM3 = source.source_wRtaM3[index_wRtaM3];
                if (!source.predicate_04jjO.Invoke(current_wRtaM3))
                    continue;
                var current_KZ5014 = source.selector_KZ5014.Invoke(current_wRtaM3);
                result_5a0zT4 += current_KZ5014;
            }

            return result_5a0zT4;
        }
    }
}
// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
    // Non-exported Enumerable should consider anonymous type, thus it will be internal
    internal struct Where_kc5pa1 : IInternalStub<int>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Where_kc5pa1(in Select_6q5z23 source, Func<int, bool> predicate_kc5pa1) : this()
        {
            this.source_wRtaM3 = source.source_wRtaM3;
            this.selector_6q5z23 = source.selector_6q5z23;
            this.predicate_kc5pa1 = predicate_kc5pa1;
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        internal int[] source_wRtaM3;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, int> selector_6q5z23;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, bool> predicate_kc5pa1;
    }
}

namespace Cathei.LinqGen
{
    // Extension class needs to be internal to prevent ambiguous resolution
    internal static partial class LinqGenExtensions_Where_kc5pa1
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Sum(this Where_kc5pa1 source)
        {
            int index_wRtaM3 = default;
            index_wRtaM3 = -1;
            int result_x5AfL3 = default;
            while (true)
            {
                if ((uint)++index_wRtaM3 >= (uint)source.source_wRtaM3.Length)
                    break;
                var current_wRtaM3 = source.source_wRtaM3[index_wRtaM3];
                var current_6q5z23 = source.selector_6q5z23.Invoke(current_wRtaM3);
                if (!source.predicate_kc5pa1.Invoke(current_6q5z23))
                    continue;
                result_x5AfL3 += current_6q5z23;
            }

            return result_x5AfL3;
        }
    }
}
// DO NOT EDIT
// Generated by LinqGen.Generator
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Cathei.LinqGen;
using Cathei.LinqGen.Hidden;

namespace Cathei.LinqGen.Hidden
{
    // Non-exported Enumerable should consider anonymous type, thus it will be internal
    internal struct Where_04jjO : IInternalStub<int>
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal Where_04jjO(in Gen_wRtaM3 source, Func<int, bool> predicate_04jjO) : this()
        {
            this.source_wRtaM3 = source.source_wRtaM3;
            this.predicate_04jjO = predicate_04jjO;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Select_KZ5014 Select(Func<int, int> selector_KZ5014) => new Select_KZ5014(this, selector_KZ5014);
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal int[] source_wRtaM3;
        [EditorBrowsable(EditorBrowsableState.Never)]
        internal Func<int, bool> predicate_04jjO;
    }
}

namespace Cathei.LinqGen
{
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/LinqGen.Generator

RSCG – AutoInvoke.Generator

RSCG – AutoInvoke.Generator
 
 

name AutoInvoke.Generator
nuget https://www.nuget.org/packages/AutoInvoke.Generator/
link https://github.com/LokiMidgard/AutoInvoke.Generator
author Patrick Kranz

Finding all implementation of an interface/class and invoke them.

 

This is how you can use AutoInvoke.Generator .

The code that you start with is


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoInvoke.Generator" Version="0.0.9">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
	<PropertyGroup>
		<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
		<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
	</PropertyGroup>

</Project>


The code that you will use is


using AutoAdd;

RemoteCollection rc=new();
foreach(var item in rc.loaders)
{
    item.Execute();
}




namespace AutoAdd;
partial class RemoteCollection
{
    public List<IRemoteCommand> loaders = new ();

    public RemoteCollection()
    {
        LoadLoaders();
    }
    [AutoInvoke.FindAndInvoke]
    public void LoadLoaders<T>() where T : IRemoteCommand,new()
    {
        loaders.Add(new T());
    }
    
}




namespace AutoAdd;
internal class PCRemote : IRemoteCommand
{
    public void Execute()
    {
        Console.WriteLine("start PC");
    }
}




namespace AutoAdd;
internal class TVRemote : IRemoteCommand
{
    public void Execute()
    {
        Console.WriteLine("start TV");
    }
}


 

The code that is generated is

// <auto-generated/>
#nullable enable

namespace AutoInvoke;
[System.AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
[System.Diagnostics.Conditional("AutoNotifyGenerator_DEBUG")]
internal sealed class FindAndInvokeAttribute : System.Attribute
{
#pragma warning disable CS0169 // Remove unused parameter

#pragma warning disable IDE0060 // Remove unused parameter

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

    public FindAndInvokeAttribute()
    {
    }

    public FindAndInvokeAttribute(string pattern)
    {
    }

    public bool ScanExternalAssamblies { get; set; }
    public string MethodName { get; set; }
    public bool CallForAbstractClasses { get; set; }
    public bool CallForInterfaces { get; set; }
    public bool CallForStructs { get; set; }
    public bool CallForClasses { get; set; }
    public bool CallForRecords { get; set; }
#pragma warning restore CS0169 // Remove unused parameter

#pragma warning restore IDE0060 // Remove unused parameter

#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

}
// <auto-generated/>
#nullable enable
namespace AutoAdd;
partial class RemoteCollection {
    private void LoadLoaders() {
        LoadLoaders<global::AutoAdd.PCRemote>();
        LoadLoaders<global::AutoAdd.TVRemote>();
    }
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/AutoInvoke.Generator

RSCG – Architect.DomainModeling

RSCG – Architect.DomainModeling
 
 

name Architect.DomainModeling
nuget https://www.nuget.org/packages/Architect.DomainModeling/
link https://github.com/TheArchitectDev/Architect.DomainModeling
author Timo van Zijll Langhout

Domain Modelling -DDD, Entity and more. Here I will show just the builder

 

This is how you can use Architect.DomainModeling .

The code that you start with is


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
	  <Nullable>enable</Nullable>
  </PropertyGroup>

	  <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>

	  <ItemGroup>
	    <PackageReference Include="Architect.DomainModeling" Version="3.0.2" />
	  </ItemGroup>

	 

</Project>


The code that you will use is


using Builder;

var pOld = new Person("Andrei", "Ignat");
pOld.MiddleName = "G";
var build = new PersonBuilder()
    .WithFirstName(pOld.FirstName)
    //.WithMiddleName("") // it is not into the constructor
    .WithLastName(pOld.LastName)
    ;
    
var pNew = build.Build();
System.Console.WriteLine(pNew.FullName());
System.Console.WriteLine(pOld.FullName());



namespace Builder;
public class Person
{
    
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
    public string FirstName { get; set; }
    public string? MiddleName { get; set; }
    public string LastName { get; set; }

    public string FullName()
    {
        return FirstName + " " + MiddleName + " "+LastName;
    }
    
}



using Architect.DomainModeling;

namespace Builder;

[DummyBuilder<Person>]
public partial class PersonBuilder
{
}

 

The code that is generated is

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

#nullable disable

namespace Builder
{
	/// <summary>
	/// <para>
	/// Implements the Builder pattern to construct <see cref="Builder.Person"/> objects for testing purposes.
	/// </para>
	/// <para>
	/// Where production code relies on the type's constructor, test code can rely on this builder.
	/// That way, if the constructor changes, only the builder needs to be adjusted, rather than lots of test methods.
	/// </para>
	/// </summary>
	/* Generated */ public partial class PersonBuilder
	{
		private string FirstName { get; set; } = "FirstName";
		public PersonBuilder WithFirstName(string value) => this.With(b => b.FirstName = value);

		private string LastName { get; set; } = "LastName";
		public PersonBuilder WithLastName(string value) => this.With(b => b.LastName = value);

		private PersonBuilder With(Action<PersonBuilder> assignment)
		{
			assignment(this);
			return this;
		}

		public Builder.Person Build()
		{
			var result = new Builder.Person(
				firstName: this.FirstName,
				lastName: this.LastName);
			return result;
		}
	}
}

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/Architect.DomainModeling

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.