Creating an immutable value object in C #: PostSharp or T4 templates?

I'm sick of the immutable object code of the template. Can either PostSharp or T4 templates allow me to do the following conversion?

Input:

public struct Name { public string FirstName; public string LastName; } 

Output:

 public struct Name : IEquatable<Name> { private readonly string firstName; private readonly string lastName; public string FirstName { get { return this.firstName; } } public string LastName { get { return this.lastName; } } public Name(string firstName, string lastName) { this.firstName = firstName; this.lastName = lastName; } public bool Equals(Name other) { return this.FirstName == other.FirstName && this.LastName == other.LastName; } public override bool Equals(object obj) { return obj is Name && this.Equals((Name)obj); } public override int GetHashCode() { unchecked { int hash = 17; if (this.FirstName != null) { hash = hash * 29 + this.FirstName.GetHashCode(); } if (this.LastName != null) { hash = hash * 29 + this.LastName.GetHashCode(); } return hash; } } public static bool operator==(Name a, Name b) { return a.Equals(b); } public static bool operator !=(Name a, Name b) { return !(a == b); } } 

Obviously, PostSharp will require [MakeImmutable] annotations, which is great. I would like to be able to generate either class or struct depending on the source type. Also note that GetHashCode should omit null checks for members of the value type.

I am sure that any technology has this feature, but I was not something like a consumer of PostSharp / T4 templates on my own, I do not know which one will be better or easier for this task. Whatever answer you give, I will spend a day or two learning to do this job :).

+4
source share
3 answers

It looks like you just want to decorate your structure with templates, so PostSharp will be your way. The reason is that with T4 you need

  • Find all the objects that need to be changed (for which attributes or some marker are still required)
  • Make sure you don’t have any changes to the structure yet.
  • Use EnvDTE to add items (not always consistent)

See http://table2dto.codeplex.com for a great T4 example of this.

For this you will need to use EnvDTE (see http://dfactor.codeplex.com for a code on how to do this)

The problem is that if you need to generate the template code that you want to use during development (which doesn't look like what you have), then it will take some thinking to get PostSharp to work for you.

PostSharp will be your best choice.

+2
source

I did something that does what you have been asking lately (using T4 templates), so this is absolutely possible:

https://github.com/xaviergonz/T4Immutable

For example, given this:

 [ImmutableClass(Options = ImmutableClassOptions.IncludeOperatorEquals)] class Person { private const int AgeDefaultValue = 18; public string FirstName { get; } public string LastName { get; } public int Age { get; } [ComputedProperty] public string FullName { get { return FirstName + " " + LastName; } } } 

It automatically generates the following for you in a separate file of a partial file:

  • A constructor such as public Person (string firstName, string lastName, int age = 18) that initialize the values.
  • Working implementations for Equals (other object) and Equals (Person other). It will also add the IEquatable interface for you. Implementation work for operator == and operator! =
  • Working implementation of GetHashCode () Better than ToString () with an output, for example, "Person {FirstName = John, LastName = Doe, Age = 21}"
  • The Person With (...) method, which can be used to create a new immutable clone with 0 or more changed properties (for example, var janeDoe = johnDoe.With (firstName: "Jane", age: 20)

Therefore, it will generate this (excluding some redundant attributes):

 using System; partial class Person : IEquatable<Person> { public Person(string firstName, string lastName, int age = 18) { this.FirstName = firstName; this.LastName = lastName; this.Age = age; _ImmutableHashCode = new { this.FirstName, this.LastName, this.Age }.GetHashCode(); } private bool ImmutableEquals(Person obj) { if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(obj, null)) return false; return T4Immutable.Helpers.AreEqual(this.FirstName, obj.FirstName) && T4Immutable.Helpers.AreEqual(this.LastName, obj.LastName) && T4Immutable.Helpers.AreEqual(this.Age, obj.Age); } public override bool Equals(object obj) { return ImmutableEquals(obj as Person); } public bool Equals(Person obj) { return ImmutableEquals(obj); } public static bool operator ==(Person a, Person b) { return T4Immutable.Helpers.AreEqual(a, b); } public static bool operator !=(Person a, Person b) { return !T4Immutable.Helpers.AreEqual(a, b); } private readonly int _ImmutableHashCode; private int ImmutableGetHashCode() { return _ImmutableHashCode; } public override int GetHashCode() { return ImmutableGetHashCode(); } private string ImmutableToString() { var sb = new System.Text.StringBuilder(); sb.Append(nameof(Person) + " { "); var values = new string[] { nameof(this.FirstName) + "=" + T4Immutable.Helpers.ToString(this.FirstName), nameof(this.LastName) + "=" + T4Immutable.Helpers.ToString(this.LastName), nameof(this.Age) + "=" + T4Immutable.Helpers.ToString(this.Age), }; sb.Append(string.Join(", ", values) + " }"); return sb.ToString(); } public override string ToString() { return ImmutableToString(); } private Person ImmutableWith(T4Immutable.WithParam<string> firstName = default(T4Immutable.WithParam<string>), T4Immutable.WithParam<string> lastName = default(T4Immutable.WithParam<string>), T4Immutable.WithParam<int> age = default(T4Immutable.WithParam<int>)) { return new Person( !firstName.HasValue ? this.FirstName : firstName.Value, !lastName.HasValue ? this.LastName : lastName.Value, !age.HasValue ? this.Age : age.Value ); } public Person With(T4Immutable.WithParam<string> firstName = default(T4Immutable.WithParam<string>), T4Immutable.WithParam<string> lastName = default(T4Immutable.WithParam<string>), T4Immutable.WithParam<int> age = default(T4Immutable.WithParam<int>)) { return ImmutableWith(firstName, lastName, age); } } 

And there are a few more features as described on the project page.

+3
source

I would recommend using ReSharper for this: it generates equality members very well for such cases, so the only thing you should write is a set of getters properties.

Also note that usually you should distinguish between structures and objects in the Equals method in order to check equality faster, so if this is important, T4 will not be a good choice here.

-1
source

Source: https://habr.com/ru/post/1347388/


All Articles