Coder Social home page Coder Social logo

amis92 / recordgenerator Goto Github PK

View Code? Open in Web Editor NEW
69.0 9.0 10.0 1.08 MB

C# immutable records generator

Home Page: https://amis92.github.io/RecordGenerator/

License: MIT License

C# 98.18% PowerShell 1.82%
amadevus dotnet roslyn-analyzer csharp-library csharp immutability records nuget immutable code-generation

recordgenerator's Introduction

Amadevus.RecordGenerator

RecordGenerator logo

ℹ This is for v0.6 of RecordGenerator.

Documentation available at amis92.github.io/RecordGenerator (or in docs folder).

Description

C# Record Generator makes creating immutable record types a breeze! Just adorn your data class with [Record] attribute and keep your code clean and simple. The backing code is generated on build-time, including IntelliSense support (just save the file, Visual Studio will make a build in background).

Join the chat at gitter! License

NuGet package NuGet package preview MyGet package

GitHub Actions - .NET CI workflow Azure Pipelines Build Status


Demo

Installation, usage, examples and all other docs available at amis92.github.io/RecordGenerator

using System;
using Amadevus.RecordGenerator;

namespace QuickDemo
{
    [Record(Features.Default | Features.Equality)]
    public sealed partial class Contact
    {
        public int Id { get; }
        public string Name { get; }
        public string Email { get; }
        public DateTime? Birthday { get; }
    }

    public static class Program
    {
        public static void Main()
        {
            var adam = new Contact.Builder
            {
                Id = 1,
                Name = "Adam Demo",
                Email = "[email protected]"
            }.ToImmutable();
            var adamWithBday = adam.WithBirthday(DateTime.UtcNow);
            Console.WriteLine("Pretty display: " + adamWithBday);
            // Pretty display: { Id = 1, Name = Adam Demo, Email = [email protected], Birthday = 06.01.2020 23:17:06 }
            Console.WriteLine("Check equality: " + adam.Equals(adamWithBday));
            // Check equality: False
            Console.WriteLine("Check equality: " + adam.Equals(new Contact(1, "Adam Demo", "[email protected]", null)));
            // Check equality: True
        }
    }
}

The above is taken from QuickDemo sample

Development

To build the solution, .NET Core SDK v3.1.100 is required, as specified in global.json.

Credits

Amadevus.RecordGenerator wouldn't work if not for @AArnott AArnott's CodeGeneration.Roslyn.

Analyzers in Amadevus.RecordGenerator.Analyzers were inspired by xUnit.net's analyzers.

Contributions

All contributions are welcome, as well as critique. If you have any issues, problems or suggestions - please open an issue.

Visual Studio logo ™ Microsoft Corporation, used without permission.

RecordGenerator logo (on top) © 2017 Amadeusz Sadowski, all rights reserved.

recordgenerator's People

Contributors

amis92 avatar atifaziz avatar fl3pp avatar gitter-badger avatar jflepp avatar megakid avatar robinvanpoppel 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

recordgenerator's Issues

Which Standard to target

Revisiting this after my initial digestion of the shift to .NET Standard/Core in my library of interest. Q: Do I need to target .NET Standard 1.x? Or can I target 2.x? Or even both?

Fix the aggregate package

Currently it installs as dev dependency (as is configured). That means that the Attributes package isn't available for referencing the type from, which means the package doesn't actually serve it's purpose.

Also consider rebasing the generator onto the upcoming CodeGeneration.Roslyn.Sdk SDK.

Support for record hierarchies

That'd require calling base class ctor with appropriate parameters.

We'd need some stub method probably to get a hint what parameters to pass, like:

private BaseClass BaseCtorCall() => new BaseClass(Property1, Property3);

That'd also require modifications to mutators, making them virtual for non-sealed classes and possibly more. That's just off the top of my head.

Consider with-members returning same instance when unmodified

When I have implemented records types by-hand, the With* member have been coded to return the same instance if the new value is the same as current and it's a lot of boilerplate code. Following is an illustrated example:

sealed class Record
{
    public int    Integer { get; }
    public string String  { get; }
    
