【问题标题】:Is there a way to define C# strongly-typed aliases of existing primitive types like `string` or `int`?有没有办法定义现有原始类型(如“string”或“int”)的 C# 强类型别名?
【发布时间】:2010-12-27 21:02:29
【问题描述】:

也许我是在证明我对 C# 或 .NET 框架的一些常用功能的无知,但我想知道是否有本地支持的方法来创建像 EmailAddress 这样的类型别名,其中别名为 @987654322 @ 但是这样我就可以用我自己的方法来扩展它,比如bool Validate()

我知道 using x = Some.Type; 别名,但它们不是全局的,也不提供类型安全性,即可以将普通的 string 换成当前文件中的 using 别名。我希望我的 EmailAddress 是它自己的类型,独立且不能与它所覆盖的 string 类型互换。

我当前的解决方案是使用 T4 模板生成 public sealed partial EmailAddress : IEquatable<EmailAddress>, IXmlSerializable 类,生成样板隐式字符串转换运算符和其他类似的东西。现在这对我来说很好,并且给了我很大的灵活性,但在我的脑海里,我不得不生成如此大量的样板代码来做一些像创建强类型别名这样简单的事情,这似乎很愚蠢。

也许除了代码生成之外这是不可能的,但我很好奇其他人是否尝试过与他们的设计类似的东西以及你的经历。如果不出意外,也许这可以作为 C# 的假设未来版本中此类别名功能的一个很好的用例。谢谢!

编辑:我想要的真正价值是能够使用表示不同数据类型/格式的原始类型来获得类型安全。例如,EmailAddressSocialSecurityNumberPhoneNumber,它们都使用 string 作为它们的基础类型,但它们本身不是可互换的类型。我认为这会让您的代码更具可读性和自文档化,更不用说更多方法重载可能性的额外好处了。

【问题讨论】:

  • 看来你至少有一个合理的 C# knoledgde 所以我的评论可能看起来很愚蠢,但你想要的是所谓的“类型层次结构”,而编写 String 类的人想阻止你使用它“OO 功能”,所以他们将 String 类密封,这就是为什么你不能做你想做的事。最好的方法是您现在正在使用的方法:创建自己的类型并隐式转换为 String。

标签: c# alias strong-typing


【解决方案1】:

如果您查看 .NET Framework,System.Uri 是与电子邮件地址最接近的示例。在 .NET 中,模式是将某些内容包装在一个类中并以这种方式添加约束。

为简单类型添加额外约束的强类型是一个有趣的语言特性,我相信某些函数式语言具有。我不记得语言的名称了,它可以让您在值中添加诸如英尺之类的尺寸单位,并对您的方程式进行尺寸分析以确保单位匹配。

【讨论】:

  • 这似乎是最好的方法。我不太精通函数式语言,但我知道它们有一些漂亮的类型系统,可以让你比面向对象的系统编写得更好。
  • 我相信 Ada 语言有你描述的特性,或者类似的特性。
  • Go 和 Haskell 也有这样的限制(Go 肯定有,Haskell 我不太了解)。
【解决方案2】:

string 为何被封存的一些背景知识:

来自http://www.code-magazine.com/Article.aspx?quickid=0501091

Rory:嘿,Jay,你介意我问你一个 几个问题?我已经很好奇了 关于一些事情。首先,和 这是在 MSDN 之一提出的 我这周做的事件,为什么是 String 密封?注意:对于 VB.NET 程序员, 密封 = 不可继承。

Jay:因为我们做了很多魔术 String 中的技巧来尝试确保 我们可以优化诸如 比较,使它们尽可能快 我们可能可以。所以,我们在偷 指针和其他东西的位 在那里做标记。只为了 给你一个例子,我不知道 这当我开始,但如果一个字符串 有连字符或撇号 [then] 它的排序方式与 if 它的排序方式不同 里面只有文字,还有算法 如果您有连字符或 如果你正在做一个撇号 全局感知排序很漂亮 复杂,所以我们实际上标记 字符串是否有 其中的行为类型。

Rory:所以,你的意思是 在弦乐世界里,如果你没有 密封字符串会有很多 造成严重破坏的空间,如果 人们试图对其进行子类化。

杰伊:没错。它会改变 对象的整个布局,所以我们 不能耍花招 我们玩那个加速。

这是您可能以前看过的 CodeProject 文章:

http://www.codeproject.com/KB/cs/expandSealed.aspx

是的,隐式运算符是您唯一的解决方案。

【讨论】:

  • 感谢您提供有关 string 被封存的信息,但这并不直接相关。
  • 我知道 :) 这只是我个人试图找出为什么让我讨厌的事情是这样的。
  • 有时,即使派生类型无法访问基础类的任何非公共成员,也可以从 string 之类的东西中准继承类型,这会有所帮助。覆盖其任何成员,也不添加任何字段。如果此类类型可以添加新接口——即使它们没有任何成员,这可能有助于定义诸如深度不可变接口之类的东西(例如,如果可以声明一个行为类似于 int 的类型 DeeplyImmutableInt但是继承了IAmDeeplyImmutable,并且对于其他内置类型也这样做,然后可以...
  • ...使用通用约束来强制执行深度不变性)。即使一个人甚至不能添加空接口,也能够定义类型 X:Z 和 Y:Z 使得从 X 到 Z 存在扩大的身份转换,以及从 Z 到 X 或 Z 到 Y 的缩小身份转换,可能是有帮助。此外,能够定义一个应该支持双向标识转换到/从某个基本类型(例如 P::Q 和 R::S)的类型可能会有所帮助,特别是如果编译器允许在与从 Q 转换为 S 的情况相同。
