C可空类型Skill csharp-nullable-types

C# 可空类型技能用于在 C# 编程中处理空值,包括可空值类型、可空引用类型、空合并运算符和空条件运算符等,以提高代码的 null 安全性、防止空引用异常,并支持编译时检查。关键词:C#, 可空类型, null 安全, 编程, 软件开发, 空合并, 空条件, 可空注解, 迁移策略, 最佳实践。

后端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: csharp-nullable-types 用户可调用: false 描述: 当C#可空引用类型和值类型用于空安全性、可空注解和处理空值的模式时使用。 允许的工具:

  • 读取
  • 写入
  • 编辑
  • Grep
  • Glob
  • Bash

C# 可空类型

C#中的可空类型有助于防止空引用异常,这是最常见的编程错误之一。C# 8.0引入了可空引用类型,提供了编译时空安全性检查。此技能涵盖可空值类型、可空引用类型、空合并运算符以及安全处理空值的模式。

可空值类型

值类型(int、double、bool、struct)通常不能为null。可空值类型使得能够表示“无值”状态。

using System;

public class NullableValueTypes
{
    // 声明
    public void Declaration()
    {
        int? nullableInt = null;
        int? anotherInt = 42;

        double? nullableDouble = null;
        bool? nullableBool = true;

        // 通用语法(等价)
        Nullable<int> genericNullable = null;
    }

    // 检查空值
    public void NullChecking()
    {
        int? value = GetNullableValue();

        // HasValue属性
        if (value.HasValue)
        {
            int actualValue = value.Value;
            Console.WriteLine(actualValue);
        }

        // 与null比较
        if (value != null)
        {
            Console.WriteLine(value.Value);
        }

        // 空条件运算符
        int? doubled = value?.GetHashCode();
    }

    // 获取值
    public void GettingValues()
    {
        int? value = 42;

        // Value属性(如果为null则抛出异常)
        int val1 = value.Value;

        // GetValueOrDefault
        int val2 = value.GetValueOrDefault();  // 如果为null返回0
        int val3 = value.GetValueOrDefault(10); // 如果为null返回10

        // 空合并运算符
        int val4 = value ?? 0;  // 如果为null返回0
    }

    // 可空表达式
    public void NullableExpressions()
    {
        int? a = 10;
        int? b = 20;
        int? c = null;

        // 算术运算(如果任何操作数为null则结果为null)
        int? sum1 = a + b;  // 30
        int? sum2 = a + c;  // null

        // 比较
        bool? equal = a == b;  // false
        bool? greater = a > c;  // null

        // 逻辑运算符
        bool? and = (a > 5) & (c > 5);  // null
    }

    // 可空结构
    public struct Point
    {
        public int X { get; set; }
        public int Y { get; set; }
    }

    public void NullableStructs()
    {
        Point? point = null;

        if (point.HasValue)
        {
            int x = point.Value.X;
            int y = point.Value.Y;
        }

        // 结构体的空条件运算符
        int? x = point?.X;
    }

    private int? GetNullableValue() => null;
}

可空引用类型

C# 8.0+提供了可空引用类型,使得引用类型在编译时具有空安全性。

#nullable enable

using System;
using System.Collections.Generic;

public class NullableReferenceTypes
{
    // 默认非空
    public string Name { get; set; } = string.Empty;

    // 显式可空
    public string? MiddleName { get; set; }

    // 构造函数
    public NullableReferenceTypes(string name, string? middleName = null)
    {
        Name = name;  // 必须非空
        MiddleName = middleName;  // 可以为空
    }

    // 带有可空参数的方法
    public string FormatName(string firstName, string? middleName,
                            string lastName)
    {
        // 如果未进行空检查使用middleName,编译器会警告
        if (middleName != null)
        {
            return $"{firstName} {middleName} {lastName}";
        }

        return $"{firstName} {lastName}";
    }

    // 可空返回类型
    public string? FindPerson(int id)
    {
        // 可能返回null
        return id > 0 ? "Found" : null;
    }

    // 可空类型的集合
    public void CollectionExamples()
    {
        // 非空字符串列表
        List<string> names = new List<string> { "Alice", "Bob" };

        // 可空字符串列表
        List<string?> nullableNames = new List<string?>
        {
            "Alice", null, "Charlie"
        };

        // 带有可空值的字典
        Dictionary<string, string?> dict =
            new Dictionary<string, string?>
            {
                { "key1", "value1" },
                { "key2", null }
            };
    }

    // 可空泛型类型
    public T? FindById<T>(int id) where T : class
    {
        // 如果未找到则返回null
        return default(T);
    }

    // 非空断言运算符
    public void NotNullAssertion()
    {
        string? maybeName = GetName();

        // 告诉编译器这不会为空(谨慎使用!)
        string name = maybeName!;
    }

