首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >可空引用类型和任一模式

可空引用类型和任一模式
EN

Stack Overflow用户
提问于 2019-09-24 08:33:35
回答 3查看 839关注 0票数 5

我正在尝试新的C# 8.0中的可空引用类型,我面临以下问题。

鉴于这一结构:

代码语言:javascript
代码运行次数:0
运行
复制
public readonly struct Either<TReturn, TError>
    where TReturn : struct
    where TError : struct
{
    public TError? Error { get; }
    public TReturn? Response { get; }

    public Either(TError? error, TReturn? response)
    {
        if (error == null && response == null)
        {
           throw new ArgumentException("One argument needs not to be null.");
        }
        if (error != null && response != null)
        {
            throw new ArgumentException("One argument must be null.");
        }
        Error = error;
        Response = response;
    }
}

我如何告诉编译器,ErrorResponse都不是null,它们不能都是null?有办法用新的属性做这样的事情吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-09-24 08:56:52

结构更新()

当结果类型更改为结构时,代码不会更改。要使用struct类型参数,必须向接口和类型添加以下约束:

代码语言:javascript
代码运行次数:0
运行
复制
where TResult : struct
where TError  : struct

当我想到任何一种模式时,我想到的是F#、模式匹配和歧视联合,而不是零。实际上,Either是避免空值的一种方法。事实上,这个问题的代码看起来像是试图创建一个结果类型,而不仅仅是一个。Scott的面向铁路的规划展示了如何使用这种类型在函数式语言中实现错误处理。

在F#中,结果类型定义为:

代码语言:javascript
代码运行次数:0
运行
复制
type Result<'T,'TError> = 
    | Ok of ResultValue:'T 
    | Error of ErrorValue:'TError

我们还不能在C# 8中这样做,因为没有歧视的工会。这些都是为C# 9计划的。

模式匹配

我们可以做的是,使用模式匹配来获得相同的行为,例如:

代码语言:javascript
代码运行次数:0
运行
复制
interface IResult<TResult,TError>{} //No need for an actual implementation

public class Success<TResult,TError>:IResult<TResult,TError>

{
    public TResult Result {get;}

    public Success(TResult result) { Result=result;}
}

public class Error<TResult,TError>:IResult<TResult,TError>
{
    public TError ErrorValue {get;}

    public Error(TError error) { ErrorValue=error;}
}

这样,就无法创建一个既成功又错误的IResult<>。这可以与模式匹配一起使用,例如:

代码语言:javascript
代码运行次数:0
运行
复制
IResult<int,string> someResult=.....;

if(someResult is Success<int,string> s)
{
    //Use s.Result here
}

简化表达式

考虑到C# 8的属性模式,可以将其改写为:

代码语言:javascript
代码运行次数:0
运行
复制
if(someResult is Success<int,string> {Result: var result} )
{
    Console.WriteLine(result);
}

或者,使用开关表达式,一个典型的铁路式呼叫:

代码语言:javascript
代码运行次数:0
运行
复制
IResult<int,string> DoubleIt(IResult<int,string> data)
{
    return data switch {    Error<int,string> e=>e,
                            Success<int,string> {Result: var result}=>
                                       new Success<int,string>(result*2),
                            _ => throw new Exception("Unexpected type!")
                            };
}    

F#不需要throw,因为Result<'T,'TError>不可能是OkError以外的其他东西。在C#中,我们还没有这个特性。

开关表达式允许详尽的匹配。我认为如果缺省子句也丢失了,编译器将生成一个警告。

解析式()

如果类型有解构函数,则表达式可以简化一些,例如:

代码语言:javascript
代码运行次数:0
运行
复制
public class Success<TResult,TError>:IResult<TResult,TError>
{
    public TResult Result {get;}

    public Success(TResult result) { Result=result;}

    public void Deconstruct(out TResult result) { result=Result;}
}

public class Error<TResult,TError>:IResult<TResult,TError>
{
    public TError ErrorValue {get;}

    public Error(TError error) { ErrorValue=error;}

    public void Deconstruct(out TError error) { error=ErrorValue;}
}

在这种情况下,表达式可以写成:

代码语言:javascript
代码运行次数:0
运行
复制
return data switch {    
                Error<int,string> e => e,
                Success<int,string> (var result) => new Success<int,string>(result*3),
                _ => throw new Exception("Unexpected type!")
};

可空性

