Coder Social home page Coder Social logo

fluentbuilder's Introduction

Projects

See also these blogs:

CSharp.SourceGenerators.Extensions

See this page.

FluentBuilder

A project which uses Source Generation to create a FluentBuilder for a specified model or DTO.

This project is based on Tom Phan : "auto-generate-builders-using-source-generator-in-net-5".

Install

NuGet Badge

You can install from NuGet using the following command in the package manager window:

Install-Package FluentBuilder

Or via the Visual Studio NuGet package manager or if you use the dotnet command:

dotnet add package FluentBuilder

1️⃣ Usage on a existing class

Annotate a class

Annotate an existing class with [FluentBuilder.AutoGenerateBuilder] to indicate that a FluentBuilder should be generated for this class:

using FluentBuilder;

[AutoGenerateBuilder]
public class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime? Date { get; set; }

    [FluentBuilderIgnore] // Add this attribute to ignore this property when generating a FluentBuilder.
    public int Age { get; set; }

    public int Answer { get; set; } = 42; // When a default value is set, this value is also set as default in the FluentBuilder.
}

AutoGenerateBuilder - attribute

This attribute has 4 arguments:

  • type (Type): The type for which to create the builder, see 'Define a class which needs to act as a builder'
  • handleBaseClasses (bool): Handle also base-classes. Default value is true.
  • accessibility (enum FluentBuilderAccessibility): Generate builder methods for Public or PublicAndPrivate. Default value when not provided is Public.
  • methods (enum FluentBuilderMethods): Generate With*** methods or also Without*** methods. Default value when not provided is WithOnly. See also Notes

Use FluentBuilder

using System;
using FluentBuilder;

namespace Test;

class Program
{
    static void Main(string[] args)
    {
        var user = new UserBuilder()
            .WithFirstName("Test")
            .WithLastName("User")
            .Build();

        Console.WriteLine($"{user.FirstName} {user.LastName}");
    }
}

Use FluentBuilder when the class has a default (parameter-less) constructor

using FluentBuilder;

[AutoGenerateBuilder]
public class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime? Date { get; set; }

    public User()
    {
        FirstName = "test";
    }
}
using System;
using FluentBuilder;

namespace Test;

class Program
{
    static void Main(string[] args)
    {
        var user = new UserBuilder()
            .WithLastName("User")
            .Build(false); // ⭐ Provide `false` for `useObjectInitializer` here.

        Console.WriteLine($"{user.FirstName} {user.LastName}");
    }
}

Use FluentBuilder when the class has a constructor with parameters

using FluentBuilder;

[AutoGenerateBuilder]
public class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime? Date { get; set; }

    public User(string first)
    {
        FirstName = first;
    }
}
using System;
using FluentBuilder;

namespace Test;

class Program
{
    static void Main(string[] args)
    {
        var user = new UserBuilder()
            .UsingConstructor("First")  // ⭐ Use `UsingConstructor` here.
            .WithLastName("User")
            .Build();

        Console.WriteLine($"{user.FirstName} {user.LastName}");
    }
}

Using FluentBuilder when a class has an Array or IEnumerable<T> property

using FluentBuilder;

[AutoGenerateBuilder]
public class UserDto
{
    public IEnumerable<EmailDto> SecondaryEmails { get; set; }

    public int[] IntArray { get; set; }
}
var user = new UserDtoBuilder()
    .WithIntArray(ib => ib         // 👈 Use a Integer Array Builder
        .Add(1)                    // Add a normal integer

        .Add(() => 2)              // Add an integer with a Func<>
        .Build()
    )
    .WithSecondaryEmails(sb => sb  // 👈 Use a EmailDto IEnumerable Builder
        .Add(new EmailDto())       // Add a normal EmailDto using new() constructor

        .Add(() => new EmailDto()) // Add an EmailDto using Func<>

        .Add(eb => eb              // 👈 Use a EmailDto IEnumerable Builder to add an EmailDto
            .WithPrimary(true)
            .Build()
        )
        .Build()
    )
    .Build();

Using FluentBuilder when a class has an IDictionary<TKey, TValue> property

