【问题标题】:C#9 + .NET5, nullable enabled: is it possible to use the object initialiser with non nullable properties without using null!?C#9 + .NET5,可以为空:是否可以在不使用 null 的情况下使用具有不可为空属性的对象初始化程序?
【发布时间】:2020-11-20 14:54:26
【问题描述】:

我正在使用 NET 5.0 测试 c# 9.0,但有些东西我不明白。我启用了<nullable>enable</nullable>

如果我写一个类

public class Potato {
    public string Colour { get; set;  }

    public string Flavour { get; set; }
}

我收到警告 CS8618: Non-nullable propery 'Colour' must contain a non-null value when existing constructor. Consider declaring the propery as nullable. 我不想声明它可以为空,这就是我启用标签的原因...

然后,我声明构造函数:

public class Potato
{
    public Potato(string colour, string flavour)
    {
        this.Colour = colour;
        this.Flavour = flavour;
    }

    public string Colour { get; set; }

    public string Flavour { get; set; }
}

到目前为止一切顺利。现在我想使用对象初始化器创建一个Potato 对象:

var sweetPotato = new Potato { 
    Colour = "orange",
    Flavour = "tasty",
};

现在我收到了CS7036: There is no argument given that corresponds to the required formal parameter 'colour' of 'Potato.Potato(string, object) 的投诉。因此,它尝试使用我明确声明但我没有引入参数的构造函数。我应该这样做:

var sweetPotato = new Potato(
    colour: "orange",
    flavour: "tasty");

但是这里我没有使用对象初始化器。

另一种选择是将类声明为

public class Potato
{
    public string Colour { get; set; } = null!;

    public string Flavour { get; set; } = null!;
}

使用 null! 元素,但这允许一些奇怪的事情,例如:

var sweetPotato = new Potato()
{
    Colour = "orange",
};

我认为这非常奇怪。我声明了 2 个不可为空的属性,并且感谢 null! 我可以使用一个属性加载对象,但不能加载第二个属性。这打破了我的可空标签!

那么我的问题是:如果启用了可空标记,是否可以使用具有不可空属性的对象初始化器?

如果不是,我认为对于具有不可为空属性的类,不涉及任何null!,默认构造函数不应该是空的,而是具有所有参数的,并且应该可以初始化具有对象初始化程序形式的对象,只要所有不可为空的属性都已初始化。

【问题讨论】:

  • 您所做的是使所有内容都可空,除非使用? 明确标记为可空。如果您想要旧的行为,请删除 nullable 设置。
  • 至于剩下的问题,大部分都是尝试使用无效的语法——如果你指定了一个构造函数,你必须使用它并提供所有参数。这不受nullable 标签的影响。您可以对所有类使用对象初始化器语法。但是,如果该类没有默认构造函数,则您必须使用其中一个构造函数
  • 顺便说一句,您尝试的代码看起来很像record。除了将匿名类型存储为不透明的object 的相当奇怪的尝试之外,以后没有人可以使用它。您不能使用.Flavour.Tasty,因为object 没有Tasty 属性。您也不能将其转换为特定类型,因为它包含匿名类型。您应该将Potato 设为泛型类或将dynamic 用于Flavour
  • 感谢您的回答@PanagiotisKanavos。 1)当我的意思是“我不想宣布它可以为空”时,我写了“不可为空”。我修好了它。对困惑感到抱歉。 2)对象事物是不相关的。我用一个字符串改变了它。 3) 记录可以是可变的,但它们主要是为了更好地支持不可变数据模型而构建的。就我而言,我需要更改属性。 4)我的问题的核心仍然不清楚:如何在具有不可为空元素的类上使用对象初始化器语法?如果我不指定构造函数,我会得到 CS8614,如果我指定它,我必须使用它...
  • 简短回答是否定的,该语言不支持所需的初始化属性。您可能希望密切关注此提案:github.com/dotnet/csharplang/issues/3630

标签: .net-5 c#-9.0


【解决方案1】:

我知道这是一个老问题,但这里是全面的答案:

  1. 通过添加<nullable>true<nullable> 标记,您可以使所有可空值都必须用? 标识
  2. 因为 1.,并且由于您没有将 ? 添加到它们的类型中,所以 ColourFlavour 都不能是 NULL
  3. 由于 2.,您必须提供一个构造函数来初始化 ColourFlavour 的值 - 默认的无参数构造函数将不起作用,因为它本身什么都不做,并且会返回一个带有 NULL 的对象ColourFlavour,因为这是它们类型的默认值,您要求编译器不允许这样做

下面的sn-p:

var sweetPotato = new Potato { 
    Colour = "orange",
    Flavour = "tasty",
};

使用默认的无参数构造函数,因此不能使用。它的简写等同于:

var sweetPotato = new Potato(); // sweetPotato has Colour and Flavour NULL
sweetPotato.Colour = "orange";  // sweetPotato still has Flavour NULL
sweetPotato.Flavour = "tasty";

如您所见,它会首先创建对象(使用默认值),然后再分配值。但是由于您不允许NULL 的默认值,它会失败

这就是为什么如果你不允许它们为空,你必须添加一个初始化它们的构造函数,并且你必须使用那个构造函数

如果它们的类型是具有不同默认值的原语(例如 intdouble),则不适用。在这种情况下,默认的无参数构造函数将起作用。但是通过使用默认值为NULL 的类型(如string 或任何object 派生类型),您可以通过添加? 使它们为空,或者您必须使用将它们初始化为其他类型的构造函数比NULL.

【讨论】:

  • 是的,我明白了,但关键是我看不到使用具有不可为空元素的类的默认无参数构造函数的原因。如果需要ColourFlavour 并且有一个带有这些参数的构造函数,我猜这个对象初始化器的默认构造函数应该使用这个构造函数。使用无参数有点矛盾!
  • @xavier - 但你主动告诉他不要通过不给他参数来使用带参数的构造函数 - 就像我说的var sweetPotato = new Potato { ... }; 意味着使用无参数构造函数然后赋值。要使用非无参数构造函数,您需要给他参数,即:var sweetPotato = new Potato("orange", "tasty");
【解决方案2】:

string 的默认值为 null。由于您的属性声明没有定义默认值,它们将默认为default,在string 的情况下恰好是null。问题是您没有将类型声明为nullable

解决办法是分配一个默认值,比如String.Empty

public class Potato
{
    public string Colour { get; set; } = String.Empty;

    public string Flavour { get; set; } = String.Empty;
}

【讨论】:

  • 感谢您的回答。如果我将一个字段声明为不可为空,我会看到编写一个空字符串,例如“作弊”。如果我想强制这个属性有一个值,“空”并不是一个真正的值...... :-(
猜你喜欢
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 1970-01-01
  • 2017-09-06
相关资源
最近更新 更多