强类型 Guid 作为通用结构

我已经在代码中犯了两次相同的错误,如下所示:


void Foo(Guid appId, Guid accountId, Guid paymentId, Guid whateverId)

{

...

}


Guid appId = ....;

Guid accountId = ...;

Guid paymentId = ...;

Guid whateverId =....;


//BUG - parameters are swapped - but compiler compiles it

Foo(appId, paymentId, accountId, whateverId);

好的,我想防止这些错误,所以我创建了强类型 GUID:


[ImmutableObject(true)]

public struct AppId

{

    private readonly Guid _value;


    public AppId(string value)

    {            

        var val = Guid.Parse(value);

        CheckValue(val);

        _value = val;

    }      


    public AppId(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();

    }

}

另一个用于 PaymentId:


[ImmutableObject(true)]

public struct PaymentId

{

    private readonly Guid _value;


    public PaymentId(string value)

    {            

        var val = Guid.Parse(value);

        CheckValue(val);

        _value = val;

    }      


    public PaymentId(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();

    }

}

这些结构几乎相同,有很多重复的代码。不是吗?


除了使用类而不是结构之外,我想不出任何优雅的方法来解决它。我宁愿使用结构,因为空检查,更少的内存占用,没有垃圾收集器开销等......


您知道如何在不重复代码的情况下使用 struct 吗?


森栏
浏览 135回答 3
3回答

慕雪6442864

首先,这是一个非常好的主意。一个简短的旁白:我希望 C# 能够更轻松地围绕整数、字符串、id 等创建廉价的类型化包装器。作为程序员,我们非常“字符串快乐”和“整数快乐”;很多东西都表示为字符串和整数,可以在类型系统中跟踪更多信息;我们不想将客户名称分配给客户地址。不久前,我写了一系列关于在 OCaml 中编写虚拟机的博客文章(从未完成!),我所做的最好的事情之一就是将虚拟机中的每个整数都包装成一个表明其用途的类型。这防止了这么多的错误!OCaml 使得创建小包装器类型变得非常容易;C# 没有。其次,我不会太担心重复代码。它主要是一个简单的复制粘贴,你不太可能编辑代码或犯错误。花时间解决实际问题。一点点复制粘贴的代码没什么大不了的。如果您确实想避免复制粘贴的代码,那么我建议您使用这样的泛型:struct App {}struct Payment {}public struct Id<T>{&nbsp; &nbsp; private readonly Guid _value;&nbsp; &nbsp; public Id(string value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; var val = Guid.Parse(value);&nbsp; &nbsp; &nbsp; &nbsp; CheckValue(val);&nbsp; &nbsp; &nbsp; &nbsp; _value = val;&nbsp; &nbsp; }&nbsp; &nbsp; public Id(Guid value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; CheckValue(value);&nbsp; &nbsp; &nbsp; &nbsp; _value = value;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; }&nbsp; &nbsp; private static void CheckValue(Guid value)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if(value == Guid.Empty)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new ArgumentException("Guid value cannot be empty", nameof(value));&nbsp; &nbsp; }&nbsp; &nbsp; public override string ToString()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return _value.ToString();&nbsp; &nbsp; }}现在你完成了。您有类型Id<App>andId<Payment>而不是AppIdand PaymentId,但您仍然不能分配Id<App>to Id<Payment>or Guid。此外,如果您喜欢使用AppId,PaymentId然后在文件顶部,您可以说using AppId = MyNamespace.Whatever.Id<MyNamespace.Whatever.App>等等。第三,您可能需要在您的类型中添加更多功能;我认为这还没有完成。例如,您可能需要相等,以便检查两个 id 是否相同。第四,请注意,它default(Id<App>)仍然会给您一个“空 guid”标识符,因此您阻止它的尝试实际上不起作用;仍然可以创建一个。没有真正的好办法。

LEATH

我们也这样做,效果很好。是的,它需要大量的复制和粘贴,但这正是代码生成的目的。在 Visual Studio 中,您可以为此使用T4模板。你基本上只写一次你的类,然后有一个模板,你说“我想要这个类用于应用程序、支付、帐户……”,Visual Studio 将为每个生成一个源代码文件。这样,您就有了一个单一的来源(T4 模板),如果您在类中发现错误,您可以在其中进行更改,并且它将传播到您的所有标识符,而无需考虑更改所有标识符。

12345678_0001

这有一个很好的副作用。您可以将这些重载用于添加:void Add(Account account);void Add(Payment payment);但是,您不能对 get 进行重载:Account Get(Guid id);Payment Get(Guid id);我一直不喜欢这种不对称。你所要做的:Account GetAccount(Guid id);Payment GetPayment(Guid id);使用上述方法,这是可能的:Account Get(Id<Account> id);Payment Get(Id<Payment> id);达到对称。
打开App,查看更多内容
随时随地看视频慕课网APP