using FluentBuilder;

[AutoGenerateBuilder]
public class UserDto
{
    public IDictionary<string, int> Tags { get; set; }
}
var user = new UserDtoBuilder()
    .WithTags(db => db      // 👈 Use a Dictionary<TKey, TValue> Builder
        .Add("test", 123)   // Add a key with value

        .Add(() => new KeyValuePair<string, int>("x", 42)) // Add a KeyValuePair with a Func<>
        .Build()
    )
    .Build();

2️⃣ Define a class which needs to act as a builder

This scenario is very usefull when you cannot modify the class to annotate it.

Create a public and partial builder class

And annotate this class with [AutoGenerateBuilder(typeof(XXX))] where XXX is the type for which you want to generate a FluentBuilder.

using FluentBuilder;

[AutoGenerateBuilder(typeof(UserDto))]
public partial class MyUserDtoBuilder
{
}

Use FluentBuilder

using System;
using FluentBuilder;

namespace Test;

class Program
{
    static void Main(string[] args)
    {
        var user = new MyUserDtoBuilder() // 👈 Just use your own Builder
            .WithFirstName("Test")
            .WithLastName("User")
            .Build();

        Console.WriteLine($"{user.FirstName} {user.LastName}");
    }
}

Extension Method

By default, the AsBuilder extension method is also generated which allows you to change an existing instance using the With-methods from the builder.

Example:

var user = await dbContext.Users.FirstAsync();

user = user.AsBuilder() // Lifts the user into a builder instance
    .WithLastName("Different LastName") // Updates "LastName" while keeping existing properties
    .Build(); // Changes are applied

await dbContext.SaveChangesAsync(); // User's LastName property is updated.

Notes

Since version 0.8.0, this FluentBuilder will only generate the With*** methods. If you want the builder to also generate the Without*** methods, add the enum FluentBuilderMethods.WithAndWithout to the attribute.

using FluentBuilder;

[AutoGenerateBuilder(typeof(UserDto), FluentBuilderMethods.WithAndWithout)]
public partial class MyUserDtoBuilder
{
}

fluentbuilder's People

Contributors

emorell96 avatar iam-mholle avatar meigs2 avatar stefh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluentbuilder's Issues

Issues with generating builder from an object in another namespace

this doesn't seem to work or generate any errors:

namespace namespace1
{
    using System.Diagnostics;

    using FluentBuilder;

   [AutoGenerateBuilder(typeof(MyModel))]
   public partial class MyModelBuilder
    {
    }

    public record MyModel
    {
        /// <summary>
        ///     Gets or sets the name.
        /// </summary>
        public string Name { get; set; } = "Unknown";
    }
}

namespace namespace2
{
    using FluentBuilder;

    using namespace1;

   // doesn't work
    [AutoGenerateBuilder(typeof(MyModel))]
    public partial class MyModelBuilder2
    {
    }
}

Builder generation fails if the target type has at least one ctor with parameters

Due to the following check, an exception is thrown if the target DTO has at least one ctor with parameters, even if a parameterless one is available.

https://github.com/StefH/FluentBuilder/blob/main/src/FluentBuilderGenerator/FileGenerators/FluentBuilderClassesGenerator.cs#L247-L252

The condition should be changed to

classSymbol.NamedTypeSymbol.Constructors.IsEmpty || classSymbol.NamedTypeSymbol.Constructors.All(c => !c.Parameters.IsEmpty)

Writing out the thrown exception into the Error.g.cs file is great to allow errors to be analyzed - wouldn't it be good to also emit a compiler warning/error via GeneratorExecutionContext.ReportDiagnostic(...) for such an error? I think this would make the development experience even better.

Anyway, thank you for your work on this project!

Builder code should handle init setter so that it doesn't generate non-compilable code

Given the following classes:

public class Simple
{
   public string Name { get; private set;}  // or  public string Name { get; init;}
}

[AutoGenerateBuilder(typeof(Simple))]
public partial class SimpleBuilder
{
}

should not generate this code in the Build method:

