C# 8 可空值和结果容器

2023-12-24

我有一个IResult<T>我用来处理错误的容器。它看起来像这样:

public interface IResult<out T>
{
    ResultOutcome Outcome { get; }   //enum: {Failure, Uncertain, Success}
    string Description { get; }      //string describing the error, in case of !Success
    bool IsSuccess();                //Outcome == Success
    T Data { get; }                  //If success, it contains the data passed on, otherwise NULL
}

你会这样使用它:

IResult<int> GetSomething()
{
    try{
        int result = //things that might throw...
        return Result<int>.Success(result);  
    } 
    catch(Exception e) 
    {
        return Result<int>.Failure($"Something went wrong: {e.Message}");
    }
}

进而:

var result = GetSomething();
if (!result.IsSuccess()) return result; //<- error passed on.

int resultData = result.Data; //<- no errors, so there is something in here.


到现在为止,一切都很好。然而,当我引入可为空类型时,我遇到了一个问题:

public interface IResult<out T> where T : class // unfortunately this is necessary
{
    ...
    T? Data { get; }                  //If success, it contains the data passed on, otherwise NULL
}
var result = GetSomething();
if (!result.IsSuccess()) return result; //<- error passed on.

int resultData = result.Data; //<- WARNING!!! POSSIBLE DEREFERENCE OF NULL


现在问题: 我确定result.Data包含一些东西,因为它通过了IsSuccess()步。我怎样才能让编译器放心呢?有没有办法或 C#8 可空概念与此不兼容?
是否有其他方法以类似的方式处理结果? (传递容器而不是异常)。

P.s. 1
请不要建议使用result.Data!;.

P.s. 2
这段代码已经被使用了一千行或更多,所以如果改变可以在界面上,而不是在用法上,那就更好了。


Update

If you did改变用法,并转换IsSuccess对于属性,您可以摆脱可空性问题and获得详尽的匹配。这个开关表达式是详尽的,即编译器可以检查是否满足了所有可能性。它确实要求每个分支仅检索有效的属性:

var message=result switch { {IsSuccess:true,Data:var data} => $"Got some: {data}",
                            {IsSuccess:false,Description:var error} => $"Oops {error}",
             };  

如果你的方法接受并返回IResult<T>对象,你可以这样写:

IResult<string> Doubler(IResult<string> input)
{
    return input switch { {IsSuccess:true,Data:var data} => new Ok<string>(data+ "2"),
                          {IsSuccess:false} => input
    };  
}

...

var result2=new Ok<string>("3");
var message2=Doubler(result2) switch { 
                     {IsSuccess:true,Data:var data} => $"Got some: {data}",
                     {IsSuccess:false,Description:var error} => $"Oops {error}",
             };  

原答案

它看起来像real问题是执行Result https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/results图案。这种模式有两个特点:

  • 它防止在类型级别使用无效结果值。它通过使用两种不同的类型来表示好的结果和坏的结果来实现这一点。通过这样做,每种类型仅携带其所需的内容。
  • 它强制客户端处理所有情况或明确忽略它们。

