GithubHelp home page GithubHelp logo

immutableobjectgraph's Introduction

ImmutableObjectGraph

Build Status NuGet package Join the chat at https://gitter.im/AArnott/ImmutableObjectGraph

This project offers code generation that makes writing immutable objects much easier. For instance, the following mutable class:

public class Fruit {
    public string Color { get; set; }
    public int SkinThickness { get; set; }
}

Is very short, easily written and maintainable. The equivalent immutable type would require methods offering mutation, and ideally several other support methods and even a Builder class for use in conveniently handling the immutable object when mutation by creating new objects may be required. These codebases for immutable objects can be quite large.

To reduce the burden of writing and maintaining such codebases, this project generates immutable types for you based on a minimal definition of a class that you define.

Supported features

  • Field types may be value or reference types.
  • When field types are collections, immutable collections should be used that support the Builder pattern.
  • When field types refer to other types also defined in the template file, an entire library of immutable classes with members that reference each other can be constructed.
  • Batch property changes can be made with a single allocation using a single invocation of the With method.
  • Builder classes are generated to allow efficient multi-step mutation without producing unnecessary GC pressure.
  • Version across time without breaking changes by adding Create and With method overloads with an easy application of [Generation(2)].

Usage

You can begin using this project by simply installing a NuGet package:

Install-Package ImmutableObjectGraph.Generation -Pre

On any source file that you use the [GenerateImmutable] attribute in, set the Custom Tool property to: MSBuild:GenerateCodeFromAttributes

Example source file

[GenerateImmutable]
partial class Fruit
{
    readonly string color;
    readonly int skinThickness;
}

Example generated code

The following code will be generated automatically for you and added to a source file in your intermediate outputs folder:

partial class Fruit
{
    [System.Diagnostics.DebuggerBrowsableAttribute(System.Diagnostics.DebuggerBrowsableState.Never)]
    private static readonly Fruit DefaultInstance = GetDefaultTemplate();
    private static int lastIdentityProduced;
    [System.Diagnostics.DebuggerBrowsableAttribute(System.Diagnostics.DebuggerBrowsableState.Never)]
    private readonly uint identity;
    protected Fruit(uint identity, System.String color, System.Int32 skinThickness, bool skipValidation)
    {
        this.identity = identity;
        this.color = color;
        this.skinThickness = skinThickness;
        if (!skipValidation)
        {
            this.Validate();
        }
    }

    public string Color
    {
        get
        {
            return this.color;
        }
    }

    public int SkinThickness
    {
        get
        {
            return this.skinThickness;
        }
    }

    internal protected uint Identity
    {
        get
        {
            return this.identity;
        }
    }

    public static Fruit Create(ImmutableObjectGraph.Optional<System.String> color = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> skinThickness = default(ImmutableObjectGraph.Optional<System.Int32>))
    {
        var identity = ImmutableObjectGraph.Optional.For(NewIdentity());
        return DefaultInstance.WithFactory(color: ImmutableObjectGraph.Optional.For(color.GetValueOrDefault(DefaultInstance.Color)), skinThickness: ImmutableObjectGraph.Optional.For(skinThickness.GetValueOrDefault(DefaultInstance.SkinThickness)), identity: identity);
    }

    public Fruit With(ImmutableObjectGraph.Optional<System.String> color = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> skinThickness = default(ImmutableObjectGraph.Optional<System.Int32>))
    {
        return (Fruit)this.WithCore(color: color, skinThickness: skinThickness);
    }

    static protected uint NewIdentity()
    {
        return (uint)System.Threading.Interlocked.Increment(ref lastIdentityProduced);
    }

    protected virtual Fruit WithCore(ImmutableObjectGraph.Optional<System.String> color = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> skinThickness = default(ImmutableObjectGraph.Optional<System.Int32>))
    {
        return this.WithFactory(color: ImmutableObjectGraph.Optional.For(color.GetValueOrDefault(this.Color)), skinThickness: ImmutableObjectGraph.Optional.For(skinThickness.GetValueOrDefault(this.SkinThickness)), identity: ImmutableObjectGraph.Optional.For(this.Identity));
    }