public override Simple Build(bool useObjectInitializer = true)
        {
            if (Object?.IsValueCreated != true)
            {
                Object = new Lazy<Simple>(() =>
                {
                    if (useObjectInitializer)
                    {
                        return new Simple
                        {
                            Name = _name.Value  // this could be here if property has init but shouldn't be otherwise
                        };
                    }

                    var instance = new Simple();
                    if (_nameIsSet) { instance.Name = _name.Value; }  // this shouldn't be here since it won't compile
                    return instance;
                });
            }

            PostBuild(Object.Value);

            return Object.Value;
        }

Instead, for situations like this, generate the following:

// add this method to the builder.  A user can choose to implement and add custom behavior
partial void SetName(Simple instance, string value);  //NOTE: private could be added to the front of this to force the user to implement

public override Simple2 Build(bool useObjectInitializer = true)
        {
            if (Object?.IsValueCreated != true)
            {
                Object = new Lazy<Simple2>(() =>
                {
                    if (useObjectInitializer)
                    {
                        var result = new Simple2
                        {
                            // other settable props here
                        };
                       if (_nameIsSet) { SetName( result, _name.Value); } // or try to find a method on the class that starts with Set<PropName>
                      return result;
                    }

                    var instance = new Simple2();
                    if (_nameIsSet) { SetName( instance, _name.Value); } // or try to find a method on the class that starts with Set<PropName>
                    return instance;
                });
            }

            PostBuild(Object.Value);

            return Object.Value;
        }

Also, this technique could be used to overcome the default constructor requirement.

// add this method to the builder.  A user can choose to implement and add custom behavior
partial void SetName(Simple instance, string value);

 // Developer could provide implementation like "instance = new Simple(_name);"
// only added if no default constructor found or if you don't want to find an appropriate constructor to use
private partial void CreateInstance( out Simple instance); 

public override Simple2 Build()
        {
            if (Object?.IsValueCreated != true)
            {
                Object = new Lazy<Simple2>(() =>
                {
                    Simple instance;  
                    CreateInstance(out instance);
                    if (_nameIsSet) { SetName( instance, _name.Value); }
                    return instance;
                });
            }

            PostBuild(Object.Value);

            return Object.Value;
        }

Wrong initialization is generated for ReadOnlyCollection<> properties

When generating a builder for a class containing a ReadOnlyCollection<> property, the generated code initializes the property with a new List<>().

Consider the following class:

[AutoGenerateBuilder]
public partial class SomeDTO
{
  public ReadOnlyCollection<string> Strings { get; set; }
}

The following builder is being generated, where the _strings initialization and the WithStrings(() => new List<string>()) call cause the compilation to fail:

public partial class SomeDTOBuilder : Builder<Library.SomeDTO>
{
    private bool _stringsIsSet;
    private Lazy<System.Collections.ObjectModel.ReadOnlyCollection<string>> _strings = new Lazy<System.Collections.ObjectModel.ReadOnlyCollection<string>>(() => new List<string>());
    public SomeDTOBuilder WithStrings(System.Collections.ObjectModel.ReadOnlyCollection<string> value) => WithStrings(() => value);
    public SomeDTOBuilder WithStrings(Func<System.Collections.ObjectModel.ReadOnlyCollection<string>> func)
    {
        _strings = new Lazy<System.Collections.ObjectModel.ReadOnlyCollection<string>>(func);
        _stringsIsSet = true;
        return this;
    }
    public SomeDTOBuilder WithStrings(Action<ClassLibrary1.FluentBuilder.IListBuilder<string>> action, bool useObjectInitializer = true) => WithStrings(() =>
    {
        var builder = new ClassLibrary1.FluentBuilder.IListBuilder<string>();
        action(builder);
        return (System.Collections.ObjectModel.ReadOnlyCollection<string>) builder.Build(useObjectInitializer);
    });
    public SomeDTOBuilder WithoutStrings()
    {
        WithStrings(() => new List<string>());
        _stringsIsSet = false;
        return this;
    }


    public override SomeDTO Build(bool useObjectInitializer = true) { /*...*/ }
    public static SomeDTO Default() => new SomeDTO();
}