这个问题从可空引用类型开始,那么可空性呢?如果我们试图传递一个空值,我们会在C# 8中得到警告吗?

是的,只要启用NRT。此代码:

代码语言:javascript
代码运行次数:0
运行
复制
#nullable enable

void Main()
{
     IResult<string,string> data=new Success<string,string>(null);
     var it=Append1(data);
     Console.WriteLine(it);
}

IResult<string,string> Append1(IResult<string,string> data)
{
    return data switch {    Error<string,string> e=>e,
                            Success<string,string> (var result)=>
                                new Success<string,string>(result+"1"),
                            _ => throw new Exception("Unexpected type!")
                            };
}

通用CS8625: Cannot convert null literal to non-nullable reference type

正在尝试

代码语言:javascript
代码运行次数:0
运行
复制
string? s=null;
IResult<string,string> data=new Success<string,string>(s);

生成CS8604: Possible null reference argument ....

票数 7
EN

Stack Overflow用户

发布于 2019-09-24 08:49:31

在实现Either monad时,应该使用两个不同的构造函数。这样,您就可以轻松地避开这些检查,因为您的实现将确保无法同时分配两个属性。

代码语言:javascript
代码运行次数:0
运行
复制
public readonly class Either<TReturn, TError>
{
    bool _successful;
    private TError _error { get; }
    private TReturn _response { get; }

    public Either(TError error)
    {
        _error = error;
    }

    public Either(TReturn response)
    {
       _successful = true;
       _response = response;
    }
}

此外,还需要添加一个方法(到struct中),该方法将用于从结构中提取值,并将其转换为公共返回类型:

代码语言:javascript
代码运行次数:0
运行
复制
public Match<T>(Func<TError, T> errorFunc, Func<TResponse, T> successFunc)
    => _successful ? successFunc(_response) : errorFunc(_error);

这样,您就强制用户处理这两种情况(成功、错误),并提供将转换为公共类型的函数:

代码语言:javascript
代码运行次数:0
运行
复制
var errorEither = new Either<string, int>(10); // example of error code
var successEither = new Either<string, int>("success"); // example of success

var commonValueError = errorEither.Match<bool>(err => false, succ => true);
var commonValueSuccess = successEither.Match<bool>(err => false, succ => true);
票数 2
EN

Stack Overflow用户

发布于 2019-09-24 18:21:01

你可以用Resharper ContractAnnotation做类似的事情。这不是C# 8的特异性,(但是.我不认为您的示例实际上使用的是可空引用类型,对吗?)您使用的是可空结构。)

代码语言:javascript
代码运行次数:0
运行
复制
[ContractAnnotation(
   "=> pError: null, pResponse: notnull; => pError: notnull, pResponse: null"
)]
public void Get(out TError? pError, out TReturn? pResponse) {
   pError = Error;
   pResponse = Response;
}

(字符串的意思是,=>的左边是输入条件,=>的右边是输出条件,;将不同的情况分开,一个无标签的值引用方法返回值。因此,在这种情况下:不管输入是什么,输出条件都是null/notnull或notnull/null。

然后使用C# 7特性out var

代码语言:javascript
代码运行次数:0
运行
复制
GetAnEitherFromSomewhere()
.Get(out var error, out var response);
if (error != null) {
   // handle error
   return;
}

// response is now known to be not null, because we can only get here if error was null

老实说,我发现JetBrains的注释[NotNull][CanBeNull][ContractAnnotation]比可空引用类型灵活得多(尽管更详细)。基本上,它们允许中间情况,在这种情况下,值可以是null,但也存在值不能是null的情况,而且在运行时可以区分这些情况。对于可空引用类型,我不能指定中间的情况,我必须选择绝对可空或绝对不可空。

即使是像TryParse这样普通的东西

代码语言:javascript
代码运行次数:0
运行
复制
// should the result be nullable with nullable reference types on?
// you have to either lie with ! or else use ? and assume it can always be null
public bool TryParse(string pString, out SomeClass pResult) {
   if (<cant parse>) {
      pResult = null;
      return false;
   }

   pResult = value;
   return true;
}

// works great with JetBrains annotations and nullable reference types off
// now you can know that the result is null or notnull
// based on testing the bool return value
[ContractAnnotation("=> true, pResult: notnull; => false, pResult: null")]
public bool TryParse(string pString, out SomeClass pResult) {
   ...
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58076171

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档