C # का 'नॉटनल' प्रकार अशक्त नहीं बनाया जा सकता


10

मैं रस्ट Resultया हास्केल के समान एक प्रकार बनाने की कोशिश कर रहा हूं 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, क्यों यह शिकायत कर रहा है (कहीं भी, जहां एक प्रकार का पैरामीटर है जिसके ?बाद अशक्त चिह्न है)):

एक अशक्त प्रकार के पैरामीटर को मूल्य प्रकार या गैर-अशक्त संदर्भ प्रकार होना चाहिए। एक 'वर्ग', 'संरचना' या प्रकार की बाधा जोड़ने पर विचार करें।

?


मैं C # 8 का उपयोग कर रहा हूँ। NET कोर 3 पर अशक्त संदर्भ प्रकार सक्षम हैं।


आपको इसके बजाय F # परिणाम प्रकार से शुरू करना चाहिए , और भेदभाव रहित संघ बनाना चाहिए। आप बिना किसी मृत मान के आसानी से C # 8 में कुछ समान हासिल कर सकते हैं , लेकिन आपके पास संपूर्ण मिलान नहीं होगा। डाल करने के लिए कोशिश कर रहा है दोनों एक ही struct में प्रकार एक के बाद एक समस्या में पड़ जाएगा, और वापस लाता है बहुत समस्याओं परिणाम ठीक करने वाला था
पानागिओटिस Kanavos

जवाबों:


13

मूल रूप से आप आईएल में प्रतिनिधित्व नहीं किया जा सकता है कि कुछ के लिए पूछ रहे हैं। अशक्त मान प्रकार और अशक्त संदर्भ प्रकार बहुत अलग जानवर हैं, और जब वे स्रोत कोड में समान दिखते हैं, तो IL बहुत भिन्न होता है। एक मान प्रकार Tका अशक्त संस्करण एक अलग प्रकार है ( Nullable<T>) जबकि संदर्भ प्रकार Tका अशक्त संस्करण एक ही प्रकार है, जिसमें संकलक को यह बताना है कि क्या अपेक्षा की जाए।

इस सरल उदाहरण पर विचार करें:

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

वह उसी कारण से अमान्य है।

यदि हम Tएक संरचना बनने में बाधा डालते हैं, तो GetNullValueविधि के लिए उत्पन्न IL का रिटर्न प्रकार होगा Nullable<T>

यदि हम Tएक गैर-अशक्त संदर्भ प्रकार के लिए बाधा डालते हैं , तो GetNullValueविधि के लिए उत्पन्न IL में वापसी प्रकार होगा T, लेकिन अशक्तता के पहलू के लिए एक विशेषता के साथ।

कंपाइलर IL को एक विधि के लिए उत्पन्न नहीं कर सकता है जिसमें एक ही समय Tऔर दोनों का रिटर्न प्रकार होता है Nullable<T>

यह मूल रूप से अशक्त संदर्भ प्रकारों के परिणामस्वरूप सभी सीएलआर अवधारणा नहीं है - कोड में इरादे व्यक्त करने और संकलनकर्ता को संकलन-समय पर कुछ जाँच करने में मदद करने के लिए यह सिर्फ संकलक जादू है।

त्रुटि संदेश उतना स्पष्ट नहीं है जितना कि यह हो सकता है। T"मान प्रकार या गैर-अशक्त संदर्भ प्रकार" होने के लिए जाना जाता है। एक और अधिक सटीक (लेकिन महत्वपूर्ण वर्डियर) त्रुटि संदेश होगा:

एक अशक्त प्रकार के पैरामीटर को मूल्य प्रकार के रूप में या गैर-अशक्त संदर्भ प्रकार के रूप में जाना जाना चाहिए। एक 'वर्ग', 'संरचना' या प्रकार की बाधा जोड़ने पर विचार करें।

उस बिंदु पर त्रुटि हमारे कोड पर यथोचित रूप से लागू होगी - प्रकार पैरामीटर "एक मान प्रकार ज्ञात नहीं है" और यह "गैर-अशक्त संदर्भ प्रकार ज्ञात नहीं है"। यह दो में से एक होने के लिए जाना जाता है, लेकिन संकलक को यह जानना होगा कि कौन सा है


रनटाइम-मैजिक भी है - आप एक अशक्त अशक्त नहीं बना सकते, भले ही आईएल में उस प्रतिबंध का प्रतिनिधित्व करने का कोई तरीका नहीं है। Nullable<T>एक विशेष प्रकार है जिसे आप स्वयं नहीं बना सकते। और फिर वहाँ बोनस बिंदु है कि कैसे बॉक्सिंग को अशक्त प्रकारों के साथ किया जाता है।
लुअन

1
@ लुअन: अशक्त मूल्य प्रकारों के लिए रनटाइम जादू है, लेकिन अशक्त संदर्भ प्रकारों के लिए नहीं।
जॉन स्कीट

6

चेतावनी के लिए कारण भाग में समझाया जाता है The issue with T?की बाहर Nullable संदर्भ प्रकार की कोशिश करें । लंबी कहानी छोटी, यदि आप उपयोग करते T?हैं तो आपको यह निर्दिष्ट करना होगा कि प्रकार एक वर्ग या संरचना है। आप प्रत्येक मामले के लिए दो प्रकार का निर्माण कर सकते हैं।

गहरी समस्या यह है कि परिणाम को लागू करने के लिए एक प्रकार का उपयोग करना और सफलता और त्रुटि दोनों मानों को पकड़ना एक ही समस्या को वापस लाता है परिणाम को ठीक करना चाहिए था, और कुछ और।

  • उसी प्रकार के आसपास एक मृत मान रखना है, या तो प्रकार या त्रुटि, या वापस नल लाने के लिए
  • प्रकार पर पैटर्न का मिलान संभव नहीं है। यह काम करने के लिए आपको कुछ फैंसी पोजिशनल पैटर्न मिलान अभिव्यक्ति का उपयोग करना होगा।
  • नल से बचने के लिए आपको F # के विकल्पों के समान विकल्प / हो सकता है, का उपयोग करना होगा । आप अभी भी मूल्य और त्रुटि के लिए या तो आसपास कोई भी नहीं ले जाएगा।

परिणाम # (या तो) F # में

प्रारंभिक बिंदु F # का परिणाम प्रकार और भेदभाव रहित संघ होना चाहिए । सब के बाद, यह पहले से ही .NET पर काम करता है।

F # में एक परिणाम प्रकार है:

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

प्रकार स्वयं ही वे ले जाते हैं जिनकी उन्हें आवश्यकता होती है।

F # में DU बिना किसी आवश्यकता के नल के बिना संपूर्ण पैटर्न की अनुमति देते हैं:

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

सी # 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
                                   };
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.