Looking at the FluentBuilder source, the issue seems to be that FluentBuilderGenerator.Extensions.TypeSymbolExtensions.GetDefault calls FluentBuilderGenerator.Extensions.TypeSymbolExtensions.GetFluentTypeKind to check for an adequate default initializer, but GetFluentTypeKind returns interface types found for implementing types, meaning that ReadOnlyCollection : System.Collections.Generic.IList<T> /*, ...*/ causes FluentTypeKind.IList to be returned from GetFluentTypeKind which then leads to new List<>() to be chosen as default initializer.

I think the 4 interface implementation checks in GetFluentTypeKind should only happen if typeSymbol.TypeKind == TypeKind.Interface like so:

public static FluentTypeKind GetFluentTypeKind(this ITypeSymbol typeSymbol)
{
    if (typeSymbol.SpecialType == SpecialType.System_String)
    {
        return FluentTypeKind.String;
    }

    if (typeSymbol.TypeKind == TypeKind.Array)
    {
        return FluentTypeKind.Array;
    }

    if (typeSymbol.TypeKind == TypeKind.Interface)
    {
        if (typeSymbol.ImplementsInterfaceOrBaseClass(typeof(IDictionary<,>)) || typeSymbol.ImplementsInterfaceOrBaseClass(typeof(IDictionary)))
        {
            return FluentTypeKind.IDictionary;
        }

        if (typeSymbol.ImplementsInterfaceOrBaseClass(typeof(IList<>)) || typeSymbol.ImplementsInterfaceOrBaseClass(typeof(IList)))
        {
            return FluentTypeKind.IList;
        }

        if (typeSymbol.ImplementsInterfaceOrBaseClass(typeof(ICollection<>)) || typeSymbol.ImplementsInterfaceOrBaseClass(typeof(ICollection)))
        {
            return FluentTypeKind.ICollection;
        }

        if (typeSymbol.AllInterfaces.Any(i => i.SpecialType == SpecialType.System_Collections_IEnumerable))
        {
            return FluentTypeKind.IEnumerable;
        }
    }

    return FluentTypeKind.Other;
}

Support primary constructors

C# 12 has primary constructors in classes, but they don't generate properties automatically (it's a really bad design choice, but it's the chosen one).
It'd be interesting to generate properties (you just have to convert the first letter to uppercase).

The type or namespace name 'UserBuilder' does not exist in the namespace 'FluentBuilder' (are you missing an assembly reference?)

Using latest VS22.
It doesn't seem to work, neither with your provided samples, nor with the example projects.
Installation of the nuget runs through, but it seems like there is no FluentBuilder namespace available.

In the build logs there seems to exist the generated code tho...
1>\TestBuilder\FluentBuilderGenerator\FluentBuilderGenerator.FluentBuilderSourceGenerator\.UserBuilder.g.cs(38,65,38,80): warning CS8603: Possible null reference return.

My code:

using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var user = new FluentBuilder.UserBuilder()
                .WithFirstName("Test")
                .WithLastName("User")
                .Build();

            Console.WriteLine($"{user.FirstName} {user.LastName}");
        }
    }
}

[FluentBuilder.AutoGenerateBuilder]
public class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime? Date { get; set; }
}

Child builders for properties not generating correct code

I'm having a few issues generating builders on a child List property.

Scenario 1

public class User
{
    public List<Option> Options { get; set; }
}
public class Option
{
    public string Name { get; set; }
}

[AutoGenerateBuilder(typeof(User))]
public partial class MyUserBuilder {}
[AutoGenerateBuilder(typeof(Option))]
public partial class MyOptionBuilder {}

Build error:
...\FluentBuilderGenerator\FluentBuilderGenerator.FluentBuilderSourceGenerator\FluentBuilderSandbox.Option_IListBuilder.g.cs(28,46,28,59): error CS0246: The type or namespace name 'OptionBuilder' could not be found (are you missing a using directive or an assembly reference?)

Scenario 2

Main project

public class User
{
    public List<Option> Options { get; set; }
}
public class Option
{
    public string Name { get; set; }
}

