您的位置:首页 > 汽车 > 时评 > C#可空类型

C#可空类型

2024/11/19 15:09:16 来源:https://blog.csdn.net/mengningyun6826/article/details/139104948  浏览:    关键词:C#可空类型

“可空引用类型的目标是防止空引用异常”

可空值类型与可空引用类型

在C# 2.0之前,只有引用类型是可为null的。值类型,例如int或DateTime不能是null 。如果这些类型在没有值的情况下初始化,则它们将回退到其默认值。例如int是0,DateTime是DateTime.MinValue。常用的值类型:byte,short,int,long,float,double,decimal,char,bool,enum和struct。

string first;                   // first为null,是因为已声明引用类型string,但未进行赋值
string second = string.Empty;   // second在声明时分配string.Empty
int third;                      // third尽管没有被分配。它是一个值类型,默认值为0
DateTime date;                  // date未初始化,但其默认值为System.DateTime.MinValue

从C# 2.0开始,可使用泛型类型System.Nullable(或T?速记)定义可为null的值类型,其中T就是可替换的值类型。

int? first;             // first是null是因为可为null的值类型未初始化
int? second = null;     // second在声明时分配null
int? third = default;   // third为null是因为Nullable<int>默认值是null
int? fourth = new();    // fourth是0是因为new()表达式调用Nullable<int>构造函数时,默认int为0

C# 8.0引入了可为null的引用类型,你可以表达引用类型可能是null或始终是非null的意图。

在C# 8.0之前,所有引用类型默认情况下可以是null,并且编译器不会警告你未检查的null赋值或使用。C# 8.0引入了可为空的引用类型(nullable reference types),使得你可以在类型系统中明确地表示某个引用类型是否允许为null。这有助于减少空引用异常(NullReferenceException)并提高代码的安全性和可维护性。

  • 如果你在类型声明后加上?,例如string?,这意味着这个引用类型可以是null。
  • 如果你不加?,例如string,在启用可为空引用类型的情况下,编译器会假设这个引用类型不应该是null,并在可能为null的情况下给出警告。
#nullable enablestring first = string.Empty;//first从来都不是null,因为它被明确分配
string second;              //second不应该是null
string? third;              //third可能是null,例如,它可能指向System.String,但也可能指向null。这些中的任何一种都是可以接受的

务必注意,可为null的引用类型与可为null的值类型不是一回事。可为null的值类型映射到.NET中的具体类类型。所以int?​实际上是Nullable<int>​。但是对于string?​,它实际上是相同的string​,但有一个编译器生成的属性来注释它。这样做是为了向后兼容。换句话说,string?​是一种“假类型”,而int?​不是。

应用举例

变量在为Null时返回的一个指定的值

