Генерация неизменного значения объекта в C #: шаблоны PostSharp или T4? - PullRequest
4 голосов
/ 10 апреля 2011

Мне надоел шаблонный код объекта неизменного значения. Позволят ли шаблоны 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], что вполне нормально. Я бы хотел, чтобы опция генерировала class или struct в зависимости от исходного типа. Также обратите внимание, что GetHashCode должен будет пропустить проверки null для членов типа значения.

Я вполне уверен, что любая из этих технологий обладает такой способностью, но я сам не был ничем иным, как потребителем шаблонов аспектов PostSharp / T4, и я не знаю, какой из них лучше или проще для этой задачи. Какой бы ответ вы ни ответили, я потрачу день или два на то, чтобы научиться делать это:).

Ответы [ 3 ]

2 голосов
/ 06 ноября 2016

Я недавно сделал что-то, что вы просите (используя шаблоны 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 (строка firstName, строка lastName, int age = 18), который инициализируетценности.
  • Рабочие реализации для Равных (другой объект) и Равных (другой человек).Также он добавит IEquatable интерфейс для вас.Рабочие реализации для operator == и operator! =
  • Рабочая реализация 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);
  }

}

И есть некоторые дополнительные функции, как описано на странице проекта.

2 голосов
/ 10 апреля 2011

Похоже, что вы хотите просто украсить свою структуру шаблонным материалом, так что PostSharp будет подходящим вариантом.Причина в том, что с T4 вам нужно

  1. Найти все объекты, которые необходимо изменить (для которых все еще требуются атрибуты или какой-либо тип маркера)
  2. Убедитесь, что вы нек структуре уже применены изменения
  3. Используйте EnvDTE для добавления элементов (не всегда согласованных)

См. http://table2dto.codeplex.com для отличного примера T4 для этого

Для этого вам необходимо использовать EnvDTE (см. http://dfactor.codeplex.com, чтобы узнать, как это сделать)

Проблема в том, что вам нужно сгенерировать шаблонный кодчто вам нужно использовать во время разработки (что не похоже на то, что у вас есть что-либо), тогда вам потребуется некоторое время, чтобы заставить PostSharp работать на вас.

PostSharp будет вашим лучшим выбором.

0 голосов
/ 10 апреля 2011

Я бы порекомендовал использовать для этого ReSharper: он очень хорошо генерирует члены равенства для таких случаев, поэтому единственное, что вы должны написать, - это набор методов получения свойств.

Также обратите внимание, что обычно вы должны различать структуры и объекты в методе Equals, чтобы быстрее проверить равенство, поэтому, если это важно, T4 здесь не будет хорошим выбором.

...