    static partial void CreateDefaultTemplate(ref Template template);
    private static Fruit GetDefaultTemplate()
    {
        var template = new Template();
        CreateDefaultTemplate(ref template);
        return new Fruit(default(uint), template.Color, template.SkinThickness, skipValidation: true);
    }

    partial void Validate();
    private Fruit WithFactory(ImmutableObjectGraph.Optional<System.String> color = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> skinThickness = default(ImmutableObjectGraph.Optional<System.Int32>), ImmutableObjectGraph.Optional<uint> identity = default(ImmutableObjectGraph.Optional<uint>))
    {
        if ((identity.IsDefined && identity.Value != this.Identity) || (color.IsDefined && color.Value != this.Color) || (skinThickness.IsDefined && skinThickness.Value != this.SkinThickness))
        {
            return new Fruit(identity: identity.GetValueOrDefault(this.Identity), color: color.GetValueOrDefault(this.Color), skinThickness: skinThickness.GetValueOrDefault(this.SkinThickness), skipValidation: false);
        }
        else
        {
            return this;
        }
    }

#pragma warning disable 649 // field initialization is optional in user code

    private struct Template
    {
        internal System.String Color;
        internal System.Int32 SkinThickness;
    }
#pragma warning restore 649
}

The integration of the code generator support in Visual Studio allows for you to conveniently maintain your own code, and on every save or build of that file, the code generator runs and automatically creates or updates the generated partial class.

Known Issues

When defining more than one immutable type, you may need to keep the arguments to the [GenerateImmutable] attribute consistent for every type. The generator currently assumes that every type has the same arguments as every other type and as a result, for example, generating a Builder from one type and referencing another type, that other type will be assumed to also have a Builder even when it does not, leading to compiler errors.

immutableobjectgraph's People

Contributors

aarnott avatar amis92 avatar azeno avatar gitter-badger avatar stanislawswierc avatar tg73 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

immutableobjectgraph's Issues

More complete support for required fields

0f9c0a5 adds basic support for required fields. But it malfunctions when [Required] appears on fields not declared before all optional fields, and (presumably) also fails when declared on derived types.

[GenerateImmutable] on sealed classes generates code with errors.

For example, the following will produce generated code with errors and warnings. The errors are due to adding virtual or protected members to a sealed class.

[GenerateImmutable( DefineInterface = true, DefineWithMethodsPerProperty = true, GenerateBuilder = true )]
sealed partial class Sealed
{
    readonly string name;
}

ProjectTreeTests.ProjectTreeChangesSinceLargeTreeTest sometimes fails

Test runs randomly fail with this error:

Error Message:
   System.Collections.Generic.KeyNotFoundException : The given key was not present in the dictionary.
Stack Trace:
   at ImmutableObjectGraph.RecursiveTypeExtensions.Find[TRecursiveParent,TRecursiveType](TRecursiveParent parent, UInt32 identity) in f:\vnext1\5a7f96c7\AArnott\ImmutableObjectGraph\src\ImmutableObjectGraph\RecursiveTypeExtensions.cs:line 168
   at ImmutableObjectGraph.Tests.ProjectTree.Find(UInt32 identity) in f:\vnext1\5a7f96c7\AArnott\ImmutableObjectGraph\src\ImmutableObjectGraph.Tests\ProjectTree.generated.cs:line 752
   at ImmutableObjectGraph.Tests.RootedProjectTree.Find(UInt32 identity) in f:\vnext1\5a7f96c7\AArnott\ImmutableObjectGraph\src\ImmutableObjectGraph.Tests\ProjectTree.generated.cs:line 1425
   at ImmutableObjectGraph.Tests.ProjectTreeTests.ProjectTreeChangesSinceLargeTreeTest() in f:\vnext1\5a7f96c7\AArnott\ImmutableObjectGraph\src\ImmutableObjectGraph.Tests\ProjectTreeTests.cs:line 53
Test Run Failed.

Prior to the error, the test has been observed to produce this output:

Random seed: 1869664796
Total tree size: 8280 nodes

Missing ImmutableArray<T>.ResetContents extension method

Hi - Just started poking around with this; looks great.

