Skip to content

Records code-gen

Compare
Choose a tag to compare
@louthy louthy released this 28 Nov 22:04

This is the first-pass release of the LanguageExt.CodeGen feature for generating record types. This means there's no need to derive from Record<TYPE> any more, and also allows records to be structs, which is a real bonus.

To create a new record, simply attach the [Record] attribute to a partial class or a partial struct:

    [Record]
    public partial struct Person
    {
        public readonly string Forename;
        public readonly string Surname;
    }

You may also use properties:

    [Record]
    public partial struct Person
    {
        public string Forename { get; }
        public string Surname { get; }
    }

As well as computed properties:

    [Record]
    public partial struct Person
    {
        public string Forename { get; }
        public string Surname { get; }
        public string FullName => $"{Forename} {Surname}";
    }

The features of the generated record are:

  • Auto constructor provision
  • Auto deconstructor provision
  • Structural equality (with equality operators also)
  • Structural ordering (with ordering operators also)
  • GetHashCode provision
  • Serialisation
  • Sensible default ToString implementation
  • With method for immutable transformation
  • Lens fields for composing nested immutable type transformation

Coming soon (for both records and unions) is the ability to provide class-instances to override the default behaviour of equality, ordering, hash-code generation, and constructor validation.

The generated code looks like this:

    [System.Serializable]
    public partial struct Person : System.IEquatable<Person>, System.IComparable<Person>, System.IComparable, System.Runtime.Serialization.ISerializable
    {
        public Person(string Forename, string Surname)
        {
            this.Forename = Forename;
            this.Surname = Surname;
        }

        public void Deconstruct(out string Forename, out string Surname)
        {
            Forename = this.Forename;
            Surname = this.Surname;
        }

        public Person(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            this.Forename = (string)info.GetValue("Forename", typeof(string));
            this.Surname = (string)info.GetValue("Surname", typeof(string));
        }

        public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            info.AddValue("Forename", this.Forename);
            info.AddValue("Surname", this.Surname);
        }

        public static bool operator ==(Person x, Person y) => x.Equals(y);
        public static bool operator !=(Person x, Person y) => !(x == y);
        public static bool operator>(Person x, Person y) => x.CompareTo(y) > 0;
        public static bool operator <(Person x, Person y) => x.CompareTo(y) < 0;
        public static bool operator >=(Person x, Person y) => x.CompareTo(y) >= 0;
        public static bool operator <=(Person x, Person y) => x.CompareTo(y) <= 0;
        public bool Equals(Person other)
        {
            if (LanguageExt.Prelude.isnull(other))
                return false;
            if (!default(LanguageExt.ClassInstances.EqDefault<string>).Equals(this.Forename, other.Forename))
                return false;
            if (!default(LanguageExt.ClassInstances.EqDefault<string>).Equals(this.Surname, other.Surname))
                return false;
            return true;
        }

        public override bool Equals(object obj) => obj is Person tobj && Equals(tobj);
        public int CompareTo(object obj) => obj is Person p ? CompareTo(p) : 1;
        public int CompareTo(Person other)
        {
            if (LanguageExt.Prelude.isnull(other))
                return 1;
            int cmp = 0;
            cmp = default(LanguageExt.ClassInstances.OrdDefault<string>).Compare(this.Forename, other.Forename);
            if (cmp != 0)
                return cmp;
            cmp = default(LanguageExt.ClassInstances.OrdDefault<string>).Compare(this.Surname, other.Surname);
            if (cmp != 0)
                return cmp;
            return 0;
        }

        public override int GetHashCode()
        {
            const int fnvOffsetBasis = -2128831035;
            const int fnvPrime = 16777619;
            int state = fnvOffsetBasis;
            unchecked
            {
                state = (default(LanguageExt.ClassInstances.EqDefault<string>).GetHashCode(this.Forename) ^ state) * fnvPrime;
                state = (default(LanguageExt.ClassInstances.EqDefault<string>).GetHashCode(this.Surname) ^ state) * fnvPrime;
            }

            return state;
        }

        public override string ToString()
        {
            var sb = new System.Text.StringBuilder();
            sb.Append("Person(");
            sb.Append(LanguageExt.Prelude.isnull(Forename) ? $"Forename: [null]" : $"Forename: {Forename}");
            sb.Append($", ");
            sb.Append(LanguageExt.Prelude.isnull(Surname) ? $"Surname: [null]" : $"Surname: {Surname}");
            sb.Append(")");
            return sb.ToString();
        }

        public Person With(string Forename = null, string Surname = null) => new Person(Forename ?? this.Forename, Surname ?? this.Surname);
        public static readonly Lens<Person, string> forename = Lens<Person, string>.New(_x => _x.Forename, _x => _y => _y.With(Forename: _x));
        public static readonly Lens<Person, string> surname = Lens<Person, string>.New(_x => _x.Surname, _x => _y => _y.With(Surname: _x));
    }