    public Record(int integer, string @string) =>
        (Integer, String) = (integer, @string);
        
    public Record WithInteger(int value) =>
        Integer == value ? this : new Record(value, String);

    public Record WithString(string value) =>
        String == value ? this : new Record(Integer, value);
}

This is especially useful for record types representing options. For a real example, see AwaitQueryOptions from my MoreLINQ project.

I would like to see RecordGenerator help with such no-brainer boilerplate. It can go a long way to avoid over-allocating at the cost of fair and shallow equality tests (most of the time). It can make immutability cheaper.

I have not thought about how the design of this would look like RecordGenerator; that is, would it be merely another feature flag on [Record] and/or offer property-level attributes allowing finer control.

Use camelCase instead of PascalCase parameter names

The naming convention in .NET for parameters is to use camel case (fooBar) but the generator uses Pascal case (FooBar) like properties do.

I'd be happy to submit a PR to fix this if it would be welcome.

Allow creating "lazy" builders

Creating a builder from an immutable object results in copying over all of its properties.

It could be optimized by creating a private class, derived from Builder, that also holds onto a reference of Record, and until any setter is called, serves values from the Record.

Use Roslyn for @-prefixing camelCased names

So, I approached this problem already with 1172c74 (using MS.CA.CSharp.Workspaces package). I'm considering keeping this PR open because I might be able to allow dependencies with CG.R soon, and then it'll be as nice as in the referenced commit.

Originally posted by @amis92 in #59

Nested records are generated without partial enclosing types

Given

// file Outer.cs
namespace Library
{
    class Outer
    {
        [Record]
        partial class Inner
        {
            public string Name { get; }
        }
    }
}

we expect:

// file Outer.Inner.RecordGenerator.cs
namespace Library
{
    class Outer
    {
        [Record]
        partial class Inner
        {
            // record generated ctor and mutators
        }
    }
}

Currently Inner is generated inside Library namespace, resulting in errors.

Enhance internal Builder workings

There could be a private or protected constructor in the record class that would take Builder parameter and directly copy off the builder fields. It'd significatly reduce reference copying for larger Records, where calling constructor results in copying all of Builder's fields as arguments, and the copying these arguments onto properties in Record constructor.

What's the difference between the Generator internal bits

This is where our paths fork a little bit. But, I was curious what the internal bits are doing as far as Record Generators are concerned? Just in understanding what is being generated from an analytical perspective, from RecordGenerator, etc. Specifically, what is RecordPartialGenerator, BuilderPartialGenerator, and DeconstructPartialGenerator? This appears to be the rub of what RecordGenerator is doing as a whole. Thanks!

NuGet package has incorrect layout

Currently analyzer dll is put into lib/portable-..., should be int analyzers/dotnet/cs

Requires fixing package build - possibly solved using VS2017 SDK-style csproj with built-in nuget pack.

Q: How to test Generators...

Hello again, and sorry for the question. Not sure how else to inquire...

How do you unit test your Roslyn Generators? With the understanding, of course, the stuff you are generating would be different than the stuff I want to generate.

Intuitively, it seems to me like, assuming I reference my Generators assembly from my unit test assembly, then connect the Attribute, etc, I should see the results of the generated bits in the resulting assembly image, that I could potentially use reflection, etc, in order to verify the results?

Thank you for any insights you can share!

Move to sdk-style csproj

Original: #15

Initial work on branch feature/sdk-csproj

When moving to SDK-style csproj with nuget pack suport, there are few issues:

  1. DevelopmentDependency needs to be specified, it's fixed but not released yet: NuGet/Home#4694

  2. dlls need to be put in analyzers/dotnet/cs - to achieve that we have to set <BuildOutputFolder>True</BuilOutputFolder>, as well as <IsTool>True</IsTool> - the second one stops pack from nesting dlls in {targetFramework} folders.

  3. Remove dependency on .NET Standard Library -probably. Needs testing and also maybe it would work if target framework was set to net451/452? If not, patching generated nuspec would be needed, possibly with inspiration from https://github.com/m0sa/NuGet.FrameworkAssemblyPacker which works around NuGet/Home#4853

