C# 强类型Guid作为泛型结构

C# 强类型Guid作为泛型结构,c#,generics,struct,guid,C#,Generics,Struct,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 Fo

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

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);
好的,我想防止这些bug,所以我创建了强类型的GUI:

[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,因为空检查、更少的内存占用、没有垃圾收集器开销等等


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

首先,这是一个非常好的主意。旁白:

我希望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是否相同


第四,请注意,
default(Id)
仍然会为您提供一个“空guid”标识符,因此您试图防止这种情况的尝试实际上是行不通的;仍然可以创建一个。这并不是一个好办法。

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

我们也这样做,效果很好

是的,这需要大量的复制和粘贴,但这正是代码生成的目的

在VisualStudio中,您可以为此使用模板。基本上,您只需编写一次类,然后创建一个模板,在模板中您可以说“我希望这个类用于应用程序、付款、帐户等”,VisualStudio将为每个类生成一个源代码文件


这样,您就有了一个单一的源(T4模板),如果您在类中发现错误,您可以在其中进行更改,它将传播到所有标识符,而无需考虑更改所有标识符。

这有一个很好的副作用。您可以为添加设置以下重载:

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);
账户获取(Id);
付款获取(Id);

达到对称性。

有趣的需求。我有个问题。撇开代码重复不谈,当您想要创建其中一个结构时,您必须用一些东西初始化它们。如何确保使用正确的东西初始化它们?如..var appIdStruct=new AppId(paymentId)不应编译。我不能保证它。我不能检查所有的东西。即使这样,bug的空间也会减少一些。从这个角度来看,我认为bug的空间是一样的,你只是把源代码移到了一个不同的地方。而且,你正在使事情复杂化,这实际上会长期增加空间。你打算分配很多吗?它们在内存中会存在很长时间吗?不多,但空检查也是构造结构的一个很好的理由。您是否考虑过创建一个类似FooParams的Params类并将其传递给函数,然后如果您真的使用了有意义的变量名,则错误分配会很突出,并且很容易被发现,如FooParams.PaymentId=accountId@伯恩斯巴:如果你那样做会受伤,那就不要这样做!:-)您认为为该类型定义到Guid的转换,或者使用公开Guid的公共只读属性有用吗?这对于连接需要“原始”Guid的持久性框架非常有用。@dasblinkenlight:这似乎是一个合理的选择。可能与Guid之间的显式转换会很有用。一个有趣的小信息:在函数式编程的上下文中,以
T
的方式使用的类型称为A:它用作类型参数,但是从来没有一个
T
@SolomonUcko类型的值:我认为在大多数情况下,这是一个更好的设计。也就是说,要有一个特定的类
AppId
extend
Id
。或者将这两种方法结合起来,使用
AppId
extend
Id
。所有结构类型都需要继承自
ValueType
,而不需要继承其他内容。哦。可以改用类吗?@SolomonUcko类无法从结构继承。
Guid
可以是类吗?如果Guid是类,它将消耗更多内存,它将由垃圾收集器管理,并且可以为null(null检查问题)。
Account Get(Id<Account> id);
Payment Get(Id<Payment> id);