Ответ 1
Во-первых, это действительно хорошая идея. Вкратце:
Я бы хотел, чтобы в С# было проще создавать дешевые типизированные обертки вокруг целых чисел, строк, идентификаторов и так далее. Мы очень "счастливы как строки" и "счастливы как целое" как программисты; многие вещи представлены в виде строк и целых чисел, которые могут отслеживать больше информации в системе типов; мы не хотим присваивать имена клиентов адресам клиентов. Некоторое время назад я написал серию постов в блоге (так и не закончил!) О написании виртуальной машины в OCaml, и одна из лучших вещей, которые я сделал, заключалась в том, чтобы обернуть каждое целое число в виртуальной машине типом, который указывает ее назначение. Это предотвратило так много ошибок! OCaml позволяет очень легко создавать маленькие типы обёрток; С# нет.
Во-вторых, я бы не стал слишком беспокоиться о дублировании кода. В основном это просто копирование-вставка, и вы вряд ли будете много редактировать код или делать ошибки. Потратьте свое время на решение реальных проблем. Небольшой скопированный код не имеет большого значения.
Если вы хотите избежать копирования, вставленного в код, то я бы предложил использовать такие шаблоны:
struct App {}
struct Payment {}
public struct Id<T>
{
private readonly Guid _value;
public Id(string value)
{
var val = Guid.Parse(value);
CheckValue(val);
_value = val;
}
public Id(Guid value)
{
CheckValue(value);
_value = value;
}
private static void CheckValue(Guid value)
{
if(value == Guid.Empty)
throw new ArgumentException("Guid value cannot be empty", nameof(value));
}
public override string ToString()
{
return _value.ToString();
}
}
И теперь вы сделали. У вас есть типы Id<App>
и Id<Payment>
вместо AppId
и PaymentId
, но вы все равно не можете назначить Id<App>
для Id<Payment>
или Guid
.
Кроме того, если вам нравится использовать AppId
и PaymentId
то в верхней части файла вы можете сказать
using AppId = MyNamespace.Whatever.Id<MyNamespace.Whatever.App>
и так далее.
В-третьих, вам, вероятно, понадобится еще несколько функций в вашем типе; Я предполагаю, что это еще не сделано. Например, вам, вероятно, понадобится равенство, чтобы вы могли проверить, совпадают ли два идентификатора.
В-четвертых, имейте в виду, что default(Id<App>)
прежнему дает вам идентификатор "пустой guid", поэтому ваша попытка предотвратить это на самом деле не работает; все еще будет возможно создать один. Существует не очень хороший способ обойти это.