    private string? GetName() => null;
}

空合并运算符

空合并运算符提供简洁的空值处理。

#nullable enable

using System;
using System.Collections.Generic;

public class NullCoalescingOperators
{
    // 空合并运算符 (??)
    public string GetDisplayName(string? name)
    {
        // 如果name为null则返回"Unknown"
        return name ?? "Unknown";
    }

    // 链式
    public string GetFirstNonNull(string? a, string? b, string? c)
    {
        return a ?? b ?? c ?? "Default";
    }

    // 空合并赋值运算符 (??=)
    public void NullCoalescingAssignment()
    {
        string? name = null;

        // 仅当为null时赋值
        name ??= "Default Name";  // name现在为"Default Name"

        name ??= "Another Name";  // name保持为"Default Name"
    }

    // 与集合
    public void CollectionCoalescing()
    {
        List<string>? list = null;

        // 如果为null则初始化
        list ??= new List<string>();

        // 添加项目
        list.Add("item");
    }

    // 在属性初始化中
    private List<string>? _items;
    public List<string> Items => _items ??= new List<string>();

    // 复杂表达式
    public string ComplexCoalescing(User? user)
    {
        // 多个层级
        return user?.Profile?.DisplayName ??
               user?.Email ??
               "Guest";
    }

    public class User
    {
        public Profile? Profile { get; set; }
        public string? Email { get; set; }
    }

    public class Profile
    {
        public string? DisplayName { get; set; }
    }
}

空条件运算符

空条件运算符安全地访问可能为空的引用的成员和调用方法。

#nullable enable

using System;
using System.Collections.Generic;

public class NullConditionalOperator
{
    public class Person
    {
        public string Name { get; set; } = string.Empty;
        public Address? Address { get; set; }
        public List<string>? PhoneNumbers { get; set; }
    }

    public class Address
    {
        public string City { get; set; } = string.Empty;
        public string? PostalCode { get; set; }
    }

    // 基本空条件
    public string? GetCity(Person? person)
    {
        // 如果person或Address为null则返回null
        return person?.Address?.City;
    }

    // 与索引器
    public string? GetFirstPhone(Person? person)
    {
        // 如果person、PhoneNumbers为null或空则返回null
        return person?.PhoneNumbers?[0];
    }

    // 与方法调用
    public int? GetNameLength(Person? person)
    {
        return person?.Name.Length;
    }

    // 结合运算符
    public string GetCityOrDefault(Person? person)
    {
        return person?.Address?.City ?? "Unknown";
    }

    // 与数组
    public string? GetFirstItem(string[]? items)
    {
        return items?[0];
    }

    // 与委托
    public void InvokeCallback(Action? callback)
    {
        callback?.Invoke();
    }

    // 与事件
    public event EventHandler? DataChanged;

    public void OnDataChanged()
    {
        DataChanged?.Invoke(this, EventArgs.Empty);
    }

    // 链式多个操作
    public void ChainedOperations(Person? person)
    {
        string? result = person?
            .Address?
            .City
            .ToUpper()
            .Substring(0, 3);
    }
}

空值处理模式

安全处理空值的常见模式。

#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

public class NullHandlingPatterns
{
    // 空对象模式
    public interface ILogger
    {
        void Log(string message);
    }

    public class ConsoleLogger : ILogger
    {
        public void Log(string message) =>
            Console.WriteLine(message);
    }

    public class NullLogger : ILogger
    {
        public void Log(string message) { }
    }

    // 保护子句
    public void ProcessData(string? data)
    {
        if (data == null)
        {
            throw new ArgumentNullException(nameof(data));
        }

        // 此处data非空
        Console.WriteLine(data.Length);
    }

    // 提前返回
    public string FormatData(string? data)
    {
        if (data == null) return string.Empty;

        return data.ToUpper();
    }

    // TryGet模式
    public bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
    {
        // 模拟查找
        if (key == "valid")
        {
            value = "Found";
            return true;
        }

        value = null;
        return false;
    }

    public void UseTryGet()
    {
        if (TryGetValue("valid", out string? result))
        {
            // 此处result保证非空
            Console.WriteLine(result.Length);
        }
    }

    // MemberNotNull属性
    private string? _cachedValue;

    [MemberNotNull(nameof(_cachedValue))]
    private void EnsureCacheLoaded()
    {
        _cachedValue ??= LoadValue();
    }

    public void UseCache()
    {
        EnsureCacheLoaded();
        // _cachedValue保证非空
        Console.WriteLine(_cachedValue.Length);
    }

    // NotNullIfNotNull属性
    [return: NotNullIfNotNull(nameof(input))]
    public string? Transform(string? input)
    {
        return input?.ToUpper();
    }

    // AllowNull和DisallowNull
    private string _name = string.Empty;

