Mar*_*ari 4 c# .net-core c#-8.0 nullable-reference-types .net-core-3.0
当我遇到以下情况时,最好采取什么方法?
\n上下文是一个启用了最新的可为空检查的.NET Core 3.x 应用程序,以及一个带有ref关键字的方法。真正的代码更复杂,但更简单的版本可能是这样的:
private static bool _initialized = false;\nprivate static object _initializationLock = new object();\nprivate static MyClass _initializationTarget; //suggestion to mark as nullable\n\npublic MyClass GetInstance()\n{\n return LazyInitializer.EnsureInitialized(\n ref _initializationTarget,\n ref _initialized,\n ref _initializationLock,\n () => new MyClass()\n );\n}\nRun Code Online (Sandbox Code Playgroud)\n该EnsureInitialized()方法采用位于开头_initializationTarget的引用null,因此将其标记为可为空似乎是正确的调整。然而,同样的方法可以确保变量被正确填充。
我找不到比下面的\xe2\x80\x94更好的模式,但这真的是最好的吗?
\nprivate static bool _initialized = false;\nprivate static object _initializationLock = new object();\nprivate static MyClass? _initializationTarget; //marked as nullable\n\npublic MyClass GetInstance()\n{\n //the return value must also nullable\n MyClass? inst = LazyInitializer.EnsureInitialized(\n ref _initializationTarget,\n ref _initialized,\n ref _initializationLock,\n () => new MyClass()\n );\n\n return inst!; //null-forgive here\n}\nRun Code Online (Sandbox Code Playgroud)\n
TL;DR:如果删除initialized参数,则将EnsureInitialized()保证返回的对象是[NotNull]\xe2\x80\x94,即使你传入一个统一的MyClass?引用 \xe2\x80\x94,因此不需要使用null - 宽容运算符( !)。
这是一个很好的问题。您的具体示例的答案非常简单,但我也想借此机会解决您有关如何使用C# 8.0 可空引用类型处理参数的一般问题。ref这样做不仅有助于解决类似的其他场景,而且还可以解释为什么您的特定示例的解决方案有效。
虽然文档EnsureInitialized()没有完全清楚地说明这一点,但您调用的特定重载适用于您可能需要目标的情况null。也就是说,如果您要传递参数的值true,initialized那么它将返回null。这个条件就是为什么它必须返回可为空类型的原因。
由于您不想允许null值\xe2\x80\x94 并且不使用值类型\xe2\x80\x94,因此您只需删除该initialized参数即可。您仍然需要将您声明为_initializationTarget可为空,因为它没有在您的构造函数中初始化。然而,这种重载向编译器保证参数在运行后将target不再存在:nullEnsureInitialized()
private static object _initializationLock = new object();\nprivate static MyClass? _initializationTarget; //marked as nullable\n\npublic MyClass GetInstance() =>\n LazyInitializer.EnsureInitialized(\n ref _initializationTarget,\n ref _initializationLock,\n () => new MyClass()\n );\nRun Code Online (Sandbox Code Playgroud)\n请注意,虽然它仍然传入 null(able) MyClass?,但它会自信地返回 a MyClass,而无需求助于null-forgiving 运算符( !)。
当您进一步深入研究 C# 8.0 的可空引用类型时,您将发现 Roslyn 静态流分析中的许多差距,这些差距无法通过仅使用 and?运算!符来消除歧义。幸运的是,微软预见到了这个问题,为我们提供了多种可以用来提供编译器提示的属性。
这是一个说明一般问题的基本示例:
\npublic void EnsureNotNull(ref Object? input) => input ??= new Object();\nRun Code Online (Sandbox Code Playgroud)\n如果您使用以下代码调用此方法,您将收到警告CS8602:
Object? object = null;\nEnsureNotNull(ref object);\n_ = object.ToString(); //CS8602; Dereference of a possibly null reference\nRun Code Online (Sandbox Code Playgroud)\n[NotNull]但是,您可以通过将提示应用于参数来缓解这种情况input:
public void EnsureNotNull([NotNull]ref Object? input) => input ??= new Object();\nRun Code Online (Sandbox Code Playgroud)\n现在,任何对objectafterEnsureNotNull()被调用的引用都将被(编译器)知道不是null。
EnsureInitialized()超载如果您考虑到上述内容来评估源代码LazyInitializer,那么您的特定问题的答案就会变得更加清晰。您调用的重载标记为target,[AllowNull]这相当于返回MyClass?:
public static T EnsureInitialized<T>([AllowNull] ref T target, ref bool initialized, [NotNull] ref object? syncLock, Func<T> valueFactory) => \xe2\x80\xa6\nRun Code Online (Sandbox Code Playgroud)\n相比之下,我推荐的重载实现了[NotNull]上面讨论的属性,这相当于返回MyClass:
public static T EnsureInitialized<T>([NotNull] ref T? target, [NotNull] ref object? syncLock, Func<T> valueFactory) where T : class => \xe2\x80\xa6\nRun Code Online (Sandbox Code Playgroud)\n如果您评估实际逻辑,您会发现它们基本上是相同的,除了前者包含一个针对initialized\ truexe2\x80\x94 场景的转义子句,因此允许target潜在地保留null。
然而,由于您知道您正在使用一个类并且不需要一个null值,因此后一个重载是最佳选择,并且实现了我对您的一般问题的回答中概述的确切实践。
| 归档时间: |
|
| 查看次数: |
1250 次 |
| 最近记录: |