C #은 'notnull'유형을 nullable로 만들 수 없습니다.


9

Rust Result또는 Haskell 과 비슷한 유형을 만들려고합니다.Either 지금까지 이것을 얻었습니다.

public struct Result<TResult, TError>
    where TResult : notnull
    where TError : notnull
{
    private readonly OneOf<TResult, TError> Value;
    public Result(TResult result) => Value = result;
    public Result(TError error) => Value = error;

    public static implicit operator Result<TResult, TError>(TResult result)
        => new Result<TResult, TError>(result);

    public static implicit operator Result<TResult, TError>(TError error)
        => new Result<TResult, TError>(error);

    public void Deconstruct(out TResult? result, out TError? error)
    {
        result = (Value.IsT0) ? Value.AsT0 : (TResult?)null;
        error = (Value.IsT1) ? Value.AsT1 : (TError?)null;
    }  
}

두 유형 매개 변수가 모두로 제한되어 있기 notnull때문에 왜 null이 가능한 유형 매개 변수가있는 곳에서 불평합니까?? 기호 ?

널 입력 가능 유형 매개 변수는 값 유형 또는 널 입력 불가능 참조 유형으로 알려져 있어야합니다. '클래스', 'struct'또는 유형 제약 조건을 추가하십시오.

?


nullable 참조 유형이 활성화 된 .NET Core 3에서 C # 8을 사용하고 있습니다.


대신 F #의 결과 유형과 차별적 인 노조 에서 시작해야합니다 . C # 8에서 비슷한 값을 쉽게 얻을 수는 있지만 사소한 가치를 잃지 않으면서도 완벽하게 일치하는 것은 아닙니다. 넣어 노력 모두 다른 후 하나의 문제로 실행됩니다 같은 구조체의 형태를하고하는 것은 매우 문제의 결과가 수정했는데 다시 제공
파나지오티스 Kanavos

답변:


12

기본적으로 IL로 표현할 수없는 것을 요구하고 있습니다. 널 입력 가능 값 유형과 널 입력 가능 참조 유형은 매우 다른 짐승이며 소스 코드에서 비슷해 보이지만 IL은 매우 다릅니다. 값 형식의 nullable 버전은 T다른 형식 ( Nullable<T>)이지만 nullable 버전의 참조 형식 T 같은 속성이 무엇을 기대하는 컴파일러를 이야기로, 유형입니다.

이 간단한 예를 고려하십시오.

public class Foo<T> where T : notnull
{
    public T? GetNullValue() => 
}

같은 이유로 유효하지 않습니다.

T구조체로 제한 하면 GetNullValue메서드에 대해 생성 된 IL 의 반환 유형은 다음과 같습니다.Nullable<T> 입니다.

Tnull을 허용하지 않는 참조 유형으로 제한 하면 GetNullValue메소드에 대해 생성 된 IL 의 리턴 유형은 다음과 같습니다.T 이지만 널 입력 가능성 측면의 속성을 .

컴파일러는 양쪽의 타입을 반환하는 방법에 대한 IL 생성 수 TNullable<T>동시에.

이것은 기본적으로 CLR 개념이 아닌 nullable 참조 유형의 모든 결과입니다. 코드에 의도를 표현하고 컴파일러가 컴파일 타임에 일부 검사를 수행하도록 돕는 것은 단지 마술입니다.

오류 메시지는 확실하지 않습니다. T"값 유형 또는 널 입력 불가능 참조 유형"으로 알려져 있습니다. 보다 정확하지만 훨씬 더 오류가 많은 오류 메시지는 다음과 같습니다.

널 입력 가능 유형 매개 변수는 값 유형 또는 널 입력 불가능 참조 유형으로 알려져 있어야합니다. '클래스', 'struct'또는 유형 제약 조건을 추가하십시오.

이 시점에서 오류는 코드에 합리적으로 적용됩니다. 유형 매개 변수는 "값 유형으로 알려져 있지 않음"및 "널링 불가능한 참조 유형으로 알려져 있지 않음"입니다. 두 가지 중 하나 인 것으로 알려져 있지만 컴파일러는 어느 것을 알아야 합니다.


런타임 마술도 있습니다-IL에서 해당 제한을 나타내는 방법이 없지만 널 입력 가능 널 입력 가능을 만들 수 없습니다. Nullable<T>스스로 만들 수없는 특별한 유형입니다. 그리고 널 입력 가능 유형으로 권투를 수행하는 방법의 보너스 포인트가 있습니다.
Luaan

1
@Luaan : nullable 값 형식에는 런타임 마법이 있지만 nullable 참조 형식에는 없습니다.
Jon Skeet

6

경고의 이유는 널 입력 가능 참조 유형 시도 섹션 The issue with T?에 설명되어 있습니다. 당신이 사용한다면 긴 이야기 짧게T? 사용하는 경우 유형이 클래스인지 구조 체인지 지정해야합니다. 각 사례마다 두 가지 유형을 만들 수 있습니다.

더 큰 문제는 한 가지 유형을 사용하여 결과를 구현하고 성공 및 오류 값을 모두 보유하면 결과가 해결해야하는 것과 동일한 문제가 발생한다는 것입니다.

  • 동일한 유형은 유형 또는 오류의 데드 값을 가져 오거나 널을 가져와야합니다.
  • 유형의 패턴 일치가 불가능합니다. 이 기능을 사용하려면 멋진 위치 패턴 일치 표현식을 사용해야합니다.
  • null을 피하려면 F #의 Options 와 비슷한 Option / Maybe와 같은 것을 사용해야합니다 . 그래도 값이나 오류에 대해 None을 가지고 다닐 것입니다.

F #의 결과 (및 둘 중 하나)

시작점은 F #의 결과 유형 과 차별적 노조 여야합니다 . 결국 이것은 이미 .NET에서 작동합니다.

F #의 결과 유형은 다음과 같습니다.

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

유형 자체는 필요한 것을 운반합니다.

F #의 DU는 null없이 전체 패턴 일치를 허용합니다.

match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e

C # 8에서 이것을 에뮬레이션

불행히도 C # 8에는 아직 DU가 없으며 C # 9로 예정되어 있습니다. C # 8에서는 이것을 에뮬레이트 할 수 있지만 철저한 일치를 잃습니다.

#nullable enable

public interface IResult<TResult,TError>{}​

struct Success<TResult,TError> : IResult<TResult,TError>
{
    public TResult Value {get;}

    public Success(TResult value)=>Value=value;

    public void Deconstruct(out TResult value)=>value=Value;        
}

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

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

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

그리고 그것을 사용하십시오 :

IResult<double,string> Sqrt(IResult<double,string> input)
{
    return input switch {
        Error<double,string> e => e,
        Success<double,string> (var v) when v<0 => new Error<double,string>("Negative"),
        Success<double,string> (var v)  => new Success<double,string>(Math.Sqrt(v)),
        _ => throw new ArgumentException()
    };
}

철저한 패턴 일치가 없으면 컴파일러 경고를 피하기 위해 해당 기본 절을 추가해야합니다.

나는 단지 옵션 일지라도 죽은 값 도입 하지 않고 철저한 일치를 얻는 방법을 찾고 있습니다.

옵션 / 아마

철저한 일치를 사용하는 방법으로 옵션 클래스를 만드는 것이 더 간단합니다.

readonly struct Option<T> 
{
    public readonly T Value {get;}

    public readonly bool IsSome {get;}
    public readonly bool IsNone =>!IsSome;

    public Option(T value)=>(Value,IsSome)=(value,true);    

    public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}

//Convenience methods, similar to F#'s Option module
static class Option
{
    public static Option<T> Some<T>(T value)=>new Option<T>(value);    
    public static Option<T> None<T>()=>default;
}

다음과 함께 사용할 수 있습니다 :

string cateGory = someValue switch { Option<Category> (_    ,false) =>"No Category",
                                     Option<Category> (var v,true)  => v.Name
                                   };
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.