【解决方案3】:

System.Net.Mail.MailAddress 类是否满足您的需求,或者至少是“帮助”?

编辑:它不是明确的 IEquatable 或 ISerializable,但您可以轻松地将它们添加到您自己的包装器中。

【讨论】:

  • 不,我打算要求通用解决方案,而不是电子邮件地址的具体情况。我也在做一些事情,比如生成int 包装器来创建强类型标识符,用于自记录目的和方法重载定义的灵活性。例如,我在同一个接口上定义了Staff GetStaff(StaffID id)Staff GetStaff(TeacherID id)。这两种方法根据提供的标识符类型对底层数据源进行不同的查询。 Teacher 是 Staff,值为 1 的 TeacherID 与值为 1 的 StaffID 表示的实体不同。
  • 明白了,我把你的问题误解为询问这个具体案例。
【解决方案4】:

看来你至少有一个合理的 C# knoledgde 所以我的回答可能看起来很愚蠢,但你想要的是所谓的“类型层次结构”,而编写 String 类的人想要阻止你使用这个“OO 特性”所以他们使 String 类密封,这就是为什么你不能做你想做的事。最好的方法是您现在正在使用的方法:创建自己的类型并隐式转换为 String。

【讨论】:

    【解决方案5】:

    我认为您想使用扩展方法。它们允许您在不创建新派生类型的情况下扩展类功能。

    【讨论】:

    • 如果我可以创建独特的类型别名并在别名上定义扩展方法而不是基本 string 类型,扩展方法将很有吸引力。我希望编译器进行类型安全检查,以确保不会有人意外尝试将 EmailAddress 复制到 PhoneNumber 或类似的疯狂/意外事件中。
    【解决方案6】:

    我想我不明白你为什么要同时拥有强类型和隐式字符串转换。对我来说,一个排除另一个。

    我试图为 int 解决同样的问题(你在标题中提到了 int,但在问题中没有提到)。我发现声明一个枚举会给你一个类型安全的整数,它需要从/到 int 显式转换。

    更新

    枚举可能不打算用于开放集,但仍然可以以这种方式使用。此示例来自一个编译实验,用于区分数据库中几个表的 ID 列:

        enum ProcID { Unassigned = 0 }
        enum TenderID { Unassigned = 0 }
    
        void Test()
        {
            ProcID p = 0;
            TenderID t = 0; <-- 0 is assignable to every enum
            p = (ProcID)3;  <-- need to explicitly convert
    
            if (p == t)  <-- operator == cannot be applied
                t = -1;  <-- cannot implicitly convert
    
            DoProc(p);
            DoProc(t);   <-- no overloaded method found
            DoTender(t);
        }
    
        void DoProc(ProcID p)
        {
        }
    
        void DoTender(TenderID t)
        {
        }
    

    【讨论】:

    • 在这种情况下,我的意思是“强类型”,因为除了string 之外还有自己独特的类型,并且不能直接互换。为超载一词道歉:)。枚举对int 的情况没有多大帮助,因为它们定义了自己的范围,并且不打算用于一组开放的情况(相对于一组封闭的枚举值)。
    【解决方案7】:

    我开设这个课程是为了满足相同的需求。这是一个用于“int”类型(我也有一个用于“string”):

    public class NamedInt : IComparable<int>, IEquatable<int>
    {
        internal int Value { get; }
    
        protected NamedInt() { }
        protected NamedInt(int val) { Value = val; }
        protected NamedInt(string val) { Value = Convert.ToInt32(val); }
    
        public static implicit operator int (NamedInt val) { return val.Value; }
    
        public static bool operator ==(NamedInt a, int b) { return a?.Value == b; }
        public static bool operator ==(NamedInt a, NamedInt b) { return a?.Value == b?.Value; }
        public static bool operator !=(NamedInt a, int b) { return !(a==b); }
        public static bool operator !=(NamedInt a, NamedInt b) { return !(a==b); }
    
        public bool Equals(int other) { return Equals(new NamedInt(other)); }
        public override bool Equals(object other) {
            if ((other.GetType() != GetType() && other.GetType() != typeof(string))) return false;
            return Equals(new NamedInt(other.ToString()));
        }
        private bool Equals(NamedInt other) {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(Value, other.Value);
        }
    
        public int CompareTo(int other) { return Value - other; }
        public int CompareTo(NamedInt other) { return Value - other.Value; }
    
        public override int GetHashCode() { return Value.GetHashCode(); }
    
        public override string ToString() { return Value.ToString(); }
    }
    

    并在您的情况下使用它:

    public class MyStronglyTypedInt: NamedInt {
        public MyStronglyTypedInt(int value) : base(value) {
            // Your validation can go here
        }
        public static implicit operator MyStronglyTypedInt(int value) { 
            return new MyStronglyTypedInt(value);
        }
    
        public bool Validate() {
            // Your validation can go here
        }
    }
    

    如果您需要能够序列化它(Newtonsoft.Json),请告诉我,我会添加代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-21
      • 1970-01-01
      • 2011-02-13
      • 1970-01-01
      • 1970-01-01
      • 2016-08-10
      相关资源
      最近更新 更多