namespace Demo
{internal class Program{static void Main(string[] args){double? numNull = null;double? numNotNull = 3.14157;double num;num = numNull ?? 5.34;Console.WriteLine("num的值:{0}", num); //num的值:5.34num = numNotNull ?? 5.34;Console.WriteLine("num的值:{0}", num); //num的值:3.14157Console.ReadLine();}}
}

可空值类型的值赋给普通类型

编译器不允许将一个可空值类型的值直接赋值给普通的值类型,例如下面的代码是不能通过编译的:

int? myNullabelInteger=null;
int myInteger=myNullabelInteger;

因为可空值类型的值可以是null,而null不可以赋给普通值类型。

将一个可空值类型的值赋给普通类型的方法是:先用System.Nullable<T>.Hasvalue​属性判断这个可空值类型是否被赋过一个类型为T的有效值:

namespace Demo
{internal class Program{static void Main(string[] args){int? myNullableInteger = null;if (myNullableInteger.HasValue){int myInteger = myNullableInteger.Value;}else{Console.WriteLine("myNullableInteger尚未被赋非null值");}myNullableInteger = 666;if (myNullableInteger.HasValue){int myInteger = myNullableInteger.Value;Console.WriteLine($"myNullableInteger赋值{myInteger},并将myNullableInteger赋值给普通值类型的myInteger");}Console.ReadLine();}}
}

输出结果:

myNullableInteger尚未被赋非null值
myNullableInteger赋值666,并将myNullableInteger赋值给普通值类型的myInteger

数据转换为指定类型

将一个object类型的值转换为任何可能转换的类型

using System.ComponentModel;
public static class BaseHelper
{public static object CusChanageType(this object value, Type convertsionType){//判断convertsionType类型是否为泛型,因为nullable是泛型类。判断convertsionType是否为nullable泛型类if (convertsionType.IsGenericType && convertsionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))){if (value == null || value.ToString().Length == 0){return null;}//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换NullableConverter nullableConverter = new NullableConverter(convertsionType);//将convertsionType转换为nullable对的基础基元类型convertsionType = nullableConverter.UnderlyingType;}return Convert.ChangeType(value, convertsionType);}
}
namespace Demo
{internal class Program{static void Main(string[] args){// 示例1:将字符串转换为可空整数object value1 = "123";Type type1 = typeof(int?);object result1 = value1.CusChanageType(type1);Console.WriteLine(result1);  // 输出: 123// 示例2:将null转换为可空整数object value2 = null;Type type2 = typeof(int?);object result2 = value2.CusChanageType(type2);Console.WriteLine(result2 == null);  // 输出: True// 示例3:将字符串转换为非可空整数object value3 = "456";Type type3 = typeof(int);object result3 = value3.CusChanageType(type3);Console.WriteLine(result3);  // 输出: 456// 示例4:将字符串转换为非可空浮点数object value4 = "789.01";Type type4 = typeof(double);object result4 = value4.CusChanageType(type4);Console.WriteLine(result4);  // 输出: 789.01// 示例5:将空字符串转换为可空浮点数object value5 = "";Type type5 = typeof(double?);object result5 = value5.CusChanageType(type5);Console.WriteLine(result5 == null);  // 输出: True// 示例6:将布尔值字符串转换为布尔类型object value6 = "true";Type type6 = typeof(bool);object result6 = value6.CusChanageType(type6);Console.WriteLine(result6);  // 输出: True}}
}

继续给出一个更加贴近实际项目的例子。项目开发中经常从配置文件​读取配置信息(这里省去从文件中读取配置,直接将配置文件信息初始化到configValues字典中)

using System.ComponentModel;
using System;
using System.Collections.Generic;public class ConfigReader
{private readonly Dictionary<string, string> _configValues;public ConfigReader(Dictionary<string, string> configValues){_configValues = configValues;}public T GetConfigValue<T>(string key){if (_configValues.TryGetValue(key, out var value)){return (T)value.CusChanageType(typeof(T));}return default(T);}
}
public class AppConfig
{public int? MaxRetries { get; set; }public double? Timeout { get; set; }public bool EnableLogging { get; set; }public string LogLevel { get; set; }
}
public static class BaseHelper
{public static object CusChanageType(this object value, Type convertsionType){//判断convertsionType类型是否为泛型,因为nullable是泛型类。判断convertsionType是否为nullable泛型类if (convertsionType.IsGenericType && convertsionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))){if (value == null || value.ToString().Length == 0){return null;}//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换NullableConverter nullableConverter = new NullableConverter(convertsionType);//将convertsionType转换为nullable对的基础基元类型convertsionType = nullableConverter.UnderlyingType;}return Convert.ChangeType(value, convertsionType);}
}
namespace Demo
{internal class Program{static void Main(string[] args){// 初始化配置字典var configValues = new Dictionary<string, string>{{ "MaxRetries", "5" },{ "Timeout", "30.5" },{ "EnableLogging", "true" },{ "LogLevel", "info" }};var configReader = new ConfigReader(configValues);var config = new AppConfig{MaxRetries = configReader.GetConfigValue<int?>("MaxRetries"),Timeout = configReader.GetConfigValue<double?>("Timeout"),EnableLogging = configReader.GetConfigValue<bool>("EnableLogging"),LogLevel = configReader.GetConfigValue<string>("LogLevel")};// 输出读取的配置项Console.WriteLine($"MaxRetries: {config.MaxRetries}");Console.WriteLine($"Timeout: {config.Timeout}");Console.WriteLine($"EnableLogging: {config.EnableLogging}");Console.WriteLine($"LogLevel: {config.LogLevel}");}}
}

程序输出:

MaxRetries: 5
Timeout: 30.5
EnableLogging: True
LogLevel: info

反射来确定属性是否是一个可空类型

这个示例展示了如何使用反射来确定一个属性是否是Nullable<>类型,并获取其基础类型。在实际应用中,这种方法可以用于动态处理类型信息,适用于很多需要自动化处理属性类型的场景。

using System;
using System.Reflection;public class NullableTest
{public int? Id { get; set; }public int Age { get; set; }
}class Program
{static void Main(){var idPropertyInfo = typeof(NullableTest).GetProperty("Id");var agePropertyInfo = typeof(NullableTest).GetProperty("Age");if (idPropertyInfo.PropertyType.IsGenericType && idPropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)){Type[] typeArray = idPropertyInfo.PropertyType.GetGenericArguments();Type baseType = typeArray[0];//泛型参数数组中的第一个(也是唯一一个)元素即为基础类型//如果属性是可空类型,输出属性名称和基础类型Console.WriteLine($"Property '{idPropertyInfo.Name}' is a nullable type.");Console.WriteLine($"Base type of '{idPropertyInfo.Name}' is {baseType}.");}else{//如果属性不是可空类型,输出相应信息Console.WriteLine($"Property '{idPropertyInfo.Name}' is not a nullable type.");}if (agePropertyInfo.PropertyType.IsGenericType && agePropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)){Type[] typeArray = agePropertyInfo.PropertyType.GetGenericArguments();Type baseType = typeArray[0];//泛型参数数组中的第一个(也是唯一一个)元素即为基础类型//如果属性是可空类型,输出属性名称和基础类型Console.WriteLine($"Property '{agePropertyInfo.Name}' is a nullable type.");Console.WriteLine($"Base type of '{agePropertyInfo.Name}' is {baseType}.");}else{//如果属性不是可空类型,输出相应信息Console.WriteLine($"Property '{agePropertyInfo.Name}' is not a nullable type.");}}
}
Property 'Id' is a nullable type.
Base type of 'Id' is System.Int32.
Property 'Age' is not a nullable type.

AllowNull

AllowNull属性用于指示即使属性的类型是非可空类型,赋值时也允许null值。它告诉编译器在赋值过程中不产生警告。

using System;
using System.Diagnostics.CodeAnalysis;public class MyClass
{private string _innerValue = string.Empty;[AllowNull]public string MyValue{get{return _innerValue;}set{_innerValue = value ?? string.Empty;}}
}public class Program
{public static void Main(){// 创建MyClass类的一个实例MyClass instance = new MyClass();// 打印初始值(应该是空字符串)Console.WriteLine($"Initial value: '{instance.MyValue}'"); // Output: ''// 设置一个非null值instance.MyValue = "Hello, World!";Console.WriteLine($"After setting non-null value: '{instance.MyValue}'"); // Output: 'Hello, World!'// 设置null值instance.MyValue = null;Console.WriteLine($"After setting null value: '{instance.MyValue}'"); // Output: ''// 再次设置一个非null值instance.MyValue = "Another Value";Console.WriteLine($"After setting another non-null value: '{instance.MyValue}'"); // Output: 'Another Value'}
}

如果注释AllowNull,编译器会提示如下

在这里插入图片描述

启用可空引用类型

项目级别

需要使用 C# 8 或更高版本,在”项目名.csproj“文件中,<Nullable>enable</Nullable>​,enable​代表启用

在.NET 6之前,该特性默认是禁用的,即为disable

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup></Project>

文件方式

要控制每个文件,可以使用#nullable enable​开始启用,#nullable disable​开始禁用

class Program
{static void Main(){Console.WriteLine("");}
#nullable disablestatic void A(){Version.TryParse("1.0.0", out var version);Console.WriteLine(version.Major);}
#nullable enablestatic void B(){Version.TryParse("1.0.0", out var version);Console.WriteLine(version.Major);}
}

在这里插入图片描述

参考

  • C# 8:可为 null 的引用类型 - Meziantou 的博客 — C# 8: Nullable Reference Types - Meziantou's blog
  • C# 项目的 nullable 检查 - harrychinese - 博客园 (cnblogs.com)
  • 可为 null 的引用类型 - C# |Microsoft学习 — Nullable reference types - C# | Microsoft Learn
  • C# 8.0+新武器:如何用Nullable Reference Types消除99%的空指针异常? - 知乎 (zhihu.com)
  • .NET 6新特性试用 | 可空引用类型 - 知乎 (zhihu.com)
  • 了解可空性 - 培训 |Microsoft学习 — Understanding nullability - Training | Microsoft Learn
  • 表达意图 - 培训 |Microsoft学习 — Expressing intent - Training | Microsoft Learn
  • 试用可为 null 的引用类型 - .NET 博客 — Try out Nullable Reference Types - .NET Blog (microsoft.com)
  • https://www.cnblogs.com/RainFate/p/17236969.html#_label0
  • http://www.easy-dotnet.com/pages/72a0be/
  • https://blog.csdn.net/longhaoyou/article/details/69257790
  • https://blog.csdn.net/weixin_43267344/article/details/100034166
  • https://blog.csdn.net/pmandy/article/details/83813327
  • https://www.cnblogs.com/taofh/archive/2009/08/27/1554807.html

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com