在c#: PostSharp或T4模板中生成不可变值对象

本文关键字:不可变 对象 PostSharp T4 | 更新日期: 2023-09-27 17:49:30

我已经厌倦了模板式的不可变值对象代码。是否PostSharp或T4模板允许我做以下转换?

输入:

public struct Name
{
    public string FirstName;
    public string LastName;
}
输出:

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);
    }
}

显然PostSharp需要[MakeImmutable]注释,这很好。我希望根据原始类型生成classstruct的选项。还要注意,GetHashCode必须省略null对值类型成员的检查。

我很确定这两种技术都有这种能力,但我自己只是PostSharp方面/T4模板的消费者,我不知道哪一种技术更适合这项任务。不管你给出的答案是什么,我都会花一两天的时间来学习足够的东西来让它工作:)。

在c#: PostSharp或T4模板中生成不可变值对象

我做了一些你最近问的东西(使用T4模板),所以这是绝对可能的:

https://github.com/xaviergonz/T4Immutable

例如:

[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;
    }
  }
}

它会在一个单独的部分类文件中自动为您生成以下内容:

  • 一个构造函数,如public Person(string firstName, stringlastName, int age = 18),将初始化值。
  • Equals(object other)和Equals(Person other)的工作实现。它还将为您添加均衡界面。工作operator==和operator!的实现=
  • GetHashCode()的工作实现一个更好的ToString()与输出,如"Person {FirstName=John, LastName=Doe, Age=21}"
  • 一个人(…)的方法,可以用来生成一个新的不可变克隆与0或多个属性改变(例如var janeDoe = johnDoe)。与(firstName:"Jane",年龄:20)

所以它会生成这个(不包括一些多余的属性):

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);
  }
}

还有更多的功能,如项目页面中所解释的。

看起来你只是想装饰你的结构与样板的东西,所以PostSharp将是一种方式。原因是对于T4,您需要

  1. 查找所有需要更改的对象(仍然需要属性或某种类型的标记)
  2. 确保你还没有对结构体
  3. 进行修改
  4. 使用EnvDTE添加项目(不总是一致)

请参阅http://table2dto.codeplex.com查看T4的示例

要做到这一点,您需要使用EnvDTE(参见http://dfactor.codeplex.com获取如何做到这一点的代码)

问题是,如果你需要生成样板代码,你需要在设计时使用(它看起来不像你有什么),那么它将需要一些思考让PostSharp为你工作。

PostSharp将是你最好的选择

我建议使用ReSharper:对于这种情况,它可以很好地生成相等成员,所以你唯一需要写的就是一组属性getter。

还请注意,通常您必须在Equals方法中区分结构体和对象,以便更快地检查相等性,因此如果这很重要,那么T4将不是一个好的选择。