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

发布于 2024-10-31 06:17:16 字数 1737 浏览 8 评论 0原文

我厌倦了样板不可变值对象代码。 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 模板的使用者,所以我不知道哪种技术对于这项任务来说更好或更容易。无论你给出哪个答案,我都会花一两天的时间学习足够的知识来完成这项工作:)。

I'm getting sick of boilerplate immutable value object code. Would either PostSharp or T4 templates allow me to do the following transformation?

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 would require a [MakeImmutable] annotation, which is fine. I'd want the option of generating either a class or struct depending on the original type. Also note that GetHashCode would have to omit the null checks for value-type members.

I am fairly sure either technology has this capability, but having not been anything but a consumer of PostSharp aspects/T4 templates myself, I don't know which would be a better or easier one for this task. Whichever answer you give, I'll go spend a day or two learning enough to make this work :).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心碎的声音 2024-11-07 06:17:16

我最近做了一些事情,可以满足您的要求(使用 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, string
    lastName, int age = 18) 将初始化这些值。
  • Equals(object other) 和 Equals(Person other) 的工作实现。
    它还将为您添加 IEquatable 接口。在职的
    运算符==和运算符!=的
  • 实现 GetHashCode() 的有效实现 更好的 ToString(),其输出如“Person { FirstName=John, LastName=Doe, Age=21 }”
  • 一个 Person With(...) 方法可用于生成一个新的不可变克隆,其中 0 个或多个属性已更改(例如 var janeDoe = johnDoe.With(firstName: "Jane",age: 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);
  }

}

:项目页面中解释了更多功能。

I made something that does what you ask recently (using T4 templates), so it 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 will automatically generate for you in a separate partial class file the following:

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

So 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 some more features as explained in the project page.

安静被遗忘 2024-11-07 06:17:16

看起来您只想用样板文件来装饰您的结构,因此 PostSharp 将是最佳选择。原因是因为使用 T4,您需要

  1. 查找所有需要更改的对象(这仍然需要属性或某种类型的标记)
  2. 确保您尚未将更改应用于结构
  3. 使用 EnvDTE 添加项目(并不总是一致)

的出色 T4 示例

请参阅 http://table2dto.codeplex.com 了解执行此操作 您需要使用 EnvDTE(请参阅 http://dfactor.codeplex.com 了解如何这样做)

问题是,如果您需要生成在设计时需要使用的样板代码(看起来您没有任何东西),那么需要一些思考才能让 PostSharp 为您工作。

PostSharp 将是您最好的选择。

It looks like you want to just decorate your struct with boilerplate stuff, so PostSharp is going to be the way to go. The reason why is because with T4 you would need to

  1. Find all objects that need to be changed (which still requires attributes or some type of marker)
  2. Make sure you don't already have the changes applied to the struct
  3. Use EnvDTE to add the items (not always consistent)

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

To do this you would need to employ the use of EnvDTE (see http://dfactor.codeplex.com for code on how to do this)

The problem is, if you need to generate boilerplate code that you need to use at design time (which it doesnt look like you have anything) then it's going to require some thinking to get PostSharp to work for you.

PostSharp is going to be your best bet.

幻想少年梦 2024-11-07 06:17:16

我建议为此使用 ReSharper:它可以在这种情况下很好地生成相等成员,因此您唯一需要编写的是一组属性 getter。

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

I'd recommend to use ReSharper for this: it generates equality members very well for such cases, so the only thing you must write is a set of property getters.

Also note that normally you must distinguish between structs and objects in Equals method to check the equality faster, so if this is important, T4 won't be a good choice here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文