    [AllowNull]
    public string Name
    {
        get => _name;
        set => _name = value ?? string.Empty;
    }

    private string LoadValue() => "loaded";
}

可空注解

为编译器提供额外空状态信息的属性。

#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;

public class NullableAnnotations
{
    // NotNull - 参数在方法返回时不会为空
    public void Initialize([NotNull] ref string? value)
    {
        value ??= "default";
    }

    // MaybeNull - 尽管返回类型非空,但可能返回null
    [return: MaybeNull]
    public T GetDefaultValue<T>()
    {
        return default(T);
    }

    // DoesNotReturn - 方法从不正常返回
    [DoesNotReturn]
    public void ThrowError(string message)
    {
        throw new System.InvalidOperationException(message);
    }

    public void UseDoesNotReturn(string? value)
    {
        if (value == null)
        {
            ThrowError("Value is null");
        }

        // 编译器知道此处value非空
        System.Console.WriteLine(value.Length);
    }

    // MemberNotNullWhen
    private string? _data;

    [MemberNotNullWhen(true, nameof(_data))]
    private bool IsDataLoaded()
    {
        return _data != null;
    }

    public void UseData()
    {
        if (IsDataLoaded())
        {
            // _data保证非空
            System.Console.WriteLine(_data.Length);
        }
    }

    // NotNullIfNotNull
    [return: NotNullIfNotNull(nameof(source))]
    public string? ProcessString(string? source)
    {
        return source?.Trim();
    }
}

迁移策略

在现有代码中采用可空引用类型的策略。

// 渐进迁移方法

// 文件1:保持可空性禁用
#nullable disable

public class LegacyClass
{
    public string Name { get; set; }  // 隐式可空
}

#nullable restore

// 文件2:为新代码启用可空性
#nullable enable

public class ModernClass
{
    public string Name { get; set; } = string.Empty;  // 非空
    public string? MiddleName { get; set; }  // 显式可空
}

#nullable restore

// 文件3:迁移期间抑制警告
#nullable enable

public class MigratingClass
{
    // 抑制特定警告
    #pragma warning disable CS8618
    public string Name { get; set; }
    #pragma warning restore CS8618

    // 或临时使用!运算符
    public string GetName() => Name!;
}

与外部库协作

处理未使用可空注解的库时的可空性。

#nullable enable

using System;

public class ExternalLibraryHandling
{
    // 假设外部库方法
    // public string GetData() { ... }

    // 防御性编程
    public void UseExternalLibrary()
    {
        // 将返回值视为可能为空
        string? data = GetExternalData();

        if (data != null)
        {
            ProcessData(data);
        }
    }

    // 创建包装器
    public string? GetExternalDataSafe()
    {
        string result = GetExternalData();
        return string.IsNullOrEmpty(result) ? null : result;
    }

    // 安全扩展方法
    public static class StringExtensions
    {
        public static string OrEmpty(this string? value)
        {
            return value ?? string.Empty;
        }

        public static bool IsNullOrEmpty(
            [NotNullWhen(false)] this string? value)
        {
            return string.IsNullOrEmpty(value);
        }
    }

    private string GetExternalData() => "data";
    private void ProcessData(string data) { }
}

最佳实践

  1. 在新项目中从一开始就启用可空引用类型
  2. 对可选字符串参数和返回值使用 string?
  3. 在构造函数中或用默认值初始化非空属性
  4. 优先使用空合并运算符而非显式空检查
  5. TryGet 模式方法使用 NotNullWhen 属性
  6. 早期用保护子句验证参数
  7. 使用 ThrowIfNull 助手进行参数验证
  8. 除非绝对确定值非空,否则避免使用 ! 运算符
  9. 利用编译器警告发现潜在的空引用问题
  10. 在公共API中记录可空性预期

常见陷阱

  1. 使用 ! 运算符抑制警告而不确保非空
  2. 未在所有构造函数中初始化非空属性
  3. 从声明为非空返回类型的方法返回null
  4. 在解引用可空引用前忘记检查空值
  5. 混合可空和非空上下文导致混淆
  6. 过度使用可空类型而默认值已足够
  7. 未通过方法链传播空检查
  8. 假设外部库方法尊重可空性
  9. 迁移期间忽略可空警告
  10. 过度使用 #pragma warning disable

何时使用可空类型

当需要时使用可空类型:

  • 表示无值状态,区别于默认值
  • 数据库可为NULL的字段映射到C#属性
  • 方法和构造函数中的可选参数
  • 可能包含或不包含数据的API响应
  • 在编译时防止空引用异常
  • 将旧代码逐步迁移到空安全模式
  • 关于哪些值可以为空的清晰契约
  • 可选配置值的类型安全处理
  • 与可能返回null的外部API集成
  • 带有Option/Maybe类型的函数式编程模式

资源