Test project

[AutoGenerateBuilder(typeof(User))]
public partial class UserBuilder { }

[AutoGenerateBuilder(typeof(Option))]
public partial class OptionBuilder { }

Build error:
...\FluentBuilderGenerator\FluentBuilderGenerator.FluentBuilderSourceGenerator\FluentBuilderSandboxTests.UserBuilder.g.cs(31,68,31,86): error CS0234: The type or namespace name 'IListOptionBuilder' does not exist in the namespace 'FluentBuilderSandbox' (are you missing an assembly reference?)

Generating a builder for a property of a type where the ctor with the least parameters expects the property type itself causes a stack overflow

FluentBuilderGenerator.Extensions.TypeSymbolExtensions.GetNewConstructor always selects the ctor with the least parameters and fills in a fitting default by calling GetDefault for the expected ctor parameters.

For the following class, an infinite nesting of new Person(new Person(/*...*/)) is generated, leading to stack overflow.

public class Person
{
  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
  }

  public Person(Person person)
  {
    FirstName = person.FirstName;
    LastName = person.LastName;
  }

  public string FirstName { get; set; }
  public string LastName { get; set; }
}

[AutoGenerateBuilder]
public partial class SomeDTO
{
  public Person Person { get; set; }
}

Builder code should handle private setters

if we have a model like the following:

public class Model
{
   public string Name { get; private set; }
   public void DoSomething(string newName)
   {
      // magic done here...
   }

then a following builder could be generated to handle this:

    public partial class ModelBuilderCliff : Builder<RedDragon.BeastMode.Domain.Exercises.Model>
    {

        private Lazy<string>? _name=null;

        public ModelBuilderCliff WithName(string value) => WithName(() => value);

        partial void SetName(Model instance, string value);
        public ModelBuilderCliff WithName(Func<string> func)
        {
            _name = new Lazy<string>(func);

            return this;
        }

        public ModelBuilderCliff WithoutName()
        {
            _name = null;

            return this;
        }




        public override Model Build(bool useObjectInitializer = true)
        {
            if (Object?.IsValueCreated != true)
            {
                Object = new Lazy<Model>(
                    () =>
                    {
                        if (useObjectInitializer)
                        {
                            var instance1 = new Model
                                   {
                                   };

                            SetName(instance1, _name == null?Default().Name:_name.Value);
                            return instance1;
                        }

                        var instance = new Model();

                        if (_name !=null )
                        {
                            SetName(instance, _name == null ? Default().Name : _name.Value);

                        }

                        return instance;
                    });
            }

            PostBuild(Object.Value);

            return Object.Value;
        }

        public static Model Default() => new Model();

    }

then a developer could provide an implementation of SetName that does something meaningful for the object:

partial void SetName(Model instance, string value)
{
  instance.DoSomething(value);
}

AutoGenerateBuilder for type doesn't generate classess

If more than 1 AutoGenerateBuilder class is defined, no classes are generated

[AutoGenerateBuilder(typeof(A))]
public partial class ABuilder {
}
[AutoGenerateBuilder(typeof(B))]
public partial class BBuilder {
}

A and B are third-party DTOs

My project :

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

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <IsPackable>true</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="FluentBuilder" Version="0.9.0"  PrivateAssets="All"/>
        <PackageReference Include="ABNuget" Version="xxx" />
    </ItemGroup>
</Project>

dotnet sdk 7.0.401

Automatic extension method generation for easy updating of existing class instances

Proposal

I'm loving the library, but I think it would be very helpful to have the source generator automatically create extension methods for the updating of existing class instances. This would allow classes to become more "record-like", allowing updates in-place.

I'm proposing here that the "lifting" method to be called BuildNew(), but I dont think this is ideal, and I think a better name should be thought of. I would call it With(), but that conflicts with the current WithX naming convention of the existing builders.

Example API:

[AutoGenerateBuilder(generateSetters: true)]
public class User
{
    public string Name { get; set; }
    public string Gender { get; set; }
}

...

var user = await dbContext.Users.FirstAsync();

user.BuildNew() // `BuildNew` originates from an auto-generated extension method class in UserBuilder.g.cs. Lifts user into a UserBuilder
    .WithName("New Name") // usual updates happen here
    .Build(); // Updates the object in-place.

// OPTIONALLY if generateSetter == True

user.SetName("Newer Name"); // Sets the name

await dbContext.SaveChangesAsync(); // user's Name property is updated.

// also allow for operations on items in a collection
var users = dbContext.Users.Where(x => x.Gender == "Male").ToListAsync();

users.SetGender("Female") // Updates all elements in-place. Could also use `BuildNew()` syntax.

dbContext.SaveChangesAsync();

I feel like this is a natural continuation of the current library. It appears like you're supposed to be able to use UsingInstance for this purpose to update existing instances in-place, but I can't get it to work. This would also bring existing non-record classes to be more "record like".

Add WithProperty overloads that accepts the property's builder when available

Coming from WireMock-Net/WireMock.Net#621, I suggested that the builder API could create overloads that took other builders if the property type is also annotated with the AutoGenerateBuilder attribute. I was thinking of the following API:

