First off, this is a really good idea. A brief aside:
I wish C# made it easier to create cheap typed wrappers around integers, strings, ids, and so on. We are very "string happy" and "integer happy" as programmers; lots of things are represented as strings and integers which could have more information tracked in the type system; we don't want to be assigning customer names to customer addresses. A while back I wrote a series of blog posts (never finished!) about writing a virtual machine in OCaml, and one of the best things I did was wrapped every integer in the virtual machine with a type that indicates its purpose. That prevented so many bugs! OCaml makes it very easy to create little wrapper types; C# does not.
Second, I would not worry too much about duplicating the code. It's mostly an easy copy-paste, and you are unlikely to edit the code much or make mistakes. Spend your time solving real problems. A little copy-pasted code is not a big deal.
If you do want to avoid the copy-pasted code, then I would suggest using generics like this:
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();
}
}
And now you're done. You have types Id<App>
and Id<Payment>
instead of AppId
and PaymentId
, but you still cannot assign an Id<App>
to Id<Payment>
or Guid
.
Also, if you like using AppId
and PaymentId
then at the top of your file you can say
using AppId = MyNamespace.Whatever.Id<MyNamespace.Whatever.App>
and so on.
Third, you will probably need a few more features in your type; I assume this is not done yet. For example, you'll probably need equality, so that you can check to see if two ids are the same.
Fourth, be aware that default(Id<App>)
still gives you an "empty guid" identifier, so your attempt to prevent that does not actually work; it will still be possible to create one. There is not really a good way around that.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…