泛型类型值和'default'关键字的'=='运算符的行为是什么?

Jor*_*vão 13 c#

问题的第1部分:在下面的代码中,为什么value == default编译良好,而其他替代方法却没有?

bool MyEqual<T>(T value)
{
    T value2 = default;
    if (value == value2)     // Error: Operator '==' cannot be applied to operands of type 'T' and 'T'
        return true;
    if (value == default(T)) // Error: Operator '==' cannot be applied to operands of type 'T' and 'T'
        return true;
    if (value == default)    // No error
        return true;
    return false;
}
Run Code Online (Sandbox Code Playgroud)

问题的第2部分:在下面的代码中,为什么前三幅显示而后三幅false显示true

bool MyEqual<T>(T value)
{
    if (value == default)
        return true;
    return false;
}

Console.WriteLine($"{MyEqual<int>(0)}");                  // False
Console.WriteLine($"{MyEqual<int>(default)}");            // False
Console.WriteLine($"{MyEqual<int>(default(int))}");       // False

Console.WriteLine($"{MyEqual<string>(null)}");            // True
Console.WriteLine($"{MyEqual<string>(default)}");         // True
Console.WriteLine($"{MyEqual<string>(default(string))}"); // True
Run Code Online (Sandbox Code Playgroud)

概括起来:表达式的行为是value == default什么?

编辑:请不要将其标记为该另一个问题的重复项,因为那是另一个不同的情况,value == default(T)而不是value == defaultdefault(object)是什么?用C#做? 另外,我的问题是关于使用“ ==”运算符时的奇怪行为,正如我上面解释的那样。

Mik*_*bel 6

==操作数类型是泛型参数的上下文中,value == default似乎发出等效的 IL 到value == null,它总是评估false为不可为空的值类型操作数。

鉴于:

static bool IsDefault<T>(T value) => value == default;
static bool IsNull<T>(T value) => value == null;
Run Code Online (Sandbox Code Playgroud)

我们得到 IL:

.method private hidebysig static 
    bool IsDefault<T> (
        !!T 'value'
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq
    IL_0009: ret
} // end of method C::IsDefault

.method private hidebysig static 
    bool IsNull<T> (
        !!T 'value'
    ) cil managed 
{
    // Method begins at RVA 0x205b
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq
    IL_0009: ret
} // end of method C::IsNull
Run Code Online (Sandbox Code Playgroud)

你可以原谅你发现这令人惊讶。这意味着,例如,当T绑定到不可为空的值类型(如 )时int,表达式的value == default计算false结果为0。与此相反,内联表达0 == default,其评估对true

Console.WriteLine(IsDefault<int>(0));     // False
Console.WriteLine(IsNull<int>(0));        // False
Console.WriteLine(IsDefault<int?>(null)); // True
Console.WriteLine(IsNull<int?>(null));    // True
Console.WriteLine(IsDefault<int?>(0));    // False
Console.WriteLine(IsNull<int?>(0));       // False
Run Code Online (Sandbox Code Playgroud)

因此,显然,对于value不受约束的泛型参数类型的 a,表达式value == defaultvalue == default(T)等价的。如果合法,后者将(大概)评估true值是否为空、假或“归零”值类型(例如,所有组成值也是默认值的值类型)。

至于为什么value == default(T)不编译,答案很简单:编译器不知道如何评估==编译时未知的类型。如果您要添加约束where T : class,那么编译器至少可以执行引用比较。但是只要T可能是原始类型、自定义值类型或引用类型,编译器就不知道如何发出比较。给定的实例化的正确实现T可能是内置的原始比较、op_Equality重载或引用比较。更重要的是,它可能根本不受支持。 真正造成问题的是最后一种可能性。

虽然 C#/.NET 工程师可以想出一种方法,将正确比较的计算推迟到运行时,但这也意味着在运算符根本不适用的情况下,将编译时错误换成运行时异常,而我不要觉得那笔交易很吸引人