繁体   English   中英

有没有办法定义现有原始类型(如 `string` 或 `int`)的 C# 强类型别名?

[英]Is there a way to define C# strongly-typed aliases of existing primitive types like `string` or `int`?

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

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

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

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

编辑:我想要的真正价值是能够使用代表不同数据类型/格式的原始类型获得类型安全。 例如,一个EmailAddress和一个SocialSecurityNumber和一个PhoneNumber ,所有这些都使用string作为它们的基础类型,但它们本身不是可互换的类型。 我认为这会让你的代码更具可读性和自我记录性,更不用说更多方法重载可能性的额外好处,这些可能性不那么模糊。

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

添加为简单类型添加额外约束的强类型是一个有趣的语言特性,我相信某些函数式语言具有。 我不记得允许您将尺寸单位(例如英尺)添加到您的值并对您的方程进行尺寸分析以确保单位匹配的语言名称。

关于为什么string被密封的一些背景:

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

Rory:嘿,Jay,你介意我问你几个问题吗? 我已经对一些事情很好奇了。 首先,这是在我本周参加的一次 MSDN 活动中提出的,为什么 String 是密封的? 注意:对于 VB.NET 程序员,Sealed = NotInheritable。

Jay:因为我们在 String 中做了很多魔术来尝试确保我们可以优化比较之类的东西,使它们尽可能快。 所以,我们从指针和其他东西中窃取位来标记东西。 举个例子,我刚开始的时候不知道这一点,但是如果一个字符串中有一个连字符或一个撇号 [那么] 它的排序与它只有文本的排序不同,以及排序算法如果你有一个连字符或一个撇号,如果你在做全局感知排序是非常复杂的,所以我们实际上标记了字符串中是否有这种类型的行为。

Rory:所以,你的意思是,在字符串世界中,如果你不密封 String,那么如果人们试图将其子类化,就会有很大的空间来造成大量破坏。

杰:没错。 它会改变对象的整个布局,所以我们将无法玩我们玩的加快速度的技巧。

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

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

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

System.Net.Mail.MailAddress类是否符合您的需要,或者至少是“帮助”?

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

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

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

我试图为 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)
    {
    }

我认为您想使用扩展方法。 它们允许您扩展类功能而无需创建新的派生类型。

我制作了这个课程来满足相同的需求。 这是一种用于“int”类型(我也有一种用于“字符串”):

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),请告诉我,我会添加代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM