可为空的引用类型和任何模式

Rom*_*ory 4 c# nullable c#-8.0

我正在尝试使用C#8.0中的新Nullable引用类型,并且遇到了这个问题。给定这个结构

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我怎么能告诉编译器,要么ErrorResponse不为空,而且他们不能同时为空?有没有办法用新属性来做这样的事情?

Pan*_*vos 5

更新结构

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

where TResult : struct
where TError  : struct
Run Code Online (Sandbox Code Playgroud)

当我想到Either模式时,我想到的是F#,模式匹配和区别联合,而不是null。实际上,Either避免空值的一种方法。实际上,问题的代码看起来像是尝试创建Result类型,而不仅仅是Either。Scott Wlaschin的“ 铁路定向编程”展示了如何使用这种类型以功能语言实现错误处理。

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

type Result<'T,'TError> = 
    | Ok of ResultValue:'T 
    | Error of ErrorValue:'TError
Run Code Online (Sandbox Code Playgroud)

我们现在还不能在C#8中做到这一点,因为还没有歧视的联合。这些计划用于C#9。

模式匹配

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

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;}
}
Run Code Online (Sandbox Code Playgroud)

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

IResult<int,string> someResult=.....;

if(someResult is Success<int,string> s)
{
    //Use s.Result here
}
Run Code Online (Sandbox Code Playgroud)

简化表达

鉴于C#8的属性模式,可以将其重写为:

if(someResult is Success<int,string> {Result: var result} )
{
    Console.WriteLine(result);
}
Run Code Online (Sandbox Code Playgroud)

或者,使用开关表达式,一个典型的铁路风格的电话:

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!")
                            };
}    
Run Code Online (Sandbox Code Playgroud)

F#不需要这样做,throw因为a不可能Result<'T,'TError>不是Okor Error。在C#中,我们没有这个功能还没有

switch表达式允许穷举匹配。我认为如果缺少默认子句,编译器也会生成警告。

与解构函数

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

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;}
}
Run Code Online (Sandbox Code Playgroud)

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

return data switch {    
                Error<int,string> e => e,
                Success<int,string> (var result) => new Success<int,string>(result*3),
                _ => throw new Exception("Unexpected type!")
};
Run Code Online (Sandbox Code Playgroud)

可空性

问题始于可为空的引用类型,那么可为空又如何呢?如果尝试传递nulll,是否会在C#8中收到警告?

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

#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!")
                            };
}
Run Code Online (Sandbox Code Playgroud)

产生 CS8625: Cannot convert null literal to non-nullable reference type

string? s=null;
IResult<string,string> data=new Success<string,string>(s);
Run Code Online (Sandbox Code Playgroud)

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