【问题标题】:How to implicitly convert nullable type to non-nullable type如何将可空类型隐式转换为不可空类型
【发布时间】:2022-01-22 13:52:40
【问题描述】:

我有一个可以为空的 c# 10 .net 6 项目,其扩展方法为 ThrowIfNull

using System;
using System.Runtime.CompilerServices;

#nullable enable
public static class NullExtensions
{
    public static T ThrowIfNull<T>(
        this T? argument, 
        string? message = default, 
        [CallerArgumentExpression("argument")] string? paramName = default
    )
    {
        if (argument is null)
        {
            throw new ArgumentNullException(paramName, message);
        }
        else
        {
            return argument;
        }
    }
}

扩展方法将string?隐式转换为string,但它不适用于int?bool?等其他原始类型

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        
        string? foo = "foo";
        string nonNullableFoo = foo.ThrowIfNull();  // success from "string?" to "string"
        Console.WriteLine(nonNullableFoo);
        
        bool? baz = true;
        bool nonNullableBaz = baz.ThrowIfNull();    // success from "string?" to "string"
        Console.WriteLine(nonNullableFoo);
        
        int? bar = 2;
        int nonNullableBar = bar.ThrowIfNull(); // error: Cannot implicitly convert type 'int?' to 'int'
        Console.WriteLine(nonNullableBar);
    }
}

如何使扩展隐式转换int?bool?

这是完整的 dotnet fiddle https://dotnetfiddle.net/LiQ8NL

【问题讨论】:

    标签: c# .net


    【解决方案1】:
    使用空合并运算符??

    要将可空值分配给非空变量,请考虑以下代码:

    int? value = 28;
    int result = value ?? -1;
    Console.WriteLine($"The result is {result}");
    
    • 输出:The result is 28
    int? value = null;
    int result = value ?? -1;
    Console.WriteLine($"The result is {result}");
    
    • 输出:The result is -1

    编辑代码

    重新排列代码如下。因此bool?可以隐式使用该类型:

    int? bar = 2;
    int nonNullableBar = bar ?? -1; 
    Console.WriteLine(nonNullableBar);
    
    bool? baz = true;
    bool nonNullableBaz = false;
            
    if (baz == true){ 
        nonNullableBaz = true;
    } 
    else if(baz == false){ 
        nonNullableBaz = false;
    } 
    else {
        /* Something */
    }
    

    参考文献

    【讨论】:

    • 这将一个可空值(int?)显式转换为相同的可空值(int?),这不是我想要的。我想将一个可空值(int?)隐式转换为不可空值(int)
    • 这是我第一次遇到这种用法。这就是您正在寻找的答案。
    • 分配像 -1 这样的魔法值来表示 null 并不是一个好习惯。
    • 你能解释一下原因吗?
    • 我现在快速回顾了关于这个主题的良好实践,但没有遇到任何关于劣势的提及。我会进一步调查此事。
    【解决方案2】:

    您可以通过为不可为空的引用类型提供一种扩展方法和为非托管(例如 int、bool、...)类型提供另一种扩展方法来实现您的目标。请注意,非托管类型需要强制转换。

    public static class NullExtensions
    {
        public static T ThrowIfNull<T>(
            this T? argument,
            string? message = default,
            [CallerArgumentExpression("argument")] string? paramName = default
        ) where T : notnull
        {
            if (argument is null)
            {
                throw new ArgumentNullException(paramName, message);
            }
            else
            {
                return argument;
            }
        }
    
        public static T ThrowIfNull<T>(
            this T? argument,
            string? message = default,
            [CallerArgumentExpression("argument")] string? paramName = default
        ) where T : unmanaged
        {
            if (argument is null)
            {
                throw new ArgumentNullException(paramName, message);
            }
            else
            {
                return (T)argument;
            }
        }
    }
    

    这样使用:

    int? foo = 42;
    int bar = foo.ThrowIfNull();
    Console.WriteLine(bar);
    
    string? baz = "Hello";
    string quus = baz.ThrowIfNull();
    Console.WriteLine(quus);
    
    // Comment out either this
    baz = null;
    quus = baz.ThrowIfNull();
    // Or this
    foo = null;
    bar = foo.ThrowIfNull();
    

    【讨论】:

    • TIL:notnullmanaged。接受这个作为答案。谢谢!供将来参考的完整小提琴在这里dotnetfiddle.net/fjwIo7
    【解决方案3】:

    在 MS dotnet docs https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters 中找到一种可能的选项

    约束 "where T : struct" 类型参数必须是不可为空的值类型

    同样根据这个答案https://stackoverflow.com/a/8745492/2877168 不可能使string? 和所有其他值类型工作。所以唯一的办法就是定义两个扩展方法。这是适用于string?int?bool? 等的更新版本

    public static class NullExtensions
    {
        public static T ThrowIfNull<T>(this T? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
            where T : struct
        {
            if (argument is null)
                throw new ArgumentNullException(paramName, message);
            return (T)argument;
        }
    
        public static string ThrowIfNull(this string? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
        {
            if (argument is null)
                throw new ArgumentNullException(paramName, message);
            return argument;
        }
    }
    

    此版本的工作版本位于 https://dotnetfiddle.net/uBX1w6

    【讨论】:

      猜你喜欢
      • 2019-07-18
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      • 2019-12-03
      • 2012-04-21
      • 1970-01-01
      • 2014-11-21
      • 2010-10-21
      相关资源
      最近更新 更多