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 enable

string 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.34
            num = numNotNull ?? 5.34;
            Console.WriteLine("num的值:{0}", num); //num的值:3.14157
            Console.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 disable
    static void A()
    {
        Version.TryParse("1.0.0", out var version);
        Console.WriteLine(version.Major);
    }
#nullable enable
    static void B()
    {
        Version.TryParse("1.0.0", out var version);
        Console.WriteLine(version.Major);
    }
}

在这里插入图片描述

参考

  • 35
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值