#c# #domain-driven-design
#c# #domain-driven-design
Вопрос:
Объекты значений в DDD неизменяемы, свойства обычно устанавливаются один раз с помощью конструктора.
Иногда мне требуется копия объекта value только с некоторыми изменениями, например, десять свойств должны быть скопированы, и одно свойство получит новое значение. В этом случае я хочу избежать использования конструктора с одиннадцатью параметрами, вместо этого я хочу реализовать метод, который возвращает копию, но в то же время применяет некоторые изменения к свойствам. Я знаю, что могу сделать это с помощью отражения, но хотел бы проверить, можно ли избежать отражения.
public class Foo
{
public int Bar { get; set; } // actual use case is { get; private set; }
public Foo(int bar)
{
Bar = bar;
}
public Foo CloneAndApply(Action<Foo> apply)
{
var result = new Foo(Bar);
apply(result);
return resu<
}
}
Это работает, потому что свойство «Bar» имеет общедоступный установщик. Мне нужно что-то, что позволяет устанавливать «Bar», когда он является закрытым членом.
var test = new Foo(1);
var clone = test.CloneAndApply(x => x.Bar = 2);
Console.WriteLine(clone.Bar);
Комментарии:
1.
public Foo CloneAndApply(int bar)
Не будет работать? Я знаю, что ваш пример, скорее всего, является упрощенной версией того, что вам нужно, но, может быть, вам просто нужно несколько разных конструкторов?2. @tymtam Да, это сработало бы, но оставляет меня с уродливым количеством параметров для CloneAndApply, когда я использую много свойств — плюс мне придется менять подпись каждый раз, когда свойство будет добавлено или удалено
Ответ №1:
Я получаю эти записи pre .net5 с WithX
помощью методов, которые передают свойство, которое должно быть другим. Это не сложно, но это работает.
public class Foo
{
public int X { get; private set; }
public int Bar { get; private set; }
public Foo(int x, int bar) { X = x; Bar = bar; }
public Foo WithBar(int bar) => new Foo(X, bar);
}
}
Комментарии:
1. Это то, что делает Roslyn, FWIW
Ответ №2:
Вас могут заинтересовать записи, введенные в C # 9. Записи содержат много частей, но одной из них является поддержка «withers«, которые взаимодействуют с новыми свойствами, доступными только для инициализации, что позволяет легко создавать измененные копии записей.
Записи поддерживают такие вещи, как первичные конструкторы, которые я собираюсь здесь замалчивать. На простом уровне ваша запись может выглядеть так:
public record Foo
{
public int Bar { get; init; }
public Foo(int bar)
{
Bar = bar;
}
}
и может использоваться как:
var foo = new Foo(3);
var foo2 = foo with { Bar = 4 };
Эта запись также автоматически реализует равенство, основанное на равенстве ее отдельных членов, и переопределенную ToString
реализацию.
Долгосрочный план состоит в том, чтобы разрешить использование withers с типами, отличными от записей, хотя (начиная с C # 9) это еще не поддерживается.
Если вы воспользуетесь преимуществами первичных конструкторов, вы сможете написать свою запись еще более лаконично:
public record Foo(int Bar);
Это автоматически генерирует Bar
свойство с get
init
помощью средств доступа и и конструктора, который ему присваивает.
Комментарии:
1. Прекрасно, одна из причин начать использовать .NET 5 — жаль, что в настоящее время я застрял с Core 3.1. Я думаю, что это ответ, поскольку, вероятно, нет способа сделать это без отражения и C # < 9
2. Вы можете использовать это в .NET Core 3.1, если вы используете .NET 5 SDK и задаете
<LangVersion>9</LangVersion>
в своем csproj. Вам также понадобитсяnamespace System.Runtime.CompilerServices { internal static class IsExternalInit { } }
где-нибудь в вашем проекте.3. Полезно знать — я работаю в команде, и это не случайное изменение, которое я могу внести, но, похоже, влияние можно контролировать
Ответ №3:
Другим решением может быть отражение. Это также можно использовать для присвоения значений частным свойствам:
class Program
{
static void Main(string[] args)
{
var test = new Foo(1);
var clone = test.CloneAndApply(x => x.GetType().GetProperty("Bar").SetValue(x, 2));
Console.WriteLine(clone.Bar);
}
}
public class Foo
{
public int Bar { get; private set; }
public Foo(int bar)
{
Bar = bar;
}
public Foo CloneAndApply(Action<Foo> apply)
{
var result = new Foo(Bar);
apply(result);
return resu<
}
}
Комментарии:
1. Не то, что я ищу, поскольку он использует отражение, но все же хороший пример того, как это сделать с отражением
2. @TvdH А, понятно. Извините. Просто из интереса, почему без отражения? Есть ли какая-то конкретная причина?