如何暗示C#8.0可为空的参考系统使用反射来初始化属性

Joh*_*lle 9 c# entity-framework entity-framework-core c#-8.0 nullable-reference-types

当我尝试将Entity Framework Core与C#8.0中的新可为空的引用类型一起使用时,遇到了一个有趣的问题。

实体框架(各种样式)允许我声明我从未初始化过的DBSet属性。例如:

    public class ApplicationDbContext : IdentityDbContext
      {
    #pragma warning disable nullable
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
          : base(options)
        { }
    #pragma warning restore nullable

        public DbSet<Probe> Probes { get; set; }
        public DbSet<ProbeUnitTest> ProbeUnitTests { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

DbContext构造函数反映类型并初始化所有DbSet属性,因此我知道,根据构造函数的结论,所有属性都将不为空。如果我省略#pragma,则会收到预期的警告,因为我的代码未初始化这些属性。

 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'Probes' is uninitialized.
 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'ProbeUnitTests' is uninitialized.
Run Code Online (Sandbox Code Playgroud)

当我只想通知编译器某个属性不会为null时,关闭警告似乎是一种钝器。

如果事实证明,我可以像这样愚弄编译器:

 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'Probes' is uninitialized.
 Data\ApplicationDbContext.cs(10,12,10,32): warning CS8618: Non-nullable property 'ProbeUnitTests' is uninitialized.
Run Code Online (Sandbox Code Playgroud)

该代码的优点是范围很窄-仅适用于特定属性的初始化,不会抑制其他警告。缺点是这是胡扯的代码,因为为其本身分配属性实际上不应该执行任何操作。

是否有首选的惯用法通知编译器它只是不知道该属性已被初始化?

Joã*_*ela 36

Microsoft对于DbContext 和 DbSet上下文中的可空引用有2 条建议

1 - “在旧版本的 EF Core 上”

在上下文类型上拥​​有未初始化的 DbSet 属性的常见做法也是有问题的,因为编译器现在将为它们发出警告。可以按如下方式修复此问题:

    // ...
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<Order> Orders => Set<Order>();
    // ...
Run Code Online (Sandbox Code Playgroud)

2 - 推荐解决方案

另一个是@dave-m 的回答

另一种策略是使用不可为 null 的自动属性,但将它们初始化为 null,并使用 null 宽容运算符 (!) 来消除编译器警告。

public DbSet<Customer> Customers { get; set; } = null!;
public DbSet<Order> Orders { get; set; } = null!;
Run Code Online (Sandbox Code Playgroud)

第二个现在是首选解决方案,因为(根据文档)第一个仅适用于旧版本的 EF Core。

  • 第一个是net 6的最佳解决方案。这是MS建议的解决方案。命名空间是正确的。Set&lt;&gt;() 是 DbContext 的一个方法,它不是一个类:“=&gt;” (2认同)

Dav*_*e M 13

每当您要告诉编译器有关可空引用类型时“关闭,我知道我在做什么”时,请使用!运算符。您可以这样声明属性来解决问题:

public DbSet<Probe> Probes { get; set; } = null!;
public DbSet<ProbeUnitTest> ProbeUnitTests { get; set; } = null!;
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢你的成语。断言“我知道null不为null”似乎有些奇怪,但它可以工作。这个习语有一些优点。1)它对于所描述的属性是本地的,2)它使用的是null原谅运算符,而这正是我要执行的操作,3)它是如此无意义(断言null不为null),看起来就像是hack这是。感谢您的深刻见解。 (3认同)
  • @BOB问题具体是当您知道该属性将通过编译器不知道的某种机制自动初始化时该怎么做。在这种情况下,实体框架会自动初始化这些属性(可能使用反射),因此您永远不会获得 NRE。然而编译器不知道这一点。所以“分配”null!实际上根本没有做任何事情,它只是与编译器通信的一种方式,我们知道这个属性将被“神奇地”初始化。 (3认同)
  • 这 !操作员不执行任何操作。它无非是给编译器的一个提示。这里没有“解开空值”。将 null 分配给属性是完全有效的,不会导致异常。它是如何编译的?整整点好了!就是对编译器说:“嘿,我知道这看起来不对,但无论如何请编译它” (2认同)