I ran into an issue with an ImmutableArray that's a member of the class. The code generator generates members that call the ResetContents extension method. The member, though, doesn't exist. I switched it to an ImmutableList and all was well, but I thought I'd let you know.

Take care!

Dependency on CodeGeneration.Roslyn

Just had a look at the latest version (1.1.69-beta-gba4e1c3e26) and noticed that installing ImmutableObjectGraph.Generation is not enough, one also needs to install the proper version of CodeGeneration.Roslyn in order for the code generator to kick in. Is this intentional? If so it should either get documented or the ImmutableObjectGraph.Generation package should depend on it.

Support Json deserialization (eg, via WebAPI)

Json serialization of immutable object graphs work as expected.

However Json deserialization does not work. So far only tested against a class of the form

public class FilterCriteria
{
    System.Collections.Immutable.ImmutableList<System.Guid> ids,
    SomeEnum someEnum
}

. Json.NET invokes the protected default constructor with no parameters rather than the one that accepts an ImmutableList and an enum, presumably because there's no other constructor it properly understands how to invoke.

One workaround at the moment is to create a constructor in a partial class that looks like

[JsonConstructor()]
public FilterCriteria(Guid[] ids, SomeEnum someEnum)
        :this(
            ids.ToImmutableList(),
            someEnum
            )
    {
    }

Json.NET will favour this constructor over any other due to the attribute. It knows how to bind to simple arrays.

