什么是.NET的强类型字符串(Strongly typed string)?

.NET中,强类型字符串(Strongly typed string)并不是一个官方的概念,是指使用特定的结构来表示某种类型字符串数据的编码实践。类似于枚举,可以提供编译时检查类型,减少运行时错误,以及更好的可读性和维护性。相比于枚举,具有更好的扩展性以及更强的约束性。

枚举

枚举提供了一种便捷的方法来使用相关常数集并将常数值与名称相关联,具有类型安全、可读性高以及编译时检查等优点。但是枚举类型不能定义任何方法、属性或事件,只能通过扩展方法功能模拟向枚举类型添加方法。

尽管枚举提供了编译时检查,但对输入值的约束是有限的。例如,下面这个枚举有四个值,默认情况下是int类型。取值范围为0 ~ 3。

c# 复制代码
public enum Roles {
    Author,
    Editor,
    Administrator,
    SalesRepresentative
}

然后,有一个方法接受这个枚举类型的参数:

c# 复制代码
public string DoSomething(Roles role) {
    return role.ToString();
}

许多开发人员可能不会检查传入值是否为实际有效的枚举值。任何int类型都可以转换,可能出现下边这种代码:

c# 复制代码
var result = myObject.DoSomething((Roles)10);

输出的结果是 "10",如果后续代码中有基于这个枚举的分支语句或者条件判断,将产生错误的结果。对于这种情况,强类型字符串是一个不错的选择。

强类型字符串(Strongly typed string)

强类型字符串要声明成带有字符串构造函数的不可变值类型(struct),即要在该类型上用 readonly 修饰符,并为其实现 IEquatable<T> 接口。要覆写强类型字符串的 ToString() 方法,以返回隐式的字符串值。并将已知的强类型字符串通过静态只读属性声明到该类型上。

为了让强类型字符串在通用代码的语言结构上看起来更像字符串或者枚举,需要为强类型字符串覆写相等运算符。

以下就是 .NET 源码中加密哈希算法的名称强类型字符串HashAlgorithmName的代码

c# 复制代码
using System.Diagnostics.CodeAnalysis;

namespace System.Security.Cryptography
{
    
    public readonly struct HashAlgorithmName : IEquatable<HashAlgorithmName>
    {
        public static HashAlgorithmName MD5 { get { return new HashAlgorithmName("MD5"); } }

        public static HashAlgorithmName SHA1 { get { return new HashAlgorithmName("SHA1"); } }

        public static HashAlgorithmName SHA256 { get { return new HashAlgorithmName("SHA256"); } }

        public static HashAlgorithmName SHA384 { get { return new HashAlgorithmName("SHA384"); } }

        public static HashAlgorithmName SHA512 { get { return new HashAlgorithmName("SHA512"); } }

        public static HashAlgorithmName SHA3_256 => new HashAlgorithmName("SHA3-256");

        public static HashAlgorithmName SHA3_384 => new HashAlgorithmName("SHA3-384");

        public static HashAlgorithmName SHA3_512 => new HashAlgorithmName("SHA3-512");

        private readonly string? _name;

        public HashAlgorithmName(string? name)
        {
            // Note: No validation because we have to deal with default(HashAlgorithmName) regardless.
            _name = name;
        }

        public string? Name
        {
            get { return _name; }
        }

        public override string ToString()
        {
            return _name ?? string.Empty;
        }

        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            return obj is HashAlgorithmName && Equals((HashAlgorithmName)obj);
        }

        public bool Equals(HashAlgorithmName other)
        {
            // NOTE: intentionally ordinal and case sensitive, matches CNG.
            return _name == other._name;
        }

        public override int GetHashCode()
        {
            return _name == null ? 0 : _name.GetHashCode();
        }

        public static bool operator ==(HashAlgorithmName left, HashAlgorithmName right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(HashAlgorithmName left, HashAlgorithmName right)
        {
            return !(left == right);
        }

        //其他扩展功能
        public static bool TryFromOid(string oidValue, out HashAlgorithmName value)
        {
            ArgumentNullException.ThrowIfNull(oidValue);

            switch (oidValue)
            {
                case Oids.Md5:
                    value = MD5;
                    return true;
                case Oids.Sha1:
                    value = SHA1;
                    return true;
                case Oids.Sha256:
                    value = SHA256;
                    return true;
                case Oids.Sha384:
                    value = SHA384;
                    return true;
                case Oids.Sha512:
                    value = SHA512;
                    return true;
                case Oids.Sha3_256:
                    value = SHA3_256;
                    return true;
                case Oids.Sha3_384:
                    value = SHA3_384;
                    return true;
                case Oids.Sha3_512:
                    value = SHA3_512;
                    return true;
                default:
                    value = default;
                    return false;
            }
        }

        public static HashAlgorithmName FromOid(string oidValue)
        {
            if (TryFromOid(oidValue, out HashAlgorithmName value))
            {
                return value;
            }

            throw new CryptographicException(SR.Format(SR.Cryptography_InvalidHashAlgorithmOid, oidValue));
        }
    }
}

这段代码更好地约束了加密哈希算法名称的输入,同时还扩展了其他功能。但比枚举繁琐不少。

根据《框架设计指南》建议:当基类支持一组固定的输入参数,但是派生类需要支持更多的参数时,建议使用强类型字符串;当仅由密封类型使用时,只需要使用预定义的值,枚举将是更好的选择。

此外,枚举通常定义的是封闭的选项集,对于操作系统版本这种开放集合,也建议使用强类型字符串。控件库 HandyControl 中的 SystemVersionInfo正是这样的例子。

参考

Enum Alternatives in C# | Blog
使用枚举类(而不是枚举类型) - .NET | Microsoft Learn

相关推荐
go_bai33 分钟前
动态内存管理的知识点笔记总结
c语言·开发语言·笔记·c#·学习方法
zaim133 分钟前
计算机的错误计算(一百六十八)
java·c++·python·matlab·rust·c#·大数
Lazy龙1 小时前
C#中判断两个 List<T> 的内容是否相等
c#·游戏程序
三天不学习1 小时前
【实体配置】.NET开源 ORM 框架 SqlSugar 系列
数据库·c#·.net·orm
程饱饱吃得好饱2 小时前
“小bug”示例
开发语言·c#·bug
大王小生3 小时前
C# 7.1 .Net Framwork4.7 VS2017环境下,方法的引用与调用
c#·.net
坐井观老天3 小时前
在C#中使用OpenCV的.net包装器EmguCV
人工智能·opencv·c#·.net
Eiceblue4 小时前
.NET平台用C#添加动作到PDF文档
开发语言·vscode·pdf·c#·.net
军训猫猫头6 小时前
猜一个0到10之间的数字 C#
开发语言·c#