代码之家  ›  专栏  ›  技术社区  ›  Tomas Kubes

强类型Guid作为泛型结构

  •  37
  • Tomas Kubes  · 技术社区  · 6 年前

    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吗?

    3 回复  |  直到 6 年前
        1
  •  45
  •   Eric Lippert    6 年前

    首先,这真是个好主意。旁白:

    花时间解决真正的问题。 一点复制粘贴的代码并不是什么大问题。

    如果您确实希望避免复制粘贴的代码,那么我建议使用以下泛型:

    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<应用程序> Id<付款> Guid .

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

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

    等等

    第三,您可能需要在您的类型中增加一些功能;我想这还没有完成。例如,您可能需要相等,以便检查两个ID是否相同。

    第四,要意识到 default(Id<App>) 仍然会给您一个“空guid”标识符,因此您试图阻止它实际上是行不通的;仍然可以创建一个。这并不是一个好办法。

        2
  •  5
  •   nvoigt    6 年前

    我们也这么做,效果很好。

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

    在VisualStudio中,可以使用 T4

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

        3
  •  0
  •   Michiel de Wolde    4 年前

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

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

    实现了对称性。

        4
  •  -9
  •   Solomon Ucko    6 年前

    您可能可以将子类化与其他编程语言一起使用。