        public UserDtoBuilder WithPrimaryEmail(Action<FluentBuilder.EmailDtoBuilder> action) => WithPrimaryEmail(() =>
        {
            var builder = new FluentBuilder.EmailDtoBuilder();

            action(builder);

            return builder.Build();
        });

        public UserDtoBuilder WithPrimaryEmailBuilder(FluentBuilder.EmailDtoBuilder builder) => WithPrimaryEmail(() =>
        {
            return builder.Build();
        });

I was looking into the code and was able to implement it with no problem. If you're interested, I could open a pull request right now, this is my fork

Builder could handle classes without default constructor

if we have a class like the following:

public class Model
{
    public Model(string id, string name)
   {
       Id = id;
       Name = name;
   }
   public string Name {get; set;}
}

then the following builder could be generated:

// Dev's code
public partial class ModelBuilderCliff
{
    static private partial void CreateInstance(ModelBuilderCliff builder,out Model instance)
    {
        instance = new Model(builder._id.Value, builder._name.Value);
    }
}

public partial class ModelBuilderCliff : Builder<RedDragon.BeastMode.Domain.Exercises.Model>
{

 static   private partial  void CreateInstance(ModelBuilderCliff builder, out Model instance);

    private Lazy<string>? _id=null;

    public ModelBuilderCliff WithId(string value) => WithId(() => value);

    partial void SetId(Model instance, string value); // since we know this is a private field, "private" could be added to the beginning of this to force the developer to provide an implementation.  In order to remove any ambiguity, could add prop to AutoGenerateBuilder so user can specify if they want forced partials
    public ModelBuilderCliff WithId(Func<string> func)
    {
        _id = new Lazy<string>(func);

        return this;
    }

    public ModelBuilderCliff WithoutId()
    {
        _id = null;

        return this;
    }

    private Lazy<string>? _name=null;

    public ModelBuilderCliff WithName(string value) => WithName(() => value);

    partial void SetName(Model instance, string value);
    public ModelBuilderCliff WithName(Func<string> func)
    {
        _name = new Lazy<string>(func);

        return this;
    }

    public ModelBuilderCliff WithoutName()
    {
        _name = null;

        return this;
    }

    public override Model Build(bool useObjectInitializer = true)
    {
        if (Object?.IsValueCreated != true)
        {
            Object = new Lazy<Model>(
                () =>
                {
                    if (useObjectInitializer)
                    {
                        CreateInstance(this,out var instance1);

                        if (_id != null)
                        {
                            SetId(instance1, _id == null ? string.Empty : _id.Value);
                        }

                        if (_name != null)
                        {
                            instance1.Name= _name == null ? string.Empty : _name.Value;
                        }

                        return instance1;
                    }

                    // these next lines are dupes of the first if useObjectInitializer is kept
                    CreateInstance(this,out var instance);

                    if (_id != null)
                    {
                        SetId(instance, _id == null ? string.Empty : _id.Value);
                    }

                    if (_name !=null )
                    {
                        instance.Name = _name == null ? string.Empty : _name.Value;

                    }

                    return instance;
                });
        }

        PostBuild(Object.Value);

        return Object.Value;
    }
}

Unable to use `Func` and `Action` with the builder methods

I'm trying to use this library to create my objects, but I've encountered a problem. My library uses callbacks (System.Func and System.Action) pretty heavily, so I have classes like:

public class Operation
{
    public Func<int, string> ToStringCallback { get; set; }
    public Action<int> IntCallback { get; set; }
}

The resulting code is:

