强类型Guid作为通用结构

问题描述 投票:39回答:3

我已经在代码中制作了两次相同的bug,如下所示:

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();
    }
}

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

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

你有一些想法如何使用struct而不重复代码?

c# generics struct guid
3个回答
45
投票

首先,这是一个非常好的主意。简要说一下:

我希望C#能够更容易地围绕整数,字符串,ID等创建廉价的类型包装器。作为程序员,我们非常“快乐”和“整体快乐”;很多东西都表示为字符串和整数,可以在类型系统中跟踪更多信息;我们不希望将客户名称分配给客户地址。前段时间我写了一系列关于在OCaml中编写虚拟机的博客文章(从未完成!),我所做的最好的事情之一就是将虚拟机中的每个整数包装成一个指示其用途的类型。这防止了这么多错误! OCaml使创建小包装类型变得非常容易; C#没有。

其次,我不会过分担心重复代码。它主要是一个简单的复制粘贴,你不太可能编辑代码或犯错误。花时间解决实际问题。一点点复制粘贴的代码并不是什么大问题。

如果你确实想避免使用复制粘贴代码,那么我建议使用这样的泛型:

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>而不是AppIdPaymentId,但你仍然不能将Id<App>分配给Id<Payment>Guid

此外,如果你喜欢使用AppIdPaymentId然后在你的文件的顶部你可以说

using AppId = MyNamespace.Whatever.Id<MyNamespace.Whatever.App>

等等。

第三,您可能需要更多类型的功能;我认为这还没有完成。例如,您可能需要相等,以便您可以检查两个ID是否相同。

第四,要注意default(Id<App>)仍然给你一个“空guid”标识符,所以你试图阻止它实际上不起作用;它仍然可以创建一个。这并不是一个好方法。


5
投票

我们这样做,效果很好。

是的,这是很多复制和粘贴,但这正是代码生成的目的。

在Visual Studio中,您可以使用T4模板。你基本上写了一次你的课程,然后有一个模板,你说“我希望这个课程适用于App,Payment,Account,...”,Visual Studio将为你生成一个源代码文件。

这样你就有了一个单一的源(T4模板),如果你在类中发现了一个bug,你可以在其中进行更改,它会传播到你的所有标识符,而不必考虑更改所有标识符。


-9
投票

您可以使用不同编程语言的子类。

© www.soinside.com 2019 - 2024. All rights reserved.