gpt4 book ai didi

C# 可空 : Make nullchecking dependent on another property/variable

转载 作者:行者123 更新时间:2023-12-04 12:35:39 32 4
gpt4 key购买 nike

我刚刚在我的 .net core 3.1 项目中启用了空检查。

问题是我有一个响应类

public class DecryptResponse
{
public DecryptStatus Status { get; set; }

//This is the attribute in question
[NotNullWhen(Status==DecryptStatus.Ok)]
public Stream? Stream { get; set; }

public string? ErrorMessage { get; set; }
}

public enum DecryptStatus
{
Ok,
InvalidData,
KeyChecksumFailure,
NoData,
UnhandledError
}

以上用于这种情况,其中 Verify方法不允许空值。

但我知道流不为空,因为 DecryptStatus==Ok
if (decryptResponse.Status != DecryptStatus.Ok)
return (decryptResponse, null);

var verifyResponse = Verify(customerId, decryptResponse.Stream);
return (decryptResponse, verifyResponse);

是否有任何标签允许这种逻辑,或者是否需要对代码进行重大重写?

最佳答案

.NET 5+ 的简短回答:使用新的 MemberNotNullMemberNotNullWhen属性。
这些是在 .NET 5.0 和 C# 9.0 中引入的。 (你也可以使用 init 属性)。
问题在于 MemberNotNullWhen仅适用于 Boolean -typed 属性,但您可以添加一个新属性,该属性根据其他一些条件返回 true/false - 在您的情况下,这意味着如下所示:

public class DecryptResponse
{
public DecryptStatus Status { get; init; }

private Boolean StatusIsOK => this.Status == DecryptStatus.Ok;

[MemberNotNullWhen(true, nameof(StatusIsOK))]
public Stream? Stream { get; init; }

[MemberNotNullWhen(false, nameof(StatusIsOK))]
public string? ErrorMessage { get; init; }
}
当然,这种方法有一个巨大的漏洞:编译器无法验证 Status , Stream , 和 ErrorMessage设置正确。没有什么可以阻止您的程序执行 return new DecryptResponse();无需设置任何属性。这意味着对象处于无效状态。
您可能认为这不是问题,但如果您需要不断地向类添加或删除新属性,最终您会粗心大意而忘记设置所需的必需属性,然后您的程序就会爆炸。

长答案(用于编写更好的类):
更新到 .NET 5 + C# 9 的替代方法,并避免上述无效状态问题,请使用更好的类设计!
我不喜欢带有 get; set; 的可变 DTO在每个属性上,因为如果没有主构造函数,就无法保证对象实例将被正确初始化。
如果我在没有 MemberNotNullWhen 的情况下为较旧的 .NET 平台编写代码,然后我会有这个:
public abstract class DecryptResponse
{
public static implicit DecryptResponse( Stream okStream )
{
return new DecryptResponse.OK( okStream );
}

public static implicit DecryptResponse( DecryptStatus status, String errorMessage )
{
return new DecryptResponse.Failed( status, errorMessage );
}

private DecryptResponse( DecryptStatus status )
{
this.Status = status;
}

public DecryptStatus Status { get; }

public sealed class OK : DecryptResponse
{
public OK( Stream stream )
: base( DecryptStatus.OK )
{
this.Stream = stream ?? throw new ArgumentNullException(nameof(stream));
}

public Stream Stream { get; }
}

public sealed class Failed : DecryptResponse
{
public Failed ( DecryptStatus status, String errorMessage )
: base( status )
{
if( status == DecryptStatus.OK ) throw new ArgumentException( message: "Value cannot be " + nameof(DecryptStatus.OK) + "." );
this.ErrorMessage = errorMessage ?? throw new ArgumentNullException(nameof(errorMessage));
}

public String ErrorMessage { get; }
}
}
(从 CS 理论的角度来看, the above class is a union type)。
这种设计的优点很多:
  • 类设计清楚地表明结果数据只有两种可能的“形状”:“OK”或“Failed”——并且每个子类都拥有其特定于上下文的数据成员(分别为 StreamErrorMessage)。
  • 类型层次结构是封闭的(基础 abstract 类型有一个 private 构造函数)并且它的两个子类型都是 sealed ,使得除了 OK 之外不可能有结果或 Failed .
  • 这与“枚举(类)类型”基本相同,例如 Java 的 enum -类。而 C# 是 enum更像是一个命名常量,编译器和语言不保证 C# enum value 在运行时有效(例如,即使 MyEnum v = (MyEnum)123 不是定义的值,您也可以始终执行 123)。

  • OK中的验证逻辑和 Failed构造函数保证 DecryptStatus.OK始终表示结果类型为 DecryptResponse.OK与非 null Stream属性(property)。同样,如果 Status != DecryptStatus.OK你有一个 DecryptResponse.Failed对象。
  • implicit运算符定义意味着返​​回 DecryptResponse 的方法可以直接回一个StreamValueTuple<DecryptStatus,String>并且 C# 编译器会自动为您执行转换。

  • 这样的结果类型像这样返回:
    public DecryptResponse DecryptSomething()
    {
    Stream someStream = ... // do stuff
    if( itWorked )
    {
    return someStream; // Returning a `Stream` invokes the DecryptResponse conversion operator method.
    }
    else
    {
    DecryptStatus errorStatus = ...
    return ( errorStatus, "someErrorMessage" ); // ditto for `ValueTuple<DecryptStatus,String>`
    }
    }
    或者如果你想明确:
    public DecryptResponse DecryptSomething()
    {
    Stream someStream = ... // do stuff
    if( itWorked )
    {
    return new DecryptResponse.OK( someStream );
    }
    else
    {
    DecryptStatus errorStatus = ...
    return new DecryptResponse.Failed( errorStatus, "someErrorMessage" );
    }
    }
    并像这样消耗:
    DecryptResponse response = DecryptSomething();
    if( response is DecryptResponse.OK ok )
    {
    using( ok.Stream )
    {
    // do stuff
    }
    }
    else if( response is DecryptResponse.Failed fail )
    {
    Console.WriteLine( fail.ErrorMessage );
    }
    else throw new InvalidOperationException("This will never happen.");
    (不幸的是,C# 编译器还不够智能,无法识别封闭类型的层次结构,因此需要 else throw new... 语句, but hopefully eventually that won't be necessary )。
    如果您需要使用 JSON.net 支持序列化,那么您不需要做任何事情,因为 JSON.NET 支持这些类型的序列化就好了 - 但是如果您需要反序列化它们,那么您将需要一个自定义契约(Contract)解析器,不幸的是 - 但是为封闭类型编写一个通用的契约(Contract)解析器很简单,一旦你写了一个,你就不需要再写另一个了。

    关于C# 可空 : Make nullchecking dependent on another property/variable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62065860/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com