    public partial class OperationBuilder : Builder<Operation>
    {
        private bool _toStringCallbackIsSet;
        private Lazy<System.Func<int, string>> _toStringCallback = new Lazy<System.Func<int, string>>(() => new System.Func<int, string>(new object(), default(System.IntPtr)));
        public OperationBuilder WithToStringCallback(System.Func<int, string> value) => WithToStringCallback(() => value);
        public OperationBuilder WithToStringCallback(Func<System.Func<int, string>> func)
        {
            _toStringCallback = new Lazy<System.Func<int, string>>(func);
            _toStringCallbackIsSet = true;
            return this;
        }
        public OperationBuilder WithoutToStringCallback()
        {
            WithToStringCallback(() => new System.Func<int, string>(new object(), default(System.IntPtr)));
            _toStringCallbackIsSet = false;
            return this;
        }

        private bool _intCallbackIsSet;
        private Lazy<System.Action<int>> _intCallback = new Lazy<System.Action<int>>(() => new System.Action<int>(new object(), default(System.IntPtr)));
        public OperationBuilder WithIntCallback(System.Action<int> value) => WithIntCallback(() => value);
        public OperationBuilder WithIntCallback(Func<System.Action<int>> func)
        {
            _intCallback = new Lazy<System.Action<int>>(func);
            _intCallbackIsSet = true;
            return this;
        }
        public OperationBuilder WithoutIntCallback()
        {
            WithIntCallback(() => new System.Action<int>(new object(), default(System.IntPtr)));
            _intCallbackIsSet = false;
            return this;
        }

This produces errors when compling, mainly on these two lines:

WithToStringCallback(() => new System.Func<int, string>(new object(), default(System.IntPtr)));
WithIntCallback(() => new System.Action<int>(new object(), default(System.IntPtr)));

The constructors are the failure since those aren't allowed. A possibly alternative would be, but that might require some additional reflection/investigating. Sadly, it would add a lot more overhead to create an interface to handle these since the object being built is basically just a collection of callbacks.

new System.Action<int>((v1) => { });
new System.Func<int, string>((v1) => default(string));

Thank you.

Issue with same type in look-a-like namespace

public class MyEntity : ITableEntity
{
    #region ITableEntity properties
    public string PartitionKey { get; set; } = null!;

    public string RowKey { get; set; } = null!;

    public DateTimeOffset? Timestamp { get; set; }

    public ETag ETag { get; set; }
    #endregion

    public string Test { get; set; } = null!;
}

The ETag is defined in Azure.ETag.

Also there is a struct named ETag in the Stef.Azure.ETag.

The builder uses the full namespace:

private Lazy<Azure.ETag> _eTag = new(() => default(Azure.ETag));

However this fails.

Bug with a class with CultureInfo

Hi,

Thanks a lot for all the work done on this library it has been a real help!

I found a bug while working with a class that holds a CultureInfo member.

This is my class:

public class MenuItemDisplayModel
    {
       // other members removed since the error is based on the one below
        public CultureInfo Locale { get; set; } = CultureInfo.CurrentCulture;
    }

The builder created this line of code:

private Lazy<System.Globalization.CultureInfo> _locale = new Lazy<System.Globalization.CultureInfo>(() => new System.Globalization.CultureInfo(default(int)));

which fails because default(int) is 0 and there's no culture with that number, so I get the exception System.Globalization.CultureNotFoundException: Culture is not supported. (Parameter 'culture') 0 (0x0000) is an invalid culture identifier.

Is there any attribute to tell the builder to ignore the property?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.