As an additional test I modified the generated C# file such that the normally protected constructor that accepts arguments was made public but it otherwise was identical to that generated by the T4 template. Json.NET then throws
Cannot create and populate list type System.Collections.Immutable.ImmutableList`1[System.Guid].

The T4 template could be adjusted to include such a constructor with more simple types (such as an array), or perhaps only include it if a flag is set. I suspect there's probably no actual need for the attribute - just the fact that there's only a sole public constructor with parameters named the same as the classes properties is probably enough (this also avoids any dependencies on Json.NET).

I realise a public constructor is not quite in the spirit of creating immutable objects. I'm new to Json.NET so there could be a more generic mechanism whereby it could be taught how to instantiate an immutable object via the static Create() method rather than via a constructor.

I've also tried using Builder objects since they're naturally mutable but there's no constructor of use there at all. Maybe a default parameterless constructor on the builder objects could be used and then WebAPI (via Json.NET) could accept Builder objects as parameters to controller actions rather than the immutable objects themselves? That would still be quite readily understood by someone reading the code and the generated Json from server->client still looks great.

Sorry for the ramble - I've tried to think of the various permutations but I know enough to be dangerous but not quite enough to have a good idea if I'm missing something obvious.

For the time being I'll probably go with a public constructor in a partial class so that Json.NET has something to target but I'll also play with the Builder object some more.

Thanks for the great library!

Build after clean

The workaround to avoid dependency problems in the .tt file works fine (i.e. get the project to compile once, then enable the .tt generation). However, when you clean the project, you end up in the same boat once again, as the bin directory is emptied.
If the assembly name references in the ImmutableObjectGraph.tt file are updated like this...:

<#@ assembly name="$(SolutionDir)packages\Microsoft.Bcl.Immutable.1.0.34\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll" #>
<#@ assembly name="$(SolutionDir)packages\ImmutableObjectGraph.Core.0.1.0-beta4\lib\portable-net45+win+wp80\ImmutableObjectGraph.dll" #>

Then the project builds fine, even with a full clean. If we could get the Nuget package install to re-write the references in the .tt file, it would be awesome.

Great work on the library, by the way. Any idea when this will be out of Beta?

Consider rearchitecting around custom SFG and Roslyn

Right now the system is based on T4, which is not only difficult to read but limits what kind of information we can use from the template source. We have an alternative to consider, as demo'd in this video.

Please review and comment on this issue with your likes/dislikes.

With(...) method returns instance with the same Identity

With(...) method returns instance with the same Identity. Is this correct?

var mat1 = AffineTransform.Create();
var mat2 = mat1.With(c: 2);
Assert.That(mat1.GetHashCode(), Is.Not.EqualTo(mat2.GetHashCode()));

where my GetHasCode is:

public override int GetHashCode()
{
  return this.Identity.GetHashCode();
}

OK. I found workaround:

 public override int GetHashCode()
{
      return $"{Identity} {a} {b} {c} {d} {e} {f}".GetHashCode();
}

Build fails when system culture is set to german

I'm getting the following build error (when using the roslyn approach):

Error The "GenerateCodeFromAttributes" task failed unexpectedly.
System.NotImplementedException: Die Kultur "Deutsch (Deutschland)" wird nicht unterstรผtzt.Die Pluralisierung wird derzeit nur fรผr die englische Sprache unterstรผtzt.
at System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(CultureInfo culture)
   at ImmutableObjectGraph.CodeGeneration.CodeGen..ctor(ClassDeclarationSyntax applyTo, Document document, IProgressAndErrors progress, Options options, CancellationToken cancellationToken) in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen.cs:line 75
   at ImmutableObjectGraph.CodeGeneration.CodeGen.<GenerateAsync>d__54.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen.cs:line 88
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at ImmutableObjectGraph.CodeGeneration.DocumentTransform.<TransformAsync>d__2.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration\DocumentTransform.cs:line 55
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.<<Execute>b__28_0>d.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 127
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 98
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute()
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 55
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext() 

The german error message says something like: The culture "German (Germany)" is not supported. Pluralization is only available in the English language.

"CreateWithIdentity" not generated on base class when derived class is in separate file

Steps to reproduce:

A.cs
    [GenerateImmutable]
    public partial class A
    {
        readonly string field1;
    }
B.cs
    [GenerateImmutable]
    public partial class B : A
    {
        readonly string field2;
    }

This will produce an error where A.generated.cs does not have CreateWithIdentity, yet B.generated.cs will still try to call it in ToA(), causing an error.

Putting both class A and class B in the same file correctly generates the CreateWithIdentity method.

Convenient mutations of deep recursive object graphs

When we have a recursive object graph, changing a leaf object requires that all ancestors also get recreated to point to the changed leaf. Writing such code is error prone and tedious. We should offer helper methods to automatically perform the "spine rewrite" in these cases.

Warning in compilation from generated code when input file imports ImmutableObjectGraph

When the input file has a using ImmutableObjectGraph; line, the generated code has that line twice, resulting in a compiler warning.

using ImmutableObjectGraph; // this Using statement should remain
using ImmutableObjectGraph.CodeGeneration;

[GenerateImmutable]
partial class SomeClass
{
    ImmutableDeque<int> deque; // something to use the ImmutableObjectGraph namespace
}

Support type hierarchies

Allow type hierarchies for immutable classes. For example, an immutable Fruit class with an immutable Apple derived type.

struct Optional

I just wanted to start the conversation about the Optional struct renamed from WithParameter. When I was looking for a name of that struct I was thinking about Optional or Maybe (more popular in functional programming) but I thought it could cause some naming conflicts. Moreover, it is harder to design general purpose struct than a specific one. For example it makes sense to have private constructor on WithParameter to force people to use implicit conversion but in Optional struct I would expect the constructor to be public. Just something to think about.

Enhancement: Add support for transforming writeable properties to readonly properties

Apologies if this was already requested and denied in another closed issue, but it would be nice to be able to transform this:

public class Fruit { string Color { get; set; } }

into this:

public class Fruit { 
    public Fruit(string color) {
        Color = color;
}
    public string Color { get; private set; }
}

It's simple enough to write the classes using only fields (no properties) as shown in the documentation if you know ahead of time that you want to use this library, but if you've already got more traditionally modeled classes written (say, ~400 of them), then it becomes another maddening task to convert them by hand (or maybe use another T4 template ;) to be transformed by this template.

I assume there's gotta be a good reason for only supporting fields currently. If there would be a lot of pain to implement what's described here, which you've already discovered, could you document it here in a comment? Otherwise, I think this would be very useful - as useful as the ability to reference classes in other files, which has already been requested.

Support for declaring fake "forward references" to classes and enums

At the moment, if you add a reference to a user-defined type, it's a battle to get the text templating engine to load in the type to allow it to generate the code based on the inner class. While there are ways to achieve it, it's not very much fun.

If we could supply a fake definition of dependency types defined in the assembly, the TT code would have enough information to understand the type exists to generate the class code. So we could use a pattern like...

<#@ Include File="..\ImmutableObjectGraph\ImmutableObjectGraph.tt.inc" #>
<#+
    [ForwardReference]
    class SomeObject { }

    class ImmutableThing
    {
        SomeObject someObjectOrOther;
    }
#>

SomeObject becomes an inner class to the text templater, so allows the ImmutableThing definition to compile, and therefore the TT can run without needing to find the true definition of SomeObject.

Generated class is not public

Roslyn-generated classes are missing the 'public' access modifier at the class declaration.

I may be missing something since I'm just getting started with this project, but are the generated classes purposely being created without the 'public' access modifier?

Generated files appear as:
partial class Fruit
rather than
public partial class Fruit

The class output by the T4 template DOES output the class with the 'public' access modifier. Is the different behavior by design?

Thanks

Support type hierarchies across multiple files and projects.

Having type hierarchies across multiple files and projects will allow for extensible designs when the base class is in a consumed library. This would allow for something such as the following:

Project A

    [GenerateImmutable(GenerateBuilder = true)]
    public partial class NodeA
    {
        readonly string name;
        readonly ImmutableHashSet<string> tags;
    }

    [GenerateImmutable(GenerateBuilder = true, DefineRootedStruct = true)]
    public partial class Tree
    {
        readonly ImmutableSortedSet<Tree> children;
        readonly NodeA node;
    }

Project B

    [GenerateImmutable(GenerateBuilder = true)]
    public partial class NodeB : NodeA
    {
        readonly string mySpecializedProperty;
    }

This will not work with the current way <Immutable>.To<InheritedType>() works. Perhaps those methods should be moved to extension methods, but then CreateWithIdentity(...) would have to become public and always generated.

The "GenerateCodeFromAttributes" task failed unexpectedly when DefineRootedStruct = True on nested immutable

The objects are as follows:

    [GenerateImmutable(GenerateBuilder = true, DefineRootedStruct = true)]
    public partial class Node
    {
        readonly string name;
        readonly ImmutableHashSet<string> tags;
    }

    [GenerateImmutable(GenerateBuilder = true)]
    public partial class Tree
    {
        readonly ImmutableSortedSet<Tree> children;
        readonly Node node;
    }

This results in the following build error:

The "GenerateCodeFromAttributes" task failed unexpectedly.
System.ArgumentException: Undefined type.
Parameter name: metaType
   at ImmutableObjectGraph.CodeGeneration.CodeGen.RootedStructGen.GetRootedTypeSyntax(MetaType metaType) in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen+RootedStructGen.cs:line 70
   at ImmutableObjectGraph.CodeGeneration.CodeGen.RootedStructGen.CreateRootProperty() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen+RootedStructGen.cs:line 429
   at ImmutableObjectGraph.CodeGeneration.CodeGen.RootedStructGen.CreateRootedStruct() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen+RootedStructGen.cs:line 176
   at ImmutableObjectGraph.CodeGeneration.CodeGen.RootedStructGen.GenerateCore() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen+RootedStructGen.cs:line 107
   at ImmutableObjectGraph.CodeGeneration.CodeGen.<GenerateAsync>d__56.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen.cs:line 151
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ImmutableObjectGraph.CodeGeneration.CodeGen.<GenerateAsync>d__54.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Roslyn\CodeGen.cs:line 89
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at ImmutableObjectGraph.CodeGeneration.DocumentTransform.<TransformAsync>d__2.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration\DocumentTransform.cs:line 55
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.<<Execute>b__28_0>d.MoveNext() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 127
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 98
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute()
   at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 55
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext() ImmutableGraph

Rooted structs assume DefineWithMethodsPerProperty=true, causing compile errors in generated code

After fixing @jviau's original repo in #57, the generated code still does not compile. Here is an isolated repro for the compilation issue:

[GenerateImmutable(DefineRootedStruct = true)]
public partial class Tree
{
    readonly ImmutableSortedSet<Tree> children;
}

The generated code fails with these errors:

error CS1061: 'Tree' does not contain a definition for 'AddChild' and no extension method 'AddChild' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'AddChildren' and no extension method 'AddChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'AddChildren' and no extension method 'AddChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'RemoveChild' and no extension method 'RemoveChild' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'RemoveChildren' and no extension method 'RemoveChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'RemoveChildren' and no extension method 'RemoveChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'RemoveChildren' and no extension method 'RemoveChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'WithChildren' and no extension method 'WithChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'Tree' does not contain a definition for 'WithChildren' and no extension method 'WithChildren' accepting a first argument of type 'Tree' could be found (are you missing a using directive or an assembly reference?)

Support stable API versioning strategy for adding fields

If a library is shipped with code generated by this library and later shipped again with an added field to one of the immutable types, a breaking change occurs in the generated code in that the Create and With methods' signatures are changed. This is unacceptable for libraries which must ship stable APIs.

The proposed design is to add support for a new GenerationAttribute(int) to decorate new fields with to avoid binary breaking changes. For example, consider this type that appears in v1 of a library:

[GenerateImmutable]
partial class Person
{
    readonly string firstName;
    readonly string lastName;
}

In v1.1 of the library, the age field is added:

[GenerateImmutable]
partial class Person
{
    readonly string firstName;
    readonly string lastName;
    [Generation(2)]
    readonly int age;
}

The code generator then produces extra Create and With methods:

// original methods are preserved:
public static Person Create(ImmutableObjectGraph.Optional<System.String> firstName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.String> lastName = default(ImmutableObjectGraph.Optional<System.String>));
public Person With(ImmutableObjectGraph.Optional<System.String> firstName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.String> lastName = default(ImmutableObjectGraph.Optional<System.String>));

// additional methods are added to support the next version
public static Person Create2(ImmutableObjectGraph.Optional<System.String> firstName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.String> lastName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> age = default(ImmutableObjectGraph.Optional<System.Int32>));
public Person With2(ImmutableObjectGraph.Optional<System.String> firstName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.String> lastName = default(ImmutableObjectGraph.Optional<System.String>), ImmutableObjectGraph.Optional<System.Int32> age = default(ImmutableObjectGraph.Optional<System.Int32>));

Note that the additional methods have a 2 suffix on them to match the generation that appears in the [Generation(2)] attribute.

When [Generation(3)] appears, yet another set of methods (Create3 and With3) are added in the code generation.

Special care is taken to ensure that folks calling a With method from an earlier generation do not lose the data in the fields added in a later generation. This can likely be done simply by implementing the earlier generation methods in terms of the latest generation method.

Note we do not simply add overloads of Create and With, using the same method name as original, as that would cause source breaking changes as folks who try to compile will likely get errors from the C# compiler due to ambiguous method overload matches, on account of the optional parameters.

Deserialization

How would you handle deserialization? I haven't looked into it much yet but based on my initial thoughts unless you did something fancy like deserialized to the builder type and then called the appropriate "GetImmutable()" method I don't see how you would handle serialization. Am I missing something?

I'm working on an app and I think the pattern would work well with Event Sourcing, CQRS and Actors allowing easy version snapshots to accept out-of-order events and potentially adding multi-threaded access with reader / writer lock constructs except no locks just read most recent snapshot. I need to serialize DTOs over the wire though :\

Observable with change notifications

The milestone constitutes support for binding an immutable object graph back into an app that requires an idea of the "current" value, or a root of an immutable object graph that can be considered the up-to-date one.
In addition, an efficient way to query for changes made between two versions of an immutable object graph, and perhaps events that can be raised automatically when the "current" value is updated with a new value to reflect the changes made to the graph.

Support T4 "extension" templates

The T4 template should include some kind of extensibility mechanism so that users can perform their own additional codegen to add members and/or nested types of their choosing based on the same template data.

We can dogfood this by moving the collection helper methods (Add, Remove, Clear) into an extension T4 template, and possibly other functionality as well.

Builders should have properties that are builders as well

The object that ToBuilder() returns from an immutable object should be a mutable object with properties whose types are themselves mutable. For instance, in the sample Fruit and Basket types, the Basket+Builder type should have a Contents property that is an ImmutableList<Fruit>+Builder class.

This makes the entire object graph mutable.

Of course all the conversions to mutable objects should be lazy to keep the GC pressure low.

Error generating code with nested classes inherited from a common base class

using System.Collections.Immutable;
using ImmutableObjectGraph;
using ImmutableObjectGraph.CodeGeneration;

[GenerateImmutable]
public partial class SyntaxNode
{
    [Required]
    int startPosition;
    [Required]
    int length;
}

[GenerateImmutable]
public partial class ElseBlock : SyntaxNode
{
    [Required]
    readonly string elseKeyword;
}

[GenerateImmutable]
public partial class ElseIfBlock : SyntaxNode
{
    [Required]
    readonly string elseIfKeyword;
    [Required]
    readonly string thenKeyword;
}

[GenerateImmutable]
public partial class IfNode : SyntaxNode
{
    [Required]
    readonly string ifKeyword;
    [Required]
    readonly string thenKeyword;
    readonly ImmutableList<ElseIfBlock> elseIfList;
    readonly ElseBlock elseBlock;
    [Required]
    readonly string endKeyword;
}

ErrorCode: CS0122
Description: 'SyntaxNode.length' is inaccessible due to its protection level

Error Code: CS7036
Description: There is no argument given that corresponds to the required formal parameter 'identity' of 'SyntaxNode.SyntaxNode(uint, int, int, bool)'

Enhancements for hierarchies

Make the design and use of hierarchical object graphs really easy.

Specifically:

  • Construction of an object should include a params for children.
  • The type should implement IEnumerable, so that each type is an enumeration of its children, allowing syntax such as: foreach (var child in parent) { /* ... */ }

[GenerateImmutable] on nested classes generates code with errors.

Applying [GenerateImmutable] to a nested class generates code that won't compile. For example:

    partial class Nested
    {
        [GenerateImmutable( GenerateBuilder = true, DefineWithMethodsPerProperty = true )]
        public partial class NestedClass
        {
            readonly string name;
        }
    }

Error with 3-tiered inheritance

    [GenerateImmutable(GenerateBuilder = false)]
    public abstract partial class SN { }

    [GenerateImmutable(GenerateBuilder = false)]
    public abstract partial class ParList : SN { }

    [GenerateImmutable(GenerateBuilder = false)]
    public partial class VarArgPar : ParList
    {
        Token varargOperator;
    }

Error Code: CS0111
Error Description: "Type 'ParList' already defines a member called 'ToVarArgPar' with the same parameter types"

Design-time builds fail during code generation on VS2015 Update 1 RC

The following warning shows up after project reload after generating immutable objects. Intellisense across the entire project is broken as a result.

Repro steps

  1. Create a C# console application project (I targeted .NET 4.6)
  2. Install-Package ImmutableObjectGraph.Generation -Pre -Version 1.0.15192-beta7
  3. Follow the steps in the readme.txt that appears to define your first immutable object.
  4. Reload the solution.
  5. Build the project. The build should succeed.

Expected (observed on VS2015 RTM)
Intellisense works and Error List is free of errors

Actual (observed on VS2015 Update 1 RC - vsuvscore 24623.00)
All open documents from the project are associated with the "Miscellaneous Files" project in the context dropdown.

You may also see this in the error list appear as a warning: (This is with a build of Roslyn from Oct 14th and VS 2015.)

The "GenerateCodeFromAttributes" task failed unexpectedly.
System.IO.FileLoadException: 
Could not load file or assembly 'Microsoft.CodeAnalysis.Workspaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)File name: 'Microsoft.CodeAnalysis.Workspaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'   
at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.<Execute>b__28_0()   
at System.Threading.Tasks.Task`1.InnerInvoke()   
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---   
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)   
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   
at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 98   
at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Helper.Execute() 
at ImmutableObjectGraph.CodeGeneration.Tasks.GenerateCodeFromAttributes.Execute() in C:\Users\andarno\git\ImmutableObjectGraph\src\ImmutableObjectGraph.CodeGeneration.Tasks\GenerateCodeFromAttributes.cs:line 55   
at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()

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.