Skip to content

[Discussion] Fundamental redesign of the libraryΒ #102

@andrewlock

Description

@andrewlock

First of all, thanks everyone for your interest in this little source generator, as well as all the proposals for new features to include in the Ids! πŸ™

All of the proposals I've seen, and the associated open issues have merit. The trouble is, adding support for all of them would add a huge array of options and flags to the library, making it more and more cumbersome to use, and adding an increasing burden to keep updating the library to add extra options. I don't think that outcome is sustainable or desirable.

Instead, I was considering rebuilding the source generator with a fundamentally different design. Instead of "building in" the definition of an ID into the source generator, consumers of the library would define the "templates" themselves (it would come with default templates matching the existing IDs). The [StronglyTypedId] then selects which template to use. For example, you would decorate your Ids something like this:

using StronglyTypedIds;
[assembly:StronglyTypedIdDefaults("int")] // choose the default template

[StronglyTypedId] // use the default template
public partial struct DefaultId {}

[StronglyTypedId("guid")] // use a Guid template
public partial struct GuidId {}

[StronglyTypedId("guid-efcore")] // use a Guid template with extra converters etc
public partial struct MyEfCoreIdId {}

You would add templates to your project by adding txt files with the name StrongTypedId_<TEMPLATE>.txt to the project. The source generator would read those, to build up a dictionary of templates, e.g.

  • StrongTypedId_Int.txt, "int" template
  • StrongTypedId_Guid.txt, "guid" template
  • StrongTypedId_Guid-efcore.txt, "guid-efcore" template

An example of the template is shown below. The idea is that it's a "valid" csharp class, so it's easy to author them and then just change the extension. You would use TESTID (or similar) as a placeholder for the name of the ID:

readonly partial struct TESTID : System.IComparable<TESTID>, System.IEquatable<TESTID>
{
    public System.Guid Value { get; }

    public TESTID(System.Guid value)
    {
        Value = value;
    }

    public static TESTID New() => new TESTID(System.Guid.NewGuid());
    public static readonly TESTID Empty = new TESTID(System.Guid.Empty);

    public bool Equals(TESTID other) => this.Value.Equals(other.Value);
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is TESTID other && Equals(other);
    }

    public override int GetHashCode() => Value.GetHashCode();

    public override string ToString() => Value.ToString();
    public static bool operator ==(TESTID a, TESTID b) => a.Equals(b);
    public static bool operator !=(TESTID a, TESTID b) => !(a == b);

    public int CompareTo(TESTID other) => Value.CompareTo(other.Value);
}

The source generator would use this template to generate the partial for each of the decorated IDs. It would take care of using the correct namespace, class hierarchy, and the correct ID names etc. by doing a basic replacement of TESTID for DefaultId etc.

To help people get started easily, the source generator could automatically copy the existing "basic" definitions for Guid/int/long/string IDs etc to a project. Consumers can edit these, delete them, or odd new ones as they see fit.

The big advantage is that it's easy for you to completely control how your IDs are generated in your own project, without needing to update the library. You can create as many different templates, as many as you need, for different purposes.

So... what do people think!?

This would obviously be a big change, but I think it will make the library much more flexible for everyone. I've written a PoC, and it all seems to work well, the question is, what do other people think?

Drop a πŸ‘ if you think this sounds like a good idea, or πŸ‘Ž if you don't. And feel free to drop me a comment!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions