【问题标题】:Cannot convert null to struct when defining two nullable implicit operators定义两个可为空的隐式运算符时无法将 null 转换为 struct
【发布时间】:2018-09-20 15:02:22
【问题描述】:

以下内容无法编译:

public struct Foo
{
    public static implicit operator Foo(string bar)
    {
        return new Foo();
    }

    public static implicit operator Foo(long? bar)
    {
        return new Foo();
    }

    public static void Test()
    {
        Foo bar = 0;
        Foo bar2 = (long?)null;
        Foo bar3 = "";
        Foo bar4 = null; // Cannot convert null to 'Foo' because it is a non-nullable value type
    }
}

'Foo bar4 = null' 失败,大概是因为编译器不知道要使用哪个隐式运算符,因为将运算符从 long? to long 会导致该行编译,但 'Foo bar2 = (long?)null' 会失败(需要显式转换)。

我的问题是;有没有办法让 'Foo bar4 = null' 也能正常工作,或者这只是语言的限制(例如,我不能添加一个 'null' 运算符或告诉它使用哪个运算符来表示 null)?

我意识到我可以将结构更改为类,但我不希望它为 null,我希望能够让 null 创建它的实例。

编辑:我应该补充一点,我知道有很多方法可以解决这个问题,但是由于 '= null'(本质上是执行 'new Foo()')只适用于其中一个隐式运算符,我只是想知道是否有可能让它仍然与它们一起工作(我觉得语言中应该有一种方法可以做到这一点 - 现在或将来,不是吗?)。

【问题讨论】:

  • Is there a way to make 'Foo bar4 = null' work as well
  • 您的结构不可为空,因为您引入了一个强制转换,可以将空值强制转换为该类型。事实上,struct 本身从不为空,但对它的引用可能是,但仅当它们的类型为Nullable<Foo>时。
  • 是的,如果您希望值类型为 null,请将其设置为 Nullable<>。如果您想将其视为new Foo()(就像您在隐式运算符中使用的那样),请使用Foo bar4 = default(Foo);。通过这种方式,如果是 class,您将获得 null;如果是 struct,您将获得 new Foo()
  • 对我来说没有多大意义。如果在编写代码期间您知道需要将默认值(或您为 (long?)null 分配的任何值)分配给 Foo,您可以直接分配它。
  • 你可以编写一个带有object 参数的第三重载,你可以检查它是否为空。如果分配了任何现有对象,则抛出异常。缺点:如果您分配对象(显然),编译器不会警告您。像这样的东西:public static implicit operator Foo(object bar) { if (bar != null) throw new Exception(); return new Foo(); }

标签: c# struct null operator-keyword implicit-conversion


【解决方案1】:

我的问题是;有没有办法让 'Foo bar4 = null' 也能正常工作,或者这只是语言的限制(例如,我不能添加一个 'null' 运算符或告诉它使用哪个运算符来表示 null)?

基于这个问题和你的编辑,你基本上是在问

如果我添加另一个隐式转换运算符,为什么Foo bar4 = null 不会编译?

答案很简单。 null 没有任何上下文是无类型的,因此编译器不知道要使用哪个运算符。为什么?支持该语言的重载解析算法不会检查您尝试将 null 分配给的事物的类型,因此它不知道您打算使用哪个运算符。

您可能会争辩说可以修改未来的语言规范以进行这种额外的分析,但团队可能认为不值得付出努力,否则这将是一个突破性的变化。

在这种情况下,您能做的最好的事情就是避免强制转换。根据您的 C# 级别,您可以执行以下任一操作:

  • Foo bar4 = default;
  • Foo bar4 = default(Foo);

这会产生一个可用的Foo。两个默认表达式和new Foo() 都是等价的。它们都会导致一个结构体的所有字段都归零。

有关详细信息,您可以查看 C# 编程指南的 default value expressions 部分。

最后,虽然您可以使用将null 转换为结构的单个隐式转换运算符,但这并不意味着您应该。阅读该代码但不了解演员阵容的人可能会质疑他们的理智几分钟。如果可以的话,最好不要偏离惯用代码。

【讨论】:

  • 出于好奇,default<Foo> 是否等同于 new Foo(),因为结构总是被赋予一个默认的无参数构造函数,它什么都不做?
  • 是的,它们是等价的。隐式构造函数和两个默认表达式导致结构的所有字段都被清零。
  • 使用默认值是一个不错的选择。我想知道为什么在这两个运算符的情况下,当一个工作正常时,它是故意的限制。你有参考资料吗?
  • 如果您要分配一个“可能”为空的值,您可以执行Foo bar4 = valueThatMightBeNull ?? default<Foo>; 之类的操作,但我不知道这是否是您正在寻找的...
  • @Kit 对不起,我的意思是参考为什么它是语言的限制,我不能有两个这样(或更多)的运算符:)
【解决方案2】:

您的结构不可为空,因为您引入了一个强制转换,可以将空值强制转换为该类型。演员表本身只是一个不会改变类型的成员语义

事实上,struct 本身从不为空,但是对它的引用可能是,但仅当它们的类型为 Nullable<Foo> 时。所以bar4需要是Foo?类型,和Nullable<Foo>一样。

【讨论】:

    【解决方案3】:

    如果我没记错的话,一个结构永远不能被赋值为 null,因为它是一个值类型,类似于 intboolDateTime

    但是,您可以像这样使用 Nullable
    Foo? bar4 = null;

    Nullable<Foo> bar4 = null;

    确保您像对待任何其他 Nullable 一样对待它,并在引用 bar4.Value 之前检查 .HasValue 以避免美妙的 NullReferenceException。

    【讨论】:

    • 抱歉,是的 - 我不想将它设置为 null,我希望 null 本质上创建 Foo - 不做 Foo?。这与两个隐式运算符一起使用。
    猜你喜欢
    • 2011-09-09
    • 1970-01-01
    • 2012-02-12
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 2016-05-19
    • 2017-09-18
    • 2012-02-11
    相关资源
    最近更新 更多