- .NET 指南
- 开始操作
- .NET 教程
- .NET 体系结构组件
- .NET Standard
- 什么是.NET 标准中的新增功能
- 目标框架
- .NET 术语表
- 使用 mdoc 生成文档
- .NET 的体系结构指南
- 使用 ASP.NET Core 和 Azure 构建新式 Web 应用程序
- 现代 Web 应用程序的特征
- 传统 Web 应用程序和单页面应用程序 (Spa) 之间进行选择
- 常见的客户端 Web 技术
- 开发 ASP.NET Core MVC 应用程序
- 使用 ASP.NET Core 应用中的数据
- 测试 ASP.NET Core MVC 应用程序
- Azure 的开发过程
- Azure 托管的 ASP.NET 核心 Web 应用的建议
- 使用 Azure 云和 Windows 容器更新现有 .NET 应用程序 (v1.0)
- 提升和移动现有应用 Azure IaaS
- 将关系数据库迁移到 azure
- 直接迁移现有应用 DevOps
- 到提起并移动现有的.NET 应用到云 DevOps 通用应用程序的原因
- 云 devops 通用应用程序中的 Microsoft 技术
- 云优化应用程序如何呢?
- 如何将现有的.NET 应用程序部署到 Azure App Service
- 将现有的.NET 应用程序部署为 Windows 容器
- 何时不将部署到 Windows 容器
- 何时在你的本地部署 Windows 容器 IaaS VM 基础结构
- 何时将 Windows 容器部署到 Azure Vm (IaaS 云)
- 何时到 Service Fabric 中部署 Windows 容器
- 何时将 Windows 容器部署到 Azure 容器服务 (即,Kubernetes)
- 构建可复原的服务供云: 采用在云中的暂时性故障
- 更新你的应用的监视和遥测
- 更新使用 CI/CD 管道和 DevOps 工具在云中的应用程序的生命周期
- 将迁移到混合云方案
- 演练和技术获取启动的概述
- 技术演练列表
- 技术演练列表
- 结论
- 容器和 Docker 简介
- 什么是 Docker?
- Docker 术语
- Docker 容器、 图像和注册表
- Docker 应用程序生命周期的介绍
- DevOps 协作基础的容器
- 有关适用于容器化应用的 Microsoft 平台和工具的简介
- 使用 Docker 和 Microsoft Azure 设计和开发容器化应用
- Docker 应用程序设计
- 常见容器设计原则
- 整体应用程序
- 状态和 Docker 应用程序中的数据
- SOA 应用程序
- 协调微服务和以高可伸缩性和可用性的 multicontainer 应用程序
- Docker 应用的开发环境
- 对于 Docker 应用程序的内部循环开发工作流
- 使用 Visual Studio Tools for Docker (在 Windows 上的 Visual Studio)
- 使用在 DockerFile 中的 Windows PowerShell 命令来设置 Windows 容器 (Docker 标准基于)
- 使用 Microsoft 工具的 Docker 应用程序 DevOps 工作流
- Docker 应用程序的外部循环 DevOps 工作流中的步骤
- 运行、管理和监视 Docker 生产环境
- 在生产环境中运行由和基于微服务的应用程序
- 管理生产 Docker 环境
- 监视容器化应用程序服务
- 关键结论
- .NET 微服务。 适用于容器化 .NET 应用程序的体系结构
- .NET 微服务。 适用于容器化 .NET 应用程序的体系结构
- 容器和 Docker 简介
- 什么是 Docker?
- Docker 术语
- Docker 容器、 图像和注册表
- 为 Docker 容器选择 .NET Core 还是 .NET Framework
- 一般性指导原则
- 何时选择 Docker 容器中的.NET 核心
- 何时选择 Docker 容器中的.NET Framework
- 决策表:.NET 框架,用于 Docker
- .NET 容器定向到何种操作系统
- 正式.NET Docker 映像
- 构建基于容器和微服务的应用程序
- Containerizing 整体应用程序
- 状态和 Docker 应用程序中的数据
- 面向服务的体系结构
- 微服务体系结构
- 每个微服务构成的数据自主性
- 与物理体系结构的逻辑体系结构
- 分布式的数据管理挑战和解决方案
- 标识每个微服务的域模型边界
- 直接与 API 网关模式的微服务构成客户端通信
- 在微服务体系结构的通信
- 基于消息的异步通信
- 创建、 不断发展,和版本控制 microservice Api 和协定
- 微服务可寻址性和服务注册表
- 创建复合 UI 基于微服务,包括 visual UI 形状和布局由多个微服务生成
- 复原和微服务中的高可用性
- 协调微服务和多容器应用程序的高可伸缩性和可用性
- 使用 Azure Service Fabric
- 基于 Docker 的应用程序的开发流程
- Docker 应用的开发工作流
- 在 Linux 或 Windows Nano Server 主机上部署基于单容器的 .NET Core Web 应用
- 将旧版整体式 .NET Framework 应用程序迁移到 Windows 容器
- 设计和开发基于微服务的多容器 .NET 应用程序
- 设计面向微服务构成的应用程序
- 创建简单的数据驱动 CRUD 微服务
- 定义与 docker-compose.yml 多容器应用程序
- 使用运行的容器的数据库服务器
- 实现基于事件的微服务 (集成事件) 之间的通信
- 实现使用 RabbitMQ 开发或测试环境事件总线
- 订阅事件
- 测试 ASP.NET 核心服务和 web 应用
- 使用 DDD 和 CQRS 模式降低微服务中的业务复杂性
- 应用中的微服务构成的简化的 CQRS 和 DDD 模式
- EShopOnContainers DDD 微服务中的应用 CQRS 和 CQS 方法
- 在 CQRS 微服务中实现读取/查询
- 设计 DDD 面向微服务
- 设计 microservice 域模型
- 实现 microservice 域模型与.NET 核心
- Seedwork (可重用基类,这些类和接口,您的域模型)
- 实现值对象
- 使用枚举类,而不枚举类型
- 设计域模型层中的验证
- 客户端验证 (验证在表示层)
- 域事件: 设计和实现
- 设计基础结构持久性层
- 实现与实体框架核心基础结构持久性层
- 作为持久性基础结构使用 NoSQL 数据库
- 设计微服务应用程序层和 Web API
- 实现微服务应用程序层使用 Web API
- 实现具有恢复能力的应用程序
- 处理部分失败
- 用于处理部分失败的策略
- 实现重试使用指数退让
- 实现弹性 Entity Framework 核心 SQL 连接
- 实现自定义 HTTP 调用的重试使用指数退让
- 使用 Polly 和实施 HTTP 调用重试使用指数退让
- 实现断路器模式
- 运行状况监视
- 保护 .NET 微服务和 Web 应用程序
- 有关在.NET 微服务和 web 应用程序的授权
- 在开发过程中安全地存储应用程序机密
- 使用 Azure 密钥保管库在生产时保护机密
- 记住的要点
- 为服务器应用选择 .NET Core 或 .NET Framework
- 什么是 托管代码 ?
- Automatic Memory Management
- 公共语言运行时 (CLR)
- 语言独立性和与语言无关的组件
- 语言独立性和与语言无关的组件
- 框架库
- .NET Framework 类库概述
- 在 .NET 中使用基类型
- 常规类型系统
- .NET Framework 中的类型转换
- .NET 中的类型转换表
- .NET 中的格式化类型
- 标准数字格式字符串
- 自定义数字格式字符串
- 标准日期和时间格式字符串
- 自定义日期和时间格式字符串
- 标准 TimeSpan 格式字符串
- 自定义的 TimeSpan 格式字符串
- 枚举格式字符串
- 复合格式设置
- 执行格式设置操作
- 如何:用前导零填充数字
- 如何:从特定日期中提取星期几
- 如何:定义和使用自定义数值格式提供程序
- 如何:往返日期和时间值
- 如何:将用户在 Web 控件中输入的数值转换为数字
- 如何:向 Web 用户显示本地化的日期和时间信息
- 如何:显示日期和时间值中的毫秒
- 如何:用非公历日历显示日期
- 操作在.NET 中的字符串
- 在.NET 中使用字符串的最佳做法
- .NET 中的基本字符串操作
- 在.NET 中创建新的字符串
- 剪裁和移除从.NET 中的字符串的字符
- .NET 中的空白字符串
- 比较.NET 中的字符串
- 更改.NET 中的大小写
- 在.NET 中使用 StringBuilder 类
- 如何: 在.NET 中执行基本字符串操作
- .NET 正则表达式
- 正则表达式语言 - 快速参考
- 正则表达式中的字符转义
- 正则表达式中的字符类
- 正则表达式中的定位点
- 正则表达式中的分组构造
- 正则表达式中的限定符
- 正则表达式中的反向引用构造
- 正则表达式中的备用构造
- 正则表达式中的替代
- 正则表达式选项
- 正则表达式中的其他构造
- .NET 中的正则表达式的最佳实践
- 正则表达式对象模型
- 正则表达式行为的详细信息
- 正则表达式中的回溯
- 正则表达式中的编译和重复使用
- 正则表达式中的线程安全
- 正则表达式示例
- 正则表达式示例:扫描 HREF
- 正则表达式示例:更改日期格式
- 如何:从 URL 中提取协议和端口号
- 如何:从字符串中剥离无效字符
- 如何:确认字符串是有效的电子邮件格式
- 在.NET 中编码的字符
- 在.NET 中分析字符串
- NET 中分析数值字符串
- 分析日期和时间字符串.NET 中
- .NET 中分析其他字符串
- .NET 类库
- .NET 可移植性分析器
- 在 .NET 中处理和引发异常
- .NET 程序集文件格式
- 内存管理和.NET 中的垃圾回收
- 垃圾回收
- 垃圾回收的基础
- 垃圾回收和性能
- 被动回收
- 滞后时间模式
- 针对共享 Web 承载优化
- 垃圾回收通知
- 应用程序域资源监控
- 弱引用
- 泛型类型(泛型)概述
- 委托和 lambda
- LINQ(语言集成查询)
- 常规类型系统和公共语言规范
- 异步概述
- 深入了解异步
- 异步编程模式
- 基于任务的异步模式 (TAP)
- 实现基于任务的异步模式
- 使用基于任务的异步模式
- 与其他异步模式和类型互操作
- 基于事件的异步模式 (EAP)
- 使用基于事件的异步模式进行多线程编程
- 基于事件的异步模式概述
- 实现基于事件的异步模式
- 实现基于事件的异步模式的最佳做法
- 确定何时实现基于事件的异步模式
- 演练:实现支持基于事件的异步模式的组件
- 演练:实现支持基于事件的异步模式的组件
- 如何:实现基于事件的异步模式的客户端
- 如何:使用支持基于事件的异步模式的组件
- 异步编程模型 (APM)
- 使用 IAsyncResult 调用异步方法
- 使用 AsyncWaitHandle 阻止应用程序的执行
- 通过结束异步操作来阻止应用程序执行
- 轮询异步操作的状态
- 使用 AsyncCallback 委托结束异步操作
- 使用 AsyncCallback 委托和状态对象
- 使用委托进行异步编程
- 使用异步方式调用同步方法
- 本机互操作性
- 集合和数据结构
- .NET Framework 中的数字
- 日期、时间和时区
- 时区概述
- 在 DateTime、DateTimeOffset、TimeSpan 和 TimeZoneInfo 之间进行选择
- 查找本地系统上定义的时区
- 如何: 枚举计算机上存在的时区
- 如何: 访问预定义的 UTC 和当地时间区域对象
- 如何: 实例化 TimeZoneInfo 对象
- 实例化 DateTimeOffset 对象
- 如何: 创建不带调整规则的时区
- 如何: 创建带有调整规则的时区
- 保存和还原时区
- 如何: 将时区保存到嵌入的资源
- 如何: 从嵌入的资源还原时区
- 使用日历
- 使用日期和时间执行算术运算
- 如何: 在日期和时间运算中使用时区
- 在 DateTime 与 DateTimeOffset 之间进行转换
- 在各时区之间转换时间
- 如何: 解决不明确的时间
- 如何: 让用户解决不明确的时间
- 处理和引发事件
- 如何:引发和使用事件
- 如何:使用事件属性处理多个事件
- 如何:在 Web 窗体应用程序中使用事件
- 观察程序设计模式
- 观察程序设计模式最佳做法
- 如何:实现提供程序
- 如何:实现观察程序
- 托管执行过程
- 元数据和自描述组件
- 在 .NET Framework 中构建控制台应用程序
- .NET Framework 中的并行处理和并发
- .NET Framework 应用程序要点
- 文件和流 I/O
- 对 .NET Framework 应用程序进行全球化和本地化
- 全球化
- 本地化评审
- 本地化
- 不区分区域性的字符串操作
- 执行不区分区域性的字符串操作
- 执行不区分区域性的字符串比较
- 执行不区分区域性的大小写更改
- 在集合中执行不区分区域性的字符串操作
- 在数组中执行不区分区域性的字符串操作
- 开发全球通用应用程序的最佳做法
- 利用特性扩展元数据
- 应用特性
- 编写自定义特性
- 检索存储在特性中的信息
- 框架设计准则
- 命名准则
- 大小写约定
- 通用命名约定
- 程序集和 DLL 的名称
- 命名空间的名称
- 类、结构和接口的名称
- 类型成员的名称
- 命名参数
- 命名资源
- 类型设计准则
- 在类和结构之间选择
- 抽象类设计
- 静态类设计
- 接口设计
- 结构设计
- 枚举设计
- 嵌套类型
- 成员设计准则
- 成员重载
- 属性设计
- 构造函数设计
- 事件设计
- 字段设计
- 扩展方法
- 运算符重载
- 参数设计
- 扩展性设计
- 未密封类
- 受保护的成员
- 事件和回调
- 虚成员
- 抽象(抽象类型和接口)
- 用于实现抽象的基类
- 密封
- 异常设计准则
- 异常引发
- 使用标准异常类型
- 异常和性能
- 使用准则
- 数组
- 特性
- 集合准则
- 序列化
- System.Xml 使用情况
- 相等运算符
- 常见设计模式
- 依赖项属性
- 释放模式
- XML 文档和数据
- XML 处理选项
- 内存中 XML 数据处理
- 使用 DOM 模型处理 XML 数据
- XML 文档对象模型 (DOM)
- XML 节点类型
- XML 文档对象模型 (DOM) 层次结构
- 将对象层次结构映射到 XML 数据
- 创建 XML 文档
- 将 XML 文档读入 DOM
- 嵌入到文档中的样式表指令
- 从读取器中加载数据
- 加载 DOM 时的空白和有效空白处理
- 访问 DOM 中的属性
- 将实体声明和实体引用读入 DOM
- 保留实体引用
- 扩展但不保留实体引用
- 将节点插入 XML 文档中
- 在 DOM 中创建新节点
- 为 DOM 中的元素创建新属性
- 创建新节点时的 XML 元素和属性名验证
- 创建新实体引用
- 命名空间对包含元素和属性的新节点的实体引用扩展的影响
- 复制现有节点
- 将现有节点从一个文档复制到另一个文档
- 复制文档片段
- 移除 XML 文档中的节点、内容和值
- 从 DOM 中移除节点
- 移除 DOM 中元素节点的属性
- 移除 DOM 中的节点内容
- 修改 XML 文档中的节点、内容和值
- 在 DOM 中验证 XML 文档
- 保存和写出文档
- 使用 XPath 导航选择节点
- 解析外部资源
- 使用 XmlNameTable 的对象比较
- NamedNodeMap 和 NodeList 中的节点集合
- 按名称或索引检索未排序节点
- 按索引检索已排序节点
- NodeList 和 NamedNodeMap 的动态更新
- DOM 中的命名空间支持
- DOM 中的命名空间和 DTD
- 更改 XML 文档中的命名空间声明
- 更改命名空间前缀属性
- 使用 mlNodeChangedEventArgs 的 XML 文档中的事件处理
- 扩展 DOM
- 使用 XPath 数据模型处理 XML 数据
- 使用 XPathDocument 和 XmlDocument 读取 XML 数据
- 使用 XPathNavigator 选择、计算和匹配 XML 数据
- 使用 XPathNavigator 选择 XML 数据
- 使用 XPathNavigator 计算 XPath 表达式
- 使用 XPathNavigator 匹配节点
- XPath 查询识别的节点类型
- XPath 查询和命名空间
- 已编译的 XPath 表达式
- XPath 命名空间浏览
- 使用 XPathNavigator 访问 XML 数据
- 使用 XPathNavigator 的节点集定位
- 使用 XPathNavigator 的属性和命名空间节点定位
- 使用 XPathNavigator 提取 XML 数据
- 使用 XPathNavigator 访问强类型 XML 数据
- 用户定义的函数和变量
- 使用 XPathNavigator 编辑 XML 数据
- 使用 XPathNavigator 插入 XML 数据
- 使用 XPathNavigator 修改 XML 数据
- 使用 XPathNavigator 移除 XML 数据
- 使用 XPathNavigator 验证架构
- 使用 LINQ to XML 处理 XML 数据
- XSLT 转换
- 使用 XslCompiledTransform 类
- XslCompiledTransform 类的输入
- XslCompiledTransform 类的输出选项
- 在 XSLT 处理期间解析外部资源
- 扩展 XSLT 样式表
- XSLT 扩展对象
- XSLT 参数
- 使用 msxsl:script 的脚本块
- 可恢复的 XSLT 错误
- 如何:转换节点片断
- 从 XslTransform 类迁移
- 如何:迁移 XslTransform 代码
- XSLT 安全注意事项
- XSLT 编译器 (xsltc.exe)
- 如何:通过使用程序集执行 XSLT 转换
- XslTransform 类的 XSLT 转换
- XslTransform 类中任意行为的实现
- XslTransform 类实现 XSLT 处理器
- XslTransform 的输出
- 不同存储区的 XSLT 转换
- 解析外部 XSLT 样式表和文档
- 样式表参数和扩展对象的 XsltArgumentList
- XSLT 样式表脚本使用<msxsl: script>
- 对 msxsl:node-set() 函数的支持
- 转换中的节点集
- 转换中的结果树片断
- 转换中的 XPathNavigator
- 转换中的 XPathNodeIterator
- XslTransform 的 XPathDocument 输入
- XslTransform 的 XmlDataDocument 输入
- XslTransform 的 XmlDocument 输入
- 使用 XML 架构
- XML 架构对象模型 (SOM)
- XML 架构对象模型概述
- 读写 XML 架构
- 生成 XML 架构
- 遍历 XML 架构
- 编辑 XML 架构
- 包含或导入 XML 架构
- 用于编译架构的 XmlSchemaSet
- 后架构编译信息集
- 使用 XmlSchemaSet 进行 XML 架构 (XSD) 验证
- XmlSchemaCollection 架构编译
- 使用 XmlSchemaCollection 进行 XDR 验证
- 使用 XmlSchemaCollection 进行 XML 架构 (XSD) 验证
- XmlSchemaValidator 基于推送的验证
- 推断 XML 架构
- 从 XML 文档推断架构
- 推断架构节点类型和结构的规则
- 推断简单类型的规则
- 关系数据和 ADO.NET 的 XML 集成
- 管理 XML 文档中的命名空间
- System.Xml 类中的类型支持
- 将 XML 数据类型映射到 CLR 类型
- XML 类型支持实现说明
- XML 数据类型的转换
- 将字符串转换为 .NET Framework 数据类型
- 将 .NET Framework 类型转换为字符串
- 托管线程处理
- 托管线程处理基本知识
- 线程与线程处理
- 托管线程中的异常
- 为多线程处理同步数据
- 托管线程状态
- 前台和后台线程
- Windows 中的托管和非托管线程处理
- Thread.Suspend、垃圾回收和安全点
- 线程本地存储区:线程相关的静态字段和数据槽
- 托管线程中的取消
- 如何:通过轮询侦听取消请求
- 如何:注册取消请求的回调
- 如何:侦听具有等待句柄的取消请求
- 如何:侦听多个取消请求
- 使用线程和线程处理
- 启动时创建线程并传递数据
- 暂停和继续线程
- 销毁线程
- 调度线程
- 以协作方式取消线程
- 托管线程处理的最佳做法
- 线程处理对象和功能
- 托管线程池
- 计时器
- EventWaitHandle、AutoResetEvent、CountdownEvent、ManualResetEvent
- EventWaitHandle
- AutoResetEvent
- ManualResetEvent 和 ManualResetEventSlim
- CountdownEvent
- Mutexes
- 互锁操作
- 读取器/编写器锁
- Semaphore 和 SemaphoreSlim
- 同步基元概述
- 屏障 (.NET Framework)
- 如何:使用屏障来使并发操作保持同步
- SpinLock
- 如何:使用 SpinLock 进行低级别同步
- 如何:在 SpinLock 中启用线程跟踪模式
- SpinWait
- 如何:使用 SpinWait 实现两阶段等待操作
- .NET 中的并行编程
- 任务并行库 (TPL)
- 数据并行(任务并行库)
- 如何:编写简单的 Parallel.For 循环
- 如何:编写简单的 Parallel.ForEach 循环
- 如何:编写具有线程局部变量的 Parallel.For 循环
- 如何:编写具有线程局部变量的 Parallel.ForEach 循环
- 如何:取消 Parallel.For 或 ForEach Loop
- 如何:处理并行循环中的异常
- 如何:加快小型循环体的速度
- 如何:使用并行类循环访问文件目录
- 基于任务的异步编程
- 使用延续任务来链接任务
- 已附加和已分离的子任务
- 任务取消
- 异常处理(任务并行库)
- 如何:使用 Parallel.Invoke 来执行并行操作
- 如何:从任务中返回值
- 如何:取消任务及其子级
- 如何:创建预先计算的任务
- 如何:使用并行任务遍历二叉树
- 如何:解除嵌套任务的包装
- 如何:防止子任务附加到其父任务
- 数据流(任务并行库)
- 如何:将消息写入数据流块和从数据流块读取消息
- 如何:实现制造者-使用者数据流模式
- 如何:在数据流块收到数据时执行操作
- 演练:创建数据流管道
- 如何:取消链接数据流块
- 演练:在 Windows 窗体应用程序中使用数据流
- 如何:取消数据流块
- 演练:创建自定义数据流块类型
- 如何:使用 JoinBlock 从多个源读取数据
- 如何:指定数据流块中的并行度
- 如何:在数据流块中指定任务计划程序
- 演练:使用 BatchBlock 和 BatchedJoinBlock 提高效率
- 将 TPL 用于其他异步模式
- TPL 和传统 .NET Framework 异步编程
- 如何:在任务中包装 EAP 模式
- 数据并行和任务并行中的潜在缺陷
- 并行 LINQ (PLINQ)
- PLINQ 介绍
- 了解 PLINQ 中的加速
- PLINQ 中的顺序保留
- PLINQ 中的合并选项
- PLINQ 的潜在缺陷
- 如何:创建并执行简单的 PLINQ 查询
- 如何:在 PLINQ 查询中控制排序
- 如何:合并并行和顺序 LINQ 查询
- 如何:处理 PLINQ 查询中的异常
- 如何:取消 PLINQ 查询
- 如何:编写自定义 PLINQ 聚合函数
- 如何:在 PLINQ 中指定执行模式
- 如何:在 PLINQ 中指定合并选项
- 如何:使用 PLINQ 循环访问文件目录
- 如何:衡量 PLINQ 查询性能
- PLINQ 数据示例
- 用于并行编程的数据结构
- 并行诊断工具
- PLINQ 和 TPL 的自定义分区程序
- 如何:实现动态分区
- 如何:实现静态分区的分区程序
- PLINQ 和 TPL 中的 Lambda 表达式
- 其他阅读材料(并行编程)
- .NET Framework 中的安全性
- 安全性的基础概念
- 基于角色的安全性
- 主体和标识对象
- 如何:创建 WindowsPrincipal 对象
- 如何:创建 GenericPrincipal 和 GenericIdentity 对象
- 替换 Principal 对象
- 模拟与恢复
- .NET Framework 加密模型
- 加密服务
- 生成加密和解密的密钥
- 如何:将非对称密钥存储在密钥容器中
- 加密数据
- 解密数据
- 加密签名
- 使用哈希代码确保数据完整性
- 创建加密方案
- 如何:用对称密钥对 XML 元素进行加密
- 如何:用对称密钥对 XML 元素进行解密
- 如何:用非对称密钥对 XML 元素进行加密
- 如何:用非对称密钥对 XML 元素进行解密
- 如何:用 X.509 证书对 XML 元素进行加密
- 如何:用 X.509 证书对 XML 元素进行解密
- 如何:使用数字签名为 XML 文档签名
- 如何:验证 XML 文档的数字签名
- 如何:使用数据保护
- 如何:访问硬件加密设备
- 演练:创建加密应用程序
- 代码安全维护指南
- 保护状态数据
- 安全性和用户输入
- 安全和争用条件
- 安全性和进行中的代码生成
- .NET 中的序列化
- 序列化帮助主题
- 二进制序列化
- 序列化概念
- 基本序列化
- 有选择的序列化
- 自定义序列化
- 序列化过程中的步骤
- 版本容错序列化
- 序列化准则
- 如何:对序列化数据进行分块
- 如何: 确定是否可序列化的标准.NET 对象
- 请参阅
- 请参阅
- XML 和 SOAP 序列化
- 如何:控制派生类的序列化
- XML 序列化简介
- 如何:反序列化对象
- XML 序列化示例
- XML 架构定义工具和 XML 序列化
- How to: Use the XML Schema Definition Tool to Generate Classes and XML Schema Documents
- 使用属性控制 XML 序列化
- 用来控制 XML 序列化的属性
- 如何:指定 XML 流的替代元素名称
- 如何:序列化对象
- 如何:限定 XML 元素和 XML 属性名
- 使用 XML Web services 进行 XML 序列化
- 如何:将对象序列化为 SOAP 编码的 XML 流
- 如何:重写编码的 SOAP XML 序列化
- 用来控制编码的 SOAP 序列化的属性
- <system.xml.serialization> 元素
- <dateTimeSerialization> 元素
- <schemaImporterExtensions> 元素
- <xmlSerializer> 元素
- 序列化工具
- XML 序列化程序生成器工具 (Sgen.exe)
- XML Schema Definition Tool (Xsd.exe)
- .NET Framework 的序列化示例
- 基本序列化技术示例
- 使用 XmlSerializer 自定义序列化顺序
- SchemaImporterExtension 技术示例
- 版本容错序列化技术示例
- Web 服务泛型序列化技术示例
- Web 服务 IXmlSerializable 技术示例
- 使用 .NET Framework 针对多个平台开发
- 使用可移植类库的跨平台开发
- 将可移植类库与模型-视图-视图模型配合使用
- 面向多个平台的库的应用程序资源
- .NET Framework 对 Windows 应用商店应用程序和 Windows 运行时的支持情况
- 向 Windows 运行时传递 URI
- WindowsRuntimeStreamExtensions.AsRandomAccessStream(System.IO.Stream) 方法
在.NET 中使用字符串的最佳做法
.NET 开发本地化和全球化应用程序提供广泛支持,并且可以轻松地执行排序和显示字符串等常见操作时应用的当前区域性或特定区域性的约定。 但排序或比较字符串并不总是区分区域性的操作。 例如,对于应用程序内部使用的字符串,通常应该跨所有区域性以相同的方式对其进行处理。 如果将 XML 标记、HTML 标记、用户名、文件路径和系统对象名称等与区域性无关的字符串数据解释为区分区域性,则应用程序代码会遭遇细微的错误、不佳的性能,在某些情况下,还会遭遇安全性问题。
本主题检查字符串排序、 比较和大小写在.NET 中的方法、 提出建议用于选择适当的字符串处理方法,并提供有关字符串处理方法的其他信息。 它还讨论如何处理数据格式(如数字数据以及日期和时间数据)以用于显示和存储。
本主题包含以下各节:
对字符串用法的建议
使用 .NET 进行开发时,请遵循以下简要建议使用字符串:
- 使用为字符串操作显式指定字符串比较规则的重载。 通常情况下,这涉及调用具有 StringComparison 类型的参数的方法重载。
- 使用 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 进行比较,并以此作为匹配区域性不明确的字符串的安全默认设置。
- 将比较与 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 配合使用,以获得更好的性能。
- 向用户显示输出时,使用基于 StringComparison.CurrentCulture 的字符串操作。
- 当进行与语言(例如,符号)无关的比较时,使用非语言的 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值,而不使用基于 CultureInfo.InvariantCulture 的字符串操作。
- 在规范化要比较的字符串时,使用 String.ToUpperInvariant 方法而非 String.ToLowerInvariant 方法。
- 使用 String.Equals 方法的重载来测试两个字符串是否相等。
- 使用 String.Compare 和 String.CompareTo 方法可对字符串进行排序,而不是检查字符串是否相等。
- 在用户界面,使用区分区域性的格式显示非字符串数据,如数字和日期。 使用格式以固定区域性使非字符串数据显示为字符串形式。
使用字符串时,请避免采用以下做法:
- 不要使用未显式或隐式为字符串操作指定字符串比较规则的重载。
- 在大多数情况下,不要使用基于 StringComparison.InvariantCulture 的字符串操作。 其中的一个少数例外情况是,保存在语言上有意义但区域性不明确的数据。
- 不要使用 String.Compare 或 CompareTo 方法的重载和用于确定两个字符串是否相等的返回值为 0 的测试。
- 不要使用区分区域性格式以字符串形式来保存数值数据或日期和时间数据。
显式指定字符串比较
重载 .NET 中大部分字符串操作方法。 通常,一个或多个重载会接受默认设置,然而其他重载则不接受默认设置,而是定义比较或操作字符串的精确方式。 大多数不依赖于默认设置的方法都包括 StringComparison 类型的参数,该参数是按区域性和大小写为字符串比较显式指定规则的枚举。 下表描述 StringComparison 枚举成员。
StringComparison 成员 | 说明 |
---|---|
CurrentCulture | 使用当前区域性执行区分大小写的比较。 |
CurrentCultureIgnoreCase | 使用当前区域性执行不区分大小写的比较。 |
InvariantCulture | 使用固定区域性执行区分大小写的比较。 |
InvariantCultureIgnoreCase | 使用固定区域性执行不区分大小写的比较。 |
Ordinal | 执行序号比较。 |
OrdinalIgnoreCase | 执行不区分大小写的序号比较。 |
例如, IndexOf 方法(它返回 String 对象中与某字符或字符串匹配的子字符串的索引)具有九种重载:
- 默认情况下, IndexOf(Char) , IndexOf(Char, Int32) 和 IndexOf(Char, Int32, Int32) 对字符串中的字符执行序号(区分大小写但不区分区域性的)搜索。
- 默认情况下, IndexOf(String) , IndexOf(String, Int32) 和 IndexOf(String, Int32, Int32) 对字符串中的子字符串执行区分大小写且区分区域性的搜索。
- IndexOf(String, StringComparison) 、 IndexOf(String, Int32, StringComparison) 和 IndexOf(String, Int32, Int32, StringComparison) ,其中包括 StringComparison 类型的参数,该类型允许指定比较形式。
我们建议选择不使用默认值的重载,原因如下:
- 具有默认参数的一些重载(在字符串实例中搜索 Char 的重载)执行序号比较,而其他重载(在字符串实例中搜索字符串的重载)执行的是区分区域性的比较。 要记住哪种方法使用哪个默认值并非易事,并很容易混淆重载。
- 依赖于方法调用默认值的代码的意图并不清楚。 在下面依赖于默认值的示例中,很难了解开发人员对两个字符串的实际意图是执行序号比较还是语言比较,或者
protocol
和http
之间存在的大小写差异是否会导致相等性测试返回false
类型的参数的方法重载。string protocol = GetProtocol(url); if (String.Equals(protocol, "http")) { // ...Code to handle HTTP protocol. } else { throw new InvalidOperationException(); }
Dim protocol As String = GetProtocol(url) If String.Equals(protocol, "http") Then ' ...Code to handle HTTP protocol. Else Throw New InvalidOperationException() End If
一般情况下,我们建议调用不依赖于默认设置的方法,因为这会明确代码的意图。 这进而使代码更具可读性且更易于调试和维护。 下面的示例解决了前面示例中提出的问题。 使用序号比较并且忽略大小写差异。
string protocol = GetProtocol(url);
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}
Dim protocol As String = GetProtocol(url)
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If
字符串比较的详细信息
字符串比较是许多字符串相关操作的核心,特别是排序和相等性测试操作。 字符串以确定的顺序进行排序:如果在排序的字符串列表中, my
出现在 string
之前,则 my
必定小于或等于 string
。 此外,比较可隐式确定相等性。 对于认为是相等的字符串,比较操作将返回零。 对此很好的解释是两个字符串都不小于对方。 涉及到字符串的最有意义的操作包括这些步骤中的一个或两个步骤:与另一个字符串进行比较和执行明确的排序操作。
但是,评估两个字符串的相等性或排序顺序不会生成一个正确的结果;其结果取决于用于比较这两个字符串的条件。 特别是,序号或基于当前区域性或固定区域性(基于英语语言的区域设置不明确的区域性)的大小写和排序约定的字符串比较可能会产生不同的结果。
使用当前区域性的字符串比较
一个条件涉及在比较字符串时使用当前区域性的约定。 基于当前区域性的比较使用线程的当前区域性或区域设置。 如果用户未设置该区域性,则默认为 控制面板
中 区域选项
窗口中的设置。 当数据与语言相关并反映区分区域性的用户交互时,应始终使用基于当前区域性的比较。
但是,当区域性发生更改时,.NET 中的比较和大小写行为也发生更改。 如果执行应用程序的计算机与用于开发该应用程序的计算机具有不同的区域性,或者执行线程改变它的区域性,则会发生这种情况。 此行为是有意而为之的,但许多开发人员不易察觉此行为。 下面的示例说明了美国英语( en-US
)与瑞典语( sv-SE
)区域性在排序顺序中的差异。 请注意,单词 ångström
、 Windows
和 Visual Studio
将出现在已排序的字符串数组的不同位置。
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
Array.Sort(values);
DisplayArray(values);
// Change culture to Swedish (Sweden).
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);
// Restore the original culture.
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
}
private static void DisplayArray(string[] values)
{
Console.WriteLine("Sorting using the {0} culture:",
CultureInfo.CurrentCulture.Name);
foreach (string value in values)
Console.WriteLine(" {0}", value);
Console.WriteLine();
}
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// Æble
// apple
// Windows
// Visual Studio
// ångström
Imports System.Globalization
Imports System.Threading
Module Example
Public Sub Main()
Dim values() As String = { "able", "ångström", "apple", _
"Æble", "Windows", "Visual Studio" }
Array.Sort(values)
DisplayArray(values)
' Change culture to Swedish (Sweden).
Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)
' Restore the original culture.
Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub
Private Sub DisplayArray(values() As String)
Console.WRiteLine("Sorting using the {0} culture:", _
CultureInfo.CurrentCulture.Name)
For Each value As String In values
Console.WriteLine(" {0}", value)
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' Æble
' apple
' Windows
' Visual Studio
' ångström
使用当前区域性的不区分大小写比较和区分区域性的比较是相同的,只不过前者忽略由线程的当前区域性指示的大小写。 这种情况也可表明它的排序顺序。
以下方法默认利用使用当前区域性语义的比较:
- 不包括 StringComparison 参数的 String.Compare 重载。
- String.CompareTo 重载。
- 默认值 String.StartsWith(String) 方法,与 String.StartsWith(String, Boolean, CultureInfo) 方法替换
null
CultureInfo 参数。 - 默认值 String.EndsWith(String) 方法,与 String.EndsWith(String, Boolean, CultureInfo) 方法替换
null
CultureInfo 参数。 - 接受 String 作为搜索参数且不包含 StringComparison 参数的 String.IndexOf 重载。
- 接受 String 作为搜索参数且不包含 StringComparison 参数的 String.LastIndexOf 重载。
总之,我们建议调用具有 StringComparison 参数的重载,以便明确方法调用的意图。
当从语言角度解释非语言的字符串数据,或利用其他区域性的约定解释某个特定区域性中的字符串时,则会发生或大或小的错误。 土耳其语 I 问题便是一个规范示例。
对于几乎所有拉丁字母来讲(包括美国英语),字符 i
(\u0069) 是字符 I
(\u0049) 的小写形式。 此大小写规则快速成为在此类区域性中编程的人员的默认设置。 但是,土耳其语( tr-TR
)字母表中包含一个 带有点的 I
的字符 İ
(\u0130),该字符是 i
的大写形式。 土耳其语还包括一个小写 不带点的 i
字符,即为 ı
(\u0131),该字符的大写形式为 I
。 阿塞拜疆语( az
)区域也会出现这种情况。
因此,关于将 i
变为大写或将 I
变为小写的假设并非在所有区域性中都是有效的。 如果为字符串比较例程使用默认重载,则它们可能会因区域性不同而异。 如果对非语言的数据进行比较,使用默认重载会产生不良后果,如以下对字符串 file
和 FILE
执行不区分大小写的比较尝试所示。
using System;
using System.Globalization;
using System.Threading;
public class Example
{
public static void Main()
{
string fileUrl = "file";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
}
}
// The example displays the following output:
// Culture = English (United States)
// (file == FILE) = True
//
// Culture = Turkish (Turkey)
// (file == FILE) = False
Imports System.Globalization
Imports System.Threading
Module Example
Public Sub Main()
Dim fileUrl = "file"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
End Sub
End Module
' The example displays the following output:
' Culture = English (United States)
' (file == FILE) = True
'
' Culture = Turkish (Turkey)
' (file == FILE) = False
如果无意中在安全敏感设置中使用了区域性,则此比较会导致发生重大问题,如以下示例所示。 如果当前区域性为美国英语,则 IsFileURI("file:")
等方法调用将返回 true
;但如果当前区域性为土耳其语,则将返回 false
。 因此,在土耳其语系统中,有人可能会避开阻止访问以 FILE:
开头的不区分大小写的安全措施。
public static bool IsFileURI(String path)
{
return path.StartsWith("FILE:", true, null);
}
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", True, Nothing)
End Function
在这种情况下,由于 file:
会被解释为非语言的、不区分区域性的标识符,因此应按照下面的示例所示编写代码。
public static bool IsFileURI(string path)
{
return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
序号字符串操作
在方法调用中指定 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase 值表示非语言比较,这种比较忽略了自然语言的特性。 利用 StringComparison 值调用的方法将字符串操作决策建立在简单的字节比较的基础之上,而不是按区域性参数化的大小写或相等表。 在大多数情况下,这种方法最符合字符串的预期解释,并使代码更快更可靠。
序号比较就是字符串比较,在这种比较中,将比较每个字符串中的每个字节且不进行语言解释;例如, windows
不匹配 Windows
。 实质上,这是对 C 运行时 strcmp
函数的调用。 当上下文指示应完全匹配字符串或要求保守匹配策略时,请使用这种比较。 此外,序号比较是最快的比较操作,因为它在确定结果时不应用任何语言规则。
.NET 中的字符串可以包括嵌入的空字符。 序号比较与区分区域性的比较(包括使用固定区域性的比较)之间最明显的区别之一是对字符串中嵌入的空字符的处理方式。 当使用 String.Compare 和 String.Equals 方法执行区分区域性的比较(包括使用固定区域性的比较)时,将忽略这些字符。 因此,在区分区域性的比较中,包含嵌入的空字符的字符串可视为等于不包含空字符的字符串。
重要
尽管字符串比较方法忽略嵌入的空字符,但是 String.Contains 、 String.EndsWith 、 String.IndexOf 、 String.LastIndexOf 和 String.StartsWith 等字符串搜索方法并不会忽略这些字符。
下面的示例对字符串 Aa
与在 A
和 a
之间嵌入了多个空字符的相似字符串进行区分区域性的比较,并显示如何将这两个字符串视为相等的字符串。
using System;
public class Example
{
public static void Main()
{
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Current Culture: {0}",
String.Compare(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Compare(str1, str2, StringComparison.InvariantCulture));
Console.WriteLine(" With String.Equals:");
Console.WriteLine(" Current Culture: {0}",
String.Equals(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Equals(str1, str2, StringComparison.InvariantCulture));
}
private static string ShowBytes(string str)
{
string hexString = String.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = String.Empty;
result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Module Example
Public Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Current Culture: {0}", _
String.Compare(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Compare(str1, str2, StringComparison.InvariantCulture))
Console.WriteLine(" With String.Equals:")
Console.WriteLine(" Current Culture: {0}", _
String.Equals(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Equals(str1, str2, StringComparison.InvariantCulture))
End Sub
Private Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = String.Empty
result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
hexString += result
Next
Return hexString.Trim()
End Function
End Module
但是,当使用序号比较时,这两个字符串不会视为相等,如下面的示例所示。
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Ordinal: {0}",
String.Compare(str1, str2, StringComparison.Ordinal));
Console.WriteLine(" With String.Equals:");
Console.WriteLine(" Ordinal: {0}",
String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Ordinal: {0}", _
String.Compare(str1, str2, StringComparison.Ordinal))
Console.WriteLine(" With String.Equals:")
Console.WriteLine(" Ordinal: {0}", _
String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False
不区分大小写的序号比较是第二种最保守的方法。 这些比较会忽略大多数的大小写;例如, windows
会匹配 Windows
。 在处理 ASCII 字符时,此策略等同于 StringComparison.Ordinal ,只不过它会忽略常用的 ASCII 大小写。 因此, A, Z 中的任何字符都会匹配 a,z 中的相应字符。 超出 ASCII 范围的大小写使用固定区域性的表。 因此,下面的比较:
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
等效于(但会更快)这种比较:
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal)
这些比较仍非常快。
备注
文件系统、注册表项和值以及环境变量的字符串行为可由 StringComparison.OrdinalIgnoreCase 很好地表现出来。
StringComparison.Ordinal 和 StringComparison.OrdinalIgnoreCase 均直接使用二进制值并最适合匹配。 当不确定比较设置时,请使用这两个值中的其中一个。 不过,由于它们执行逐字节比较,因此不会按照语言排序顺序(如英语词典)进行排序,而是按照二进制排序顺序。 如果向用户显示结果,则在大多数上下文中结果都看上去不正常。
序号语义是不包括 StringComparison 参数(包括相等运算符)的 String.Equals 重载的默认项。 总之,我们建议调用具有 StringComparison 参数的重载。
使用固定区域性的字符串操作
具有固定区域性的比较使用由静态 CultureInfo.InvariantCulture 属性返回的 CompareInfo 属性。 此行为在所有系统中都相同;它会将其范围外的任何字符转换为其认为等效的固定字符。 此策略对于在各个区域性中维护一组字符串行为很有用,但经常产生意外的结果。
具有固定区域性的不区分大小写的比较也使用由静态 CultureInfo.InvariantCulture 属性返回的静态 CompareInfo 属性以获取比较信息。 所转换字符中的任何大小写差异都将被忽略。
使用 StringComparison.InvariantCulture 和 StringComparison.Ordinal 的比较对 ASCII 字符串产生相同的作用。 但是, StringComparison.InvariantCulture 会做出可能不适用于解释为一组字节的字符串的语言性决策。 还可以使用 CultureInfo.InvariantCulture.CompareInfo
对象使 Compare 方法将一组特定的字符解释为等效字符。 例如,下面的等效字符在固定区域性中是有效的:
InvariantCulture:a + ̊ = å
当 A 字符的小写拉丁字母 a
(\u0061) 位于字符 + " ̊"(\u030a) 组合圆圈旁边,则产生上方带有圆圈的小写拉丁字母“å
(\u00e5)。 如下面的示例所示,此行为不同于序号比较。
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
separated, combined,
String.Compare(separated, combined,
StringComparison.InvariantCulture) == 0);
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
separated, combined,
String.Compare(separated, combined,
StringComparison.Ordinal) == 0);
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False
Dim separated As String = ChrW(&h61) + ChrW(&h30a)
Dim combined As String = ChrW(&he5)
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _
separated, combined, _
String.Compare(separated, combined, _
StringComparison.InvariantCulture) = 0)
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _
separated, combined, _
String.Compare(separated, combined, _
StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False
当解释其中出现如 å
组合的文件名称、cookie 或其他内容时,序号比较仍会提供最透明和最合适的行为。
总的来说,固定区域性具有极少的对比较有用的属性。 它会以与语言相关的方式执行比较,使其无法保证完整的符号等效性,但它并不是任何区域性中显示的选择。 使用 StringComparison.InvariantCulture 进行比较的其中一个原因是为多个区域性相同的显示保留已排序的数据。 例如,如果应用程序附带包含用于显示的已排序标识符列表的大型数据文件,则添加到此列表将需要使用固定条件样式排序插入。
为方法调用选择 StringComparison 成员
下表概述了从语义字符串上下文到 StringComparison 枚举成员的映射。
数据 | 行为 | 相应 System.StringComparison 值 |
---|---|---|
区分大小写的内部标识符。 区分大小写的标准标识符(例如 XML 和 HTTP)。 区分大小写的安全相关设置。 | 字节完全匹配的非语言标识符。 | Ordinal |
不区分大小写的内部标识符。 不区分大小写的标准标识符(例如 XML 和 HTTP)。 文件路径。 注册表项和值。 环境变量。 资源标识符(例如,句柄名称)。 不区分大小写的安全相关设置。 | 无关大小写的非语言标识符;尤其是存储在大多数 Windows 系统服务中的数据。 | OrdinalIgnoreCase |
某些保留的、与语言相关的数据。 需要固定排序顺序的语言数据的显示。 | 仍与语言相关的区域性不明确数据。 | InvariantCulture - 或 - InvariantCultureIgnoreCase |
向用户显示的数据。 大多数用户输入。 | 需要本地语言自定义的数据。 | CurrentCulture - 或 - CurrentCultureIgnoreCase |
.NET 中的常见字符串比较方法
以下各节介绍最常用于执行字符串比较的方法。
String.Compare
默认解释: StringComparison.CurrentCulture 。
作为字符串解释最核心的操作,应根据当前区域性检查这些方法调用的所有实例来确定是否应该从区域性(符号)解释或分离字符串。 通常情况下,采用后者,并且应改用 StringComparison.Ordinal 比较。
CultureInfo.CompareInfo 属性返回的 System.Globalization.CompareInfo 类也包括利用 CompareOptions 标记枚举的方式提供大量匹配选项(序号、忽略空白、忽略假名类型等)的 Compare 方法。
String.CompareTo
默认解释: StringComparison.CurrentCulture 。
此方法当前不提供指定 StringComparison 类型的重载。 通常可以将此方法转换为建议的 String.Compare(String, String, StringComparison) 形式。
实现 IComparable 和 IComparable<T> 接口的类型实现此方法。 由于它不提供 StringComparison 参数选项,因此实现类型经常使用户在其构造函数中指定 StringComparer 。 下面的示例定义 FileName
类,其类构造函数包括 StringComparer 参数。 然后此 StringComparer 对象将用于 FileName.CompareTo
方法。
using System;
public class FileName : IComparable
{
string fname;
StringComparer comparer;
public FileName(string name, StringComparer comparer)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
this.fname = name;
if (comparer != null)
this.comparer = comparer;
else
this.comparer = StringComparer.OrdinalIgnoreCase;
}
public string Name
{
get { return fname; }
}
public int CompareTo(object obj)
{
if (obj == null) return 1;
if (! (obj is FileName))
return comparer.Compare(this.fname, obj.ToString());
else
return comparer.Compare(this.fname, ((FileName) obj).Name);
}
}
Public Class FileName : Implements IComparable
Dim fname As String
Dim comparer As StringComparer
Public Sub New(name As String, comparer As StringComparer)
If String.IsNullOrEmpty(name) Then
Throw New ArgumentNullException("name")
End If
Me.fname = name
If comparer IsNot Nothing Then
Me.comparer = comparer
Else
Me.comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub
Public ReadOnly Property Name As String
Get
Return fname
End Get
End Property
Public Function CompareTo(obj As Object) As Integer _
Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
If Not TypeOf obj Is FileName Then
obj = obj.ToString()
Else
obj = CType(obj, FileName).Name
End If
Return comparer.Compare(Me.fname, obj)
End Function
End Class
String.Equals
默认解释: StringComparison.Ordinal 。
String 类可通过调用静态或实例 Equals 方法重载或使用静态相等运算符,测试是否相等。 默认情况下,重载和运算符使用序号比较。 但是,我们仍然建议调用显式指定 StringComparison 类型的重载,即使想要执行序号比较;这将更轻松地搜索特定字符串解释的代码。
String.ToUpper 和 String.ToLower
默认解释: StringComparison.CurrentCulture 。
应谨慎使用这些方法,因为将字符串强制为大写或小写经常用作在不考虑大小写的情况下比较字符串的较小规范化。 如果是这样,请考虑使用不区分大小写的比较。
还可以使用 String.ToUpperInvariant 和 String.ToLowerInvariant 方法。 ToUpperInvariant 是规范化大小写的标准方式。 使用 StringComparison.OrdinalIgnoreCase 进行的比较在行为上是两个调用的组合:对两个字符串参数调用 ToUpperInvariant ,并使用 StringComparison.Ordinal 执行比较。
通过向方法传递表示区域性的 CultureInfo 对象,重载也已可用于转换该特性区域性中的大写和小写字母。
Char.ToUpper 和 Char.ToLower
默认解释: StringComparison.CurrentCulture 。
这些方法的工作原理类似于上一节中所述的 String.ToUpper 和 String.ToLower 方法。
String.StartsWith 和 String.EndsWith
默认解释: StringComparison.CurrentCulture 。
默认情况下,这两种方法执行区分区域性的比较。
String.IndexOf 和 String.LastIndexOf
默认解释: StringComparison.CurrentCulture 。
这些方法的默认重载如何执行比较方面缺乏一致性。 包含 Char 参数的所有 String.IndexOf 和 String.LastIndexOf 方法都执行序号比较,但是包含 String 参数的默认 String.IndexOf 和 String.LastIndexOf 方法都执行区分区域性的比较。
如果调用 String.IndexOf(String) 或 String.LastIndexOf(String) 方法并向其传递一个字符串以在当前实例中查找,那么我们建议调用显式指定 StringComparison 类型的重载。 包括 Char 参数的重载不允许指定 StringComparison 类型。
间接执行字符串比较的方法
将字符串比较作为核心操作的一些非字符串方法使用 StringComparer 类型。 StringComparer 类型包含六个返回 StringComparer 实例的静态属性,这些实例的 StringComparer.Compare 方法可执行以下类型的字符串比较:
- 使用当前区域性的区分区域性的字符串比较。 此 StringComparer 对象由 StringComparer.CurrentCulture 属性返回。
- 使用当前区域性的不区分区域性的比较。 此 StringComparer 对象由 StringComparer.CurrentCultureIgnoreCase 属性返回。
- 使用固定区域性的单词比较规则的不区分区域性的比较。 此 StringComparer 对象由 StringComparer.InvariantCulture 属性返回。
- 使用固定区域性的单词比较规则的不区分大小写和不区分区域性的比较。 此 StringComparer 对象由 StringComparer.InvariantCultureIgnoreCase 属性返回。
- 序号比较。 此 StringComparer 对象由 StringComparer.Ordinal 属性返回。
- 不区分大小写的序号比较。 此 StringComparer 对象由 StringComparer.OrdinalIgnoreCase 属性返回。
Array.Sort 和 Array.BinarySearch
默认解释: StringComparison.CurrentCulture 。
当在集合中存储任何数据,或将持久数据从文件或数据库中读取到集合中时,切换当前区域性可能会使集合中的固定条件无效。 Array.BinarySearch 方法假定已对数组中要搜索的元素排序。 若要对数组中的任何字符串元素进行排序, Array.Sort 方法会调用 String.Compare 方法以对各个元素进行排序。 如果对数组进行排序和搜索其内容的时间范围内区域性发生变化,那么使用区分区域性的比较器会很危险。 例如在下面的代码中,是在由 Thread.CurrentThread.CurrentCulture
属性。 如果在调用 StoreNames
和 DoesNameExist
之间更改了区域性(尤其是数组内容保存在两个方法调用之间的某个位置),那么二进制搜索可能会失败。
// Incorrect.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name) >= 0); // Line B.
}
' Incorrect.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name) >= 0 ' Line B.
End Function
建议的变体将显示在下面使用相同序号(不区分区域性)比较方法进行排序并搜索数组的示例中。 在这两个示例中,更改代码会反映在标记 Line A
和 Line B
的代码行中。
// Correct.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names, StringComparer.Ordinal); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0); // Line B.
}
' Correct.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names, StringComparer.Ordinal) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0 ' Line B.
End Function
如果此数据永久保留并跨区域性移动,并且使用排序来向用户显示此数据,则可以考虑使用 StringComparison.InvariantCulture ,其语言操作可获得更好的用户输出且不受区域性更改的影响。 下面的示例修改了前面两个示例,使用固定区域性对数组进行排序和搜索。
// Correct.
string []storedNames;
public void StoreNames(string [] names)
{
int index = 0;
storedNames = new string[names.Length];
foreach (string name in names)
{
this.storedNames[index++] = name;
}
Array.Sort(names, StringComparer.InvariantCulture); // Line A.
}
public bool DoesNameExist(string name)
{
return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0); // Line B.
}
' Correct.
Dim storedNames() As String
Public Sub StoreNames(names() As String)
Dim index As Integer = 0
ReDim storedNames(names.Length - 1)
For Each name As String In names
Me.storedNames(index) = name
index+= 1
Next
Array.Sort(names, StringComparer.InvariantCulture) ' Line A.
End Sub
Public Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B.
End Function
集合示例:Hashtable 构造函数
哈希字符串提供了第二个运算示例,该运算受比较字符串的方式影响。
下面的示例实例化 Hashtable 对象,方法是向其传递由 StringComparer.OrdinalIgnoreCase 属性返回的 StringComparer 对象。 由于派生自 StringComparer 的类 StringComparer 实现 IEqualityComparer 接口,其 GetHashCode 方法用于计算哈希表中的字符串的哈希代码。
const int initialTableCapacity = 100;
Hashtable h;
public void PopulateFileTable(string directory)
{
h = new Hashtable(initialTableCapacity,
StringComparer.OrdinalIgnoreCase);
foreach (string file in Directory.GetFiles(directory))
h.Add(file, File.GetCreationTime(file));
}
public void PrintCreationTime(string targetFile)
{
Object dt = h[targetFile];
if (dt != null)
{
Console.WriteLine("File {0} was created at time {1}.",
targetFile,
(DateTime) dt);
}
else
{
Console.WriteLine("File {0} does not exist.", targetFile);
}
}
Const initialTableCapacity As Integer = 100
Dim h As Hashtable
Public Sub PopulateFileTable(dir As String)
h = New Hashtable(initialTableCapacity, _
StringComparer.OrdinalIgnoreCase)
For Each filename As String In Directory.GetFiles(dir)
h.Add(filename, File.GetCreationTime(filename))
Next
End Sub
Public Sub PrintCreationTime(targetFile As String)
Dim dt As Object = h(targetFile)
If dt IsNot Nothing Then
Console.WriteLine("File {0} was created at {1}.", _
targetFile, _
CDate(dt))
Else
Console.WriteLine("File {0} does not exist.", targetFile)
End If
End Sub
显示和保存有格式的数据
当给用户显示非字符串数据(如数字、日期和时间)时,使用用户的区域性设置来格式化他们。 默认情况下,数值类型和日期时间类型的 String.Format 方法和 ToString
方法使用当前线程区域性来格式设置操作。 为了明确指定格式设置方法应使用当前区域性,您可以调用包括 provider
参数的格式设置方法的重载(例如 String.Format(IFormatProvider, String, Object[]) 或 DateTime.ToString(IFormatProvider) ),然后向其传递 CultureInfo.CurrentCulture 属性。
您可以保留非字符串数据作为二进制数据或作为格式化数据。 如果您选择将其保存为格式化数据,您应调用包括 provider
参数的格式设置方法重载,并向其传递 CultureInfo.InvariantCulture 属性。 固定区域性为独立于区域性和计算机的格式化数据提供一致的格式。 相反,使用区域性而非固定区域性进行格式化的持久性数据具有许多限制:
- 如果在具有不同区域性的系统上检索数据,或者如果当前系统用户更改当前区域性或者尝试检索数据时,该数据可能不可用。
- 特定计算机上的区域性属性可能与标准值不同。 任何时候,用户都可以自定义区分区域性的显示设置。 因此,在系统保存的格式化数据在用户自定义区域性设置之后可能无法读取。 格式化数据在计算机之间移植可能会受到更多的限制。
- 管理数值或日期时间格式的国际、区域或国家标准会随着时间发生更改,这些更改会合并到 Windows 操作系统更新中。 在格式设置约定更改时,将无法读取使用以前的约定格式化的数据。
下面的示例演示了使用区分区域性格式设置进行持久化数据导致的有限可移植性。 该示例将日期和时间数组值保存到文件中。 这些数据通过使用英语(美国)区域性约定进行格式化。 在应用程序将当前线程区域性更改为法语(瑞士)后,它尝试使用当前区域性的格式设置约定来读取保存的值。 尝试读取两个数据条目时引发 FormatException 异常,现在日期数组包含相当于 MinValue 的两个错误元素。
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
public class Example
{
private static string filename = @".\dates.dat";
public static void Main()
{
DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0),
new DateTime(1818, 5, 5, 7, 19, 0),
new DateTime(1870, 4, 22, 23, 54, 0),
new DateTime(1890, 9, 8, 6, 47, 0),
new DateTime(1905, 2, 18, 15, 12, 0) };
// Write the data to a file using the current culture.
WriteData(dates);
// Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
// Read the data using the current culture.
DateTime[] newDates = ReadData();
foreach (var newDate in newDates)
Console.WriteLine(newDate.ToString("g"));
}
private static void WriteData(DateTime[] dates)
{
StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);
for (int ctr = 0; ctr < dates.Length; ctr++) {
sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
if (ctr < dates.Length - 1) sw.Write("|");
}
sw.Close();
}
private static DateTime[] ReadData()
{
bool exceptionOccurred = false;
// Read file contents as a single string, then split it.
StreamReader sr = new StreamReader(filename, Encoding.UTF8);
string output = sr.ReadToEnd();
sr.Close();
string[] values = output.Split( new char[] { '|' } );
DateTime[] newDates = new DateTime[values.Length];
for (int ctr = 0; ctr < values.Length; ctr++) {
try {
newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
}
catch (FormatException) {
Console.WriteLine("Failed to parse {0}", values[ctr]);
exceptionOccurred = true;
}
}
if (exceptionOccurred) Console.WriteLine();
return newDates;
}
}
// The example displays the following output:
// Failed to parse 4/22/1870 11:54 PM
// Failed to parse 2/18/1905 3:12 PM
//
// 05.06.1758 21:26
// 05.05.1818 07:19
// 01.01.0001 00:00
// 09.08.1890 06:47
// 01.01.0001 00:00
// 01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading
Module Example
Private filename As String = ".\dates.dat"
Public Sub Main()
Dim dates() As Date = { #5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _
#4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _
#2/18/1905 3:12PM# }
' Write the data to a file using the current culture.
WriteData(dates)
' Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
' Read the data using the current culture.
Dim newDates() As Date = ReadData()
For Each newDate In newDates
Console.WriteLine(newDate.ToString("g"))
Next
End Sub
Private Sub WriteData(dates() As Date)
Dim sw As New StreamWriter(filename, False, Encoding.Utf8)
For ctr As Integer = 0 To dates.Length - 1
sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
If ctr < dates.Length - 1 Then sw.Write("|")
Next
sw.Close()
End Sub
Private Function ReadData() As Date()
Dim exceptionOccurred As Boolean = False
' Read file contents as a single string, then split it.
Dim sr As New StreamReader(filename, Encoding.Utf8)
Dim output As String = sr.ReadToEnd()
sr.Close()
Dim values() As String = output.Split( {"|"c } )
Dim newDates(values.Length - 1) As Date
For ctr As Integer = 0 To values.Length - 1
Try
newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
Catch e As FormatException
Console.WriteLine("Failed to parse {0}", values(ctr))
exceptionOccurred = True
End Try
Next
If exceptionOccurred Then Console.WriteLine()
Return newDates
End Function
End Module
' The example displays the following output:
' Failed to parse 4/22/1870 11:54 PM
' Failed to parse 2/18/1905 3:12 PM
'
' 05.06.1758 21:26
' 05.05.1818 07:19
' 01.01.0001 00:00
' 09.08.1890 06:47
' 01.01.0001 00:00
' 01.01.0001 00:00
'
然而,如果您在 CultureInfo.CurrentCulture 和 CultureInfo.InvariantCulture 调用中用 DateTime.ToString(String, IFormatProvider) 替换 DateTime.Parse(String, IFormatProvider) 属性,则持久化的日期时间数据会成功恢复,如下方输出所示。
06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12
另请参阅
控制字符串
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论