C # 'notnull` türünü nulllable yapamaz


10

Rust's Resultveya Haskell'e benzer bir tür oluşturmaya çalışıyorum Eitherve şu ana kadar aldım:

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;
    }  
}

Her iki tür parametresinin kısıtlı olduğu göz önüne alındığında notnull, neden ( ?bundan sonra null olabilecek işaretli bir tür parametresinin olduğu her yerde) aşağıdakilerden şikayet ediyor:

Null olabilecek bir tür parametresinin bir değer türü veya null olmayan referans türü olduğu bilinmelidir. Bir 'sınıf', 'yapı' veya tür kısıtlaması eklemeyi düşünün.

?


Nullable başvuru türleri etkin olan .NET Core 3'te C # 8 kullanıyorum.


Bunun yerine F # 'ın sonuç türünden ve ayrımcı sendikalardan başlamalısınız . Kolayca C 8. benzer bir şey elde edebilirsiniz olmadan etrafında ölü değer taşıyan, ancak ayrıntılı eşleştirme olmaz. Her iki türü de aynı yapıya koymaya çalışmak, birbiri ardına bir problemle karşılaşacak ve sorunları geri getirecektir Sonuç düzeltmesi gerekiyordu
Panagiotis Kanavos

Yanıtlar:


13

Temel olarak IL'de temsil edilemeyen bir şey istiyorsunuz. Sıfırlanabilir değer türleri ve sıfırlanabilir referans türleri çok farklı hayvanlardır ve kaynak kodunda benzer görünseler de, IL çok farklıdır. Bir değer türü null sürümü Tfarklı bir türü (olan Nullable<T>bir başvuru türü null sürümü oysa) Tolduğunu aynı nitelikler ne olacağını derleyici söylüyorum ile, türü.

Bu daha basit örneği düşünün:

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

Aynı nedenden dolayı bu geçersiz.

Eğer Tbir yapı olmayı kısıtlarsak , GetNullValueyöntem için oluşturulan IL bir dönüş türüne sahip olacaktır Nullable<T>.

TNull edilemeyen bir referans türü olmayı kısıtlarsak , GetNullValueyöntem için oluşturulan IL bir dönüş türüne sahip olur T, ancak nullabilite yönü için bir özniteliğe sahip olur.

Derleyici her ikisinin bir dönüş türüne sahip bir yöntem IL oluşturamaz Tve Nullable<T>aynı zamanda.

Bu temelde null olabilecek referans türlerinin bir CLR konsepti olmasının sonucudur - koddaki niyetleri ifade etmenize ve derleyicinin derleme zamanında bazı kontroller yapmasına yardımcı olmak için sadece derleyici büyüsüdür.

Hata mesajı, olabileceği kadar net değil. T"bir değer türü veya boş değerli olmayan başvuru türü" olarak bilinir. Daha kesin (ancak önemli ölçüde daha kötü) bir hata iletisi:

Null olabilecek bir tür parametresinin bir değer türü olduğu veya null edilemeyen bir referans türü olduğu bilinmelidir. Bir 'sınıf', 'yapı' veya tür kısıtlaması eklemeyi düşünün.

Bu noktada hata makul bir şekilde kodumuza uygulanır - type parametresi "bir değer türü olarak bilinmez" ve "null değeri olmayan bir başvuru türü olduğu bilinmez". İkisinden biri olduğu biliniyor, ancak derleyicinin hangisini bilmesi gerekiyor .


Çalışma zamanı sihri de var - IL'de bu kısıtlamayı temsil etmenin bir yolu olmamasına rağmen, boş bir null değeri sıfırlayamazsınız. Nullable<T>Kendiniz yapamayacağınız özel bir tiptir. Ve sonra nulllable tiplerle boksun nasıl yapıldığının bonus noktası var.
Luaan

1
@Luaan: Sıfırlanabilir değer türleri için çalışma zamanı büyüsü var, ancak null edilebilir referans türleri için değil.
Jon Skeet

6

Uyarı sebebi bölümünde açıklanmıştır The issue with T?arasında null Referans türlerini deneyin . Uzun lafın kısası, eğer kullanıyorsanız T?, türün bir sınıf mı yoksa yapı mı olduğunu belirtmeniz gerekir. Her durum için iki tür oluşturabilirsiniz.

Daha derin bir sorun, Sonuç uygulamak ve Başarı ve Hata değerlerini tutmak için bir tür kullanmanın, aynı sorunları geri getirmesi ve sonucun düzeltmesi gerekiyordu.

  • Aynı tür, tür veya hata etrafında ölü bir değer taşımak veya null'ları geri getirmek zorundadır
  • Tür üzerinde desen eşleşmesi mümkün değildir. Bunun işe yaraması için bazı süslü konumsal desen eşleştirme ifadeleri kullanmanız gerekir.
  • Boş değerlerden kaçınmak için, F # Seçeneklerine benzer şekilde Option / Belki gibi bir şey kullanmanız gerekir . Yine de değer ya da hata için bir Yok taşırsınız.

F # dilinde sonuç (ve her ikisi de)

Başlangıç ​​noktası F # 'ın Sonuç tipi ve ayrımcı sendikalar olmalıdır. Sonuçta, bu zaten .NET üzerinde çalışıyor.

F # içindeki bir sonuç türü:

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

Türlerin kendileri sadece ihtiyaç duyduklarını taşırlar.

F # 'deki DU'lar, boş değer gerektirmeden kapsamlı desen eşleşmesine izin verir:

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

Bunu C # 8'de taklit etmek

Ne yazık ki, C # 8'in henüz DU'ları yok, C # 9 için planlanıyorlar. C # 8'de bunu taklit edebiliriz, ancak kapsamlı eşleşmeyi kaybediyoruz:

#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;
}

Ve kullanın:

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()
    };
}

Kapsamlı desen eşleşmesi olmadan, derleyici uyarılarını önlemek için bu varsayılan maddeyi eklememiz gerekir.

Hala ayrıntılı eşleştirme almak için bir yol arıyorum olmadan sadece bir seçenek olsa bile, ölü değerleri tanıtarak.

Opsiyon / Belki

Kapsamlı eşleme kullanan bir Option sınıfı oluşturmak daha basittir:

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;
}

Hangi ile kullanılabilir:

string cateGory = someValue switch { Option<Category> (_    ,false) =>"No Category",
                                     Option<Category> (var v,true)  => v.Name
                                   };
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.