有些语言(例如 Rust)为此提供了内置类型。支持选项类型/可区分联合的函数式语言(如 F#)只需执行以下操作即可轻松实现:

type Result<'T,'TError> =
    | Ok of ResultValue:'T
    | Error of ErrorValue:'TError

详尽的模式匹配意味着客户端必须处理这两种情况。不过这种类型很常见,它已经融入了语言本身。

C# 8

在 C# 8 中,我们可以实现这两种类型,而无需进行详尽的模式匹配。目前,这些类型需要一个公共类,无论是接口还是抽象类,它实际上不需要任何成员。有很多方法可以实现它们,例如:

public interface IResult<TSuccess,TError>{}

public class Ok<TSuccess,TError>:IResult<TSuccess,TError>
{
    public TSuccess Data{get;}

    public Ok(TSuccess data)=>Data=data;

    public void Deconstruct(out TSuccess data)=>data=Data;
}

public class Fail<TSuccess,TError>:IResult<TSuccess,TError>
{
    public TError Error{get;}

    public Fail(TError error)=>Error=error;

    public void Deconstruct(out TError error)=>error=Error;
}

我们可以使用结构而不是类。

或者,要使用更接近 C# 9 的可区分联合的语法,可以嵌套类。类型仍然可以是接口,但是我真的不喜欢写new IResult<string,string>.Fail或命名接口Result代替IResult :

public abstract class Result<TSuccess,TError>
{
    public class Ok:Result<TSuccess,TError>
    {
        public TSuccess Data{get;}
        public Ok(TSuccess data)=>Data=data;
        public void Deconstruct(out TSuccess data)=>data=Data;
    }

    public class Fail:Result<TSuccess,TError>
    {
       public TError Error{get;}
        public Fail(TError error)=>Error=error;
        public void Deconstruct(out TError error)=>error=Error;
    }

    //Convenience methods
    public static Result<TSuccess,TError> Good(TSuccess data)=>new  Ok(data);
    public static Result<TSuccess,TError> Bad(TError error)=>new  Fail(error);
}

我们可以使用模式匹配来处理Result价值观。不幸的是,C# 8 不提供详尽的匹配,因此我们还需要添加默认情况。

var result=Result<string,string>.Bad("moo");
var message=result switch { Result<string,string>.Ok (var Data) => $"Got some: {Data}",
                            Result<string,string>.Fail (var Error) => $"Oops {Error}"
                            _ => throw new InvalidOperationException("Unexpected result case")
                      };

C# 9

C# 9(可能)将通过添加可区分联合枚举类 https://github.com/dotnet/csharplang/blob/master/proposals/discriminated-unions.md。我们将能够写:

enum class Result
{
    Ok(MySuccess Data),
    Fail(MyError Error)
}

并通过模式匹配来使用它。只要有匹配的解构函数,此语法就已经可以在 C# 8 中使用。 C# 9 将添加详尽的匹配,并且可能还会简化语法:

var message=result switch { Result.Ok (var Data) => $"Got some: {Data}",
                            Result.Fail (var Error) => $"Oops {Error}"
                          };

通过 DIM 更新现有类型

一些现有的功能,例如IsSuccess and Outcome只是方便的方法。事实上,F# 的选项类型还将值的“种类”公开为tag。我们可以将此类方法添加到接口中并从实现中返回固定值:

public interface IResult<TSuccess,TError>
{
    public bool IsSuccess {get;}
    public bool IsFailure {get;}
    public bool ResultOutcome {get;}
}

public class Ok<TSuccess,string>:IResult<TSuccess,TError>
{
    public bool IsSuccess     =>true;
    public bool IsFailure     =>false;
    public bool ResultOutcome =>ResultOutcome.Success;
    ...
}

The Description and Data属性也可以作为权宜之计来实现——它们破坏了结果模式,而模式匹配无论如何都会使它们过时:

public class Ok<TSuccess,TError>:IResult<TSuccess,TError>
{
    ...
    public TError Description=>throw new InvalidOperationException("A Success Result has no Description");
    ...
}

默认接口成员可用于避免乱七八糟的具体类型:

public interface IResult<TSuccess,TError>
{
    //Migration methods
    public TSuccess Data=>
        (this is Ok<TSuccess,TError> (var Data))
        ?Data
        :throw new InvalidOperationException("An Error has no data");

    public TError Description=> 
        (this is Fail<TSuccess,TError> (var Error))
        ?Error
        :throw new InvalidOperationException("A Success Result has no Description");

    //Convenience methods
    public static IResult<TSuccess,TError> Good(TSuccess data)=>new  Ok<TSuccess,TError>(data);
    public static IResult<TSuccess,TError> Bad(TError error)=>new  Fail<TSuccess,TError>(error);

}

修改以添加详尽匹配

如果我们只使用,我们可以避免模式匹配异常中的默认情况one标志和迁移属性:

public interface IResult<TSuccess,TError>
{
    public bool IsSuccess{get;}
    public bool IsFailure=>!IsSuccess;
    //Migration methods
    ...
}

var message2=result switch { {IsSuccess:true,Data:var data} => $"Got some: {data}",
                             {IsSuccess:false,Description:var error} => $"Oops {error}",
             };  

这次,编译器检测到只有两种情况,并且都被覆盖。迁移属性允许编译器检索正确的类型。消费代码必须改变and使用正确的模式,但我怀疑它已经起作用了

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 8 可空值和结果容器 的相关文章

  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • BitTorrent 追踪器宣布问题

    我花了一点业余时间编写 BitTorrent 客户端 主要是出于好奇 但部分是出于提高我的 C 技能的愿望 我一直在使用理论维基 http wiki theory org BitTorrentSpecification作为我的向导 我已经建
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 带动态元素的 WPF 启动屏幕。如何?

    我是 WPF 新手 我需要一些帮助 我有一个加载缓慢的 WPF 应用程序 因此我显示启动屏幕作为权宜之计 但是 我希望能够在每次运行时更改屏幕 并在文本区域中显示不同的引言 这是一个生产力应用程序 所以我将使用非愚蠢但激励性的引言 当然 如
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 如何在整个 ASP .NET MVC 应用程序中需要授权

    我创建的应用程序中 除了启用登录的操作之外的每个操作都应该超出未登录用户的限制 我应该添加 Authorize 每个班级标题前的注释 像这儿 namespace WebApplication2 Controllers Authorize p
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • cmake 将标头包含到每个源文件中

    其实我有一个简单的问题 但找不到答案 也许你可以给我指一个副本 所以 问题是 是否可以告诉 cmake 指示编译器在每个源文件的开头自动包含一些头文件 这样就不需要放置 include foo h 了 谢谢 CMake 没有针对此特定用例的
  • C# 成员变量继承

    我对 C 有点陌生 但我在编程方面有相当广泛的背景 我想做的事情 为游戏定义不同的 MapTiles 我已经像这样定义了 MapTile 基类 public class MapTile public Texture2D texture pu
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • IEnumreable 动态和 lambda

    我想在 a 上使用 lambda 表达式IEnumerable
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