Release NuGet versioning

NuGet for release channel (NuGet.org) should only include x.y.z (three parts) not four as it is actually visible.

Add a warning comment in generated code

A warning that modifications in this file will be lost on regeneration.

// WARNING any changes made to this file will be lost when generator is run again

Configuration in record attribute

A Flags enumeration that by default contains some options.

Also assembly-level attribute support that configures defaults for all records in a given assembly.

Include generic arity in filename

Now:

  • class Person -> Person.cs
  • class Person<T> -> Person.cs (conflict)

Expected:

  • class Person -> Person.cs
  • class Person<T> -> Person`1.cs (no conflict)

Add Deconstruct method

It should follow the pattern for deconstructing tuples, opt-out (so generated by default).

What do you do for deployment concerns?

I got my Roslyn code generation bits more or less sorted notwithstanding local testing. Now starting to turn my attention somewhat toward distribution concerns. Man oh man, the Roslyn docs are poor; and that's a generous assessment in my opinion. I found a halfway decent Roslyn composer which takes a snippet of code and spits out some Roslyn code generation code; reading between the lines, one can at least make some informed decisions how better to approach the issue, at any rate. There are perhaps half a dozen sites like that if you happen to ask the right search criteria, I suppose.

Anyway, there is the VSIX, correct, for purposes of drawing in the Analyzers? I see you also have this published via NuGet, however; is that acceptable in lieu of VSIX? What do you do for the Attributes and Generators?

Actually, I see you have a parent distribution that contains all the dependencies. That pretty much rolls it all up in one bundle, yes? Ostensibly, I assume it rolls up as a development only dependency?

As such, I'm wondering if I should target both .NET Standard 1.x (i.e. 1.6) and 2.x (i.e. 2.1, or whatever it is today).

Thanks!

Error with nested Record types

When there are nested record types, the generated files are incorrectly found to be out of date and also VS analyzer process crashes.

Support positional/ordered and nominal/unordered data objects/records

Positional records have public "primary" constructor and the order of their properties matters. They support all features but are not extensible while keeping backwards compatibility.

Nominal records have no accessible constructor, have to be built using builder or factory methods, order of their properties does not matter, and they can be extended with additional members while keeping binary compatibility with older versions.

Validate - pass parameters by value instead of ref

It's a (little but some anyway) overhead for reference types which are rather most of cases.

Also, Validate should probably not allow changing the actual values in constructor which it currently does.

Q: Did you start from VS2015?

Hello,

Not a question re: RecordGenerator itself, per se, but more the Visual Studio migration path. Did you start from VS2015 and migrate into VS2017? Or did you begin from VS2017 from scratch?

I've got a couple of projects in a solution that I am faced with such a migration path, and I wonder how best to do it in order to leverage CodeGeneration.Roslyn, much the same way as you did, in order to auto-generate some very boilerplate partial classes, overloaded operators, and so on.

Thanks!

Add ability to generate Builder struct

Builder would allow for easier instantiation in tree structures or multiple mutations (close to ImmutalbeList<T>.Builder), eg.

for:

partial class Person
{
  string FirstName { get; }
  Address PrimaryAddress { get; }
}

generator would provide, in addition to primary ctor, mutators and collection mutators, a builder:

partial class Person
{
  Builder ToBuilder() => new Builder { FirstName = FirstName, PrimaryAddress = PrimaryAddress };
  struct Builder
  {
    Builder(Person original)
    {
      FirstName = original.FirstName;
      PrimaryAddress = original.PrimaryAddress;
    }
    string FirstName { get; set; }
    Address PrimaryAddress { get; set; }
    Person ToRecord() => new Person(FirstName, PrimaryAddress);
  }
}

Usage

var person = GetVIP();
var newAddress = GetNewAddress();
var relocated = new Person.Builder(person) { PrimaryAddress = newAddress }.Build();

Of course the example above might easily be shortened as var relocated = person.WithPrimaryAddress(newAddress); but this is more useful when updating many properties at the same time.

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.