C # में भेदभावपूर्ण संघ


93

[नोट: इस सवाल का मूल शीर्षक " C (ish) स्टाइल यूनियन में C # " था लेकिन जैसा कि जेफ की टिप्पणी ने मुझे सूचित किया, जाहिर है कि इस संरचना को 'भेदभावपूर्ण संघ' कहा जाता है]

इस प्रश्न की वाचालता को क्षमा करें।

एसओ में पहले से ही मेरे लिए कुछ इसी तरह के लगने वाले सवाल हैं, लेकिन वे यूनियन के मेमोरी सेविंग फायदों पर ध्यान केंद्रित करते हैं या इसे इंटरोप के जरिए इस्तेमाल करते हैं। ऐसे प्रश्न का एक उदाहरण यहाँ दिया गया है

संघ प्रकार की चीज पाने की मेरी इच्छा कुछ अलग है।

मैं इस समय कुछ कोड लिख रहा हूं जो उन वस्तुओं को उत्पन्न करता है जो इस तरह से दिखते हैं

public class ValueWrapper
{
    public DateTime ValueCreationDate;
    // ... other meta data about the value

    public object ValueA;
    public object ValueB;
}

सुंदर जटिल सामान मुझे लगता है कि आप सहमत होंगे। बात यह है कि ValueAकेवल कुछ निश्चित प्रकार के हो सकते हैं (चलो कहते हैं string, intऔर Foo(जो कि एक वर्ग है) और ValueBप्रकारों का एक और सेट हो सकता है। मुझे इन मूल्यों को ऑब्जेक्ट्स के रूप में व्यवहार करना पसंद नहीं है (मैं चाहता हूं कि गर्म स्नेगल महसूस हो। एक प्रकार की सुरक्षा के साथ कोडिंग)।

इसलिए मैंने इस तथ्य को व्यक्त करने के लिए एक तुच्छ छोटे आवरण वर्ग को लिखने के बारे में सोचा था कि ValueA तार्किक रूप से एक विशेष प्रकार का एक संदर्भ है। मैंने कक्षा को फोन किया Unionक्योंकि मैं सी में यूनियन कॉन्सेप्ट की याद दिलाने की कोशिश कर रहा हूं।

public class Union<A, B, C>
{
    private readonly Type type; 
    public readonly A a;
    public readonly B b;
    public readonly C c;

    public A A{get {return a;}}
    public B B{get {return b;}}
    public C C{get {return c;}}

    public Union(A a)
    {
        type = typeof(A);
        this.a = a;
    }

    public Union(B b)
    {
        type = typeof(B);
        this.b = b;
    }

    public Union(C c)
    {
        type = typeof(C);
        this.c = c;
    }

    /// <summary>
    /// Returns true if the union contains a value of type T
    /// </summary>
    /// <remarks>The type of T must exactly match the type</remarks>
    public bool Is<T>()
    {
        return typeof(T) == type;
    }

    /// <summary>
    /// Returns the union value cast to the given type.
    /// </summary>
    /// <remarks>If the type of T does not exactly match either X or Y, then the value <c>default(T)</c> is returned.</remarks>
    public T As<T>()
    {
        if(Is<A>())
        {
            return (T)(object)a;    // Is this boxing and unboxing unavoidable if I want the union to hold value types and reference types? 
            //return (T)x;          // This will not compile: Error = "Cannot cast expression of type 'X' to 'T'."
        }

        if(Is<B>())
        {
            return (T)(object)b; 
        }

        if(Is<C>())
        {
            return (T)(object)c; 
        }

        return default(T);
    }
}

इस वर्ग का उपयोग कर ValueWrapper अब इस तरह दिखता है

public class ValueWrapper2
{
    public DateTime ValueCreationDate;
    public  Union<int, string, Foo> ValueA;
    public  Union<double, Bar, Foo> ValueB;
}

जो कुछ ऐसा है जो मैं प्राप्त करना चाहता था लेकिन मुझे एक महत्वपूर्ण तत्व याद आ रहा है - वह है कंपाइलर लागू प्रकार की जाँच जब कॉल करना निम्नलिखित कोड के रूप में है और जैसा कार्य करता है

    public void DoSomething()
    {
        if(ValueA.Is<string>())
        {
            var s = ValueA.As<string>();
            // .... do somethng
        }

        if(ValueA.Is<char>()) // I would really like this to be a compile error
        {
            char c = ValueA.As<char>();
        }
    }

IMO यह वैल्यू पूछने के लिए मान्य नहीं है अगर यह एक है charक्योंकि इसकी परिभाषा स्पष्ट रूप से कहती है कि यह नहीं है - यह एक प्रोग्रामिंग त्रुटि है और मैं चाहूंगा कि संकलक इस पर उठाएं । [इसके अलावा अगर मुझे यह सही लग सकता है तो (उम्मीद है) मुझे भी अच्छा लगेगा - जो एक वरदान होगा।]

इसे प्राप्त करने के लिए मैं संकलक को बताना चाहूंगा कि प्रकार Tए, बी या सी में से एक हो सकता है

    public bool Is<T>() where T : A 
                           or T : B // Yes I know this is not legal!
                           or T : C 
    {
        return typeof(T) == type;
    } 

क्या किसी के पास कोई विचार है अगर मैं जो हासिल करना चाहता हूं वह संभव है? या मैं इस वर्ग को पहली जगह लिखने के लिए सिर्फ सादा बेवकूफ हूं?

अग्रिम में धन्यवाद।


3
C में यूनियनों को C # में वैल्यू टाइप के लिए इस्तेमाल किया जा सकता है StructLayout(LayoutKind.Explicit)और FieldOffset। यह संदर्भ प्रकारों के साथ नहीं किया जा सकता है, निश्चित रूप से। आप जो कर रहे हैं वह सी यूनियन की तरह नहीं है।
ब्रायन

4
इसे अक्सर एक भेदभाव वाला संघ कहा जाता है ।
जेफ हार्डी

धन्यवाद जेफ - मैं इस शब्द से अनभिज्ञ था लेकिन यह वास्तव में बहुत कुछ है जो मैं प्राप्त करना चाहता हूं
क्रिस फेवरेट

7
संभवत: उस तरह की प्रतिक्रिया नहीं है जिसकी आप तलाश कर रहे हैं, लेकिन क्या आपने एफ # पर विचार किया है? यह भाषा में टाइप-सुरक्षित यूनियनों और पैटर्न बेक किया हुआ है, सी # की तुलना में यूनियनों का प्रतिनिधित्व करने के लिए बहुत आसान है।
जूलियट

1
विभेदित संघ का दूसरा नाम एक योग प्रकार है।
cdiggins

जवाबों:


114

मैं वास्तव में ऊपर दिए गए टाइप-चेकिंग और टाइप-कास्टिंग समाधानों को पसंद नहीं करता, इसलिए यहां 100% प्रकार-सुरक्षित संघ है जो संकलन त्रुटियों को फेंक देगा यदि आप गलत डेटाटाइप का उपयोग करने का प्रयास करते हैं:

using System;

namespace Juliet
{
    class Program
    {
        static void Main(string[] args)
        {
            Union3<int, char, string>[] unions = new Union3<int,char,string>[]
                {
                    new Union3<int, char, string>.Case1(5),
                    new Union3<int, char, string>.Case2('x'),
                    new Union3<int, char, string>.Case3("Juliet")
                };

            foreach (Union3<int, char, string> union in unions)
            {
                string value = union.Match(
                    num => num.ToString(),
                    character => new string(new char[] { character }),
                    word => word);
                Console.WriteLine("Matched union with value '{0}'", value);
            }

            Console.ReadLine();
        }
    }

    public abstract class Union3<A, B, C>
    {
        public abstract T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h);
        // private ctor ensures no external classes can inherit
        private Union3() { } 

        public sealed class Case1 : Union3<A, B, C>
        {
            public readonly A Item;
            public Case1(A item) : base() { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return f(Item);
            }
        }

        public sealed class Case2 : Union3<A, B, C>
        {
            public readonly B Item;
            public Case2(B item) { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return g(Item);
            }
        }

        public sealed class Case3 : Union3<A, B, C>
        {
            public readonly C Item;
            public Case3(C item) { this.Item = item; }
            public override T Match<T>(Func<A, T> f, Func<B, T> g, Func<C, T> h)
            {
                return h(Item);
            }
        }
    }
}

3
हाँ, अगर आप चाहते हैं कि टाइपफेयर में भेदभाव न हो, तो आपको आवश्यकता होगी match, और यह उतना ही अच्छा तरीका है जितना इसे प्राप्त करना है।
पावेल मिनाएव

21
और अगर वह सभी बॉयलरप्लेट कोड आपको नीचे मिलता है, तो आप इस कार्यान्वयन की कोशिश कर सकते हैं जो स्पष्ट रूप से मामलों को टैग करता है: pastebin.com/EEdvVh2R । संयोग से यह शैली F # के तरीके से बहुत मिलती-जुलती है और OCaml आंतरिक रूप से यूनियनों का प्रतिनिधित्व करती है।
जूलियट

4
मुझे जूलियट का छोटा कोड पसंद है, लेकिन क्या होगा यदि प्रकार <int, int, string> हैं? आप दूसरे निर्माणकर्ता को कैसे कहेंगे?
रॉबर्ट जेप्पेसेन

2
मैं नहीं जानता कि यह कैसे 100 upvotes नहीं है। यह खूबसूरती की चीज़ है!
पाओलो फलाबेला

6
@nexus इस प्रकार को F # में मानते हैं:type Result = Success of int | Error of int
AlexFoxGill

33

मुझे स्वीकृत समाधान की दिशा पसंद है लेकिन यह तीन से अधिक वस्तुओं के संघों के लिए अच्छी तरह से पैमाने पर नहीं है (उदाहरण के लिए 9 आइटमों के एक संघ को 9 वर्ग परिभाषाओं की आवश्यकता होगी)।

यहां एक और दृष्टिकोण है जो संकलन-समय पर 100% सुरक्षित है, लेकिन यह बड़ी यूनियनों के लिए विकसित करना आसान है।

public class UnionBase<A>
{
    dynamic value;

    public UnionBase(A a) { value = a; } 
    protected UnionBase(object x) { value = x; }

    protected T InternalMatch<T>(params Delegate[] ds)
    {
        var vt = value.GetType();    
        foreach (var d in ds)
        {
            var mi = d.Method;

            // These are always true if InternalMatch is used correctly.
            Debug.Assert(mi.GetParameters().Length == 1);
            Debug.Assert(typeof(T).IsAssignableFrom(mi.ReturnType));

            var pt = mi.GetParameters()[0].ParameterType;
            if (pt.IsAssignableFrom(vt))
                return (T)mi.Invoke(null, new object[] { value });
        }
        throw new Exception("No appropriate matching function was provided");
    }

    public T Match<T>(Func<A, T> fa) { return InternalMatch<T>(fa); }
}

public class Union<A, B> : UnionBase<A>
{
    public Union(A a) : base(a) { }
    public Union(B b) : base(b) { }
    protected Union(object x) : base(x) { }
    public T Match<T>(Func<A, T> fa, Func<B, T> fb) { return InternalMatch<T>(fa, fb); }
}

public class Union<A, B, C> : Union<A, B>
{
    public Union(A a) : base(a) { }
    public Union(B b) : base(b) { }
    public Union(C c) : base(c) { }
    protected Union(object x) : base(x) { }
    public T Match<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc) { return InternalMatch<T>(fa, fb, fc); }
}

public class Union<A, B, C, D> : Union<A, B, C>
{
    public Union(A a) : base(a) { }
    public Union(B b) : base(b) { }
    public Union(C c) : base(c) { }
    public Union(D d) : base(d) { }
    protected Union(object x) : base(x) { }
    public T Match<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc, Func<D, T> fd) { return InternalMatch<T>(fa, fb, fc, fd); }
}

public class Union<A, B, C, D, E> : Union<A, B, C, D>
{
    public Union(A a) : base(a) { }
    public Union(B b) : base(b) { }
    public Union(C c) : base(c) { }
    public Union(D d) : base(d) { }
    public Union(E e) : base(e) { }
    protected Union(object x) : base(x) { }
    public T Match<T>(Func<A, T> fa, Func<B, T> fb, Func<C, T> fc, Func<D, T> fd, Func<E, T> fe) { return InternalMatch<T>(fa, fb, fc, fd, fe); }
}

public class DiscriminatedUnionTest : IExample
{
    public Union<int, bool, string, int[]> MakeUnion(int n)
    {
        return new Union<int, bool, string, int[]>(n);
    }

    public Union<int, bool, string, int[]> MakeUnion(bool b)
    {
        return new Union<int, bool, string, int[]>(b);
    }

    public Union<int, bool, string, int[]> MakeUnion(string s)
    {
        return new Union<int, bool, string, int[]>(s);
    }

    public Union<int, bool, string, int[]> MakeUnion(params int[] xs)
    {
        return new Union<int, bool, string, int[]>(xs);
    }

    public void Print(Union<int, bool, string, int[]> union)
    {
        var text = union.Match(
            n => "This is an int " + n.ToString(),
            b => "This is a boolean " + b.ToString(),
            s => "This is a string" + s,
            xs => "This is an array of ints " + String.Join(", ", xs));
        Console.WriteLine(text);
    }

    public void Run()
    {
        Print(MakeUnion(1));
        Print(MakeUnion(true));
        Print(MakeUnion("forty-two"));
        Print(MakeUnion(0, 1, 1, 2, 3, 5, 8));
    }
}

+1 यह और अधिक अनुमोदन प्राप्त करना चाहिए; मुझे यह पसंद है कि जिस तरह से आपने इसे लचीला बना दिया है, वह सभी प्रकार की यूनियनों को एकजुट करने की अनुमति देता है।
पॉल डी'अवेट

आपके समाधान के लचीलेपन और संक्षिप्तता के लिए +1। कुछ विवरण हैं जो मुझे परेशान करते हैं, हालांकि। मैं हर एक को एक अलग टिप्पणी के रूप में पोस्ट करूंगा:
stakx - अब

1
1. परावर्तन का उपयोग कुछ परिदृश्यों में बहुत बड़ा प्रदर्शन जुर्माना लगा सकता है, यह देखते हुए कि भेदभाव किए गए यूनियनों को उनके मौलिक स्वभाव के कारण, बहुत बार इस्तेमाल किया जा सकता है।
stakx -

4
2. और इनहेरिटेंस चेन dynamicमें & जेनेरिक का उपयोग UnionBase<A>अनावश्यक लगता है। UnionBase<A>गैर-जेनेरिक बनाएं , निर्माण करने वाले को मारें A, और valueएक बनाएं object(जो कि वैसे भी है; इसे घोषित करने में कोई अतिरिक्त लाभ नहीं है dynamic)। फिर प्रत्येक Union<…>कक्षा को सीधे प्राप्त करें UnionBase। इससे यह फायदा होता है कि केवल उचित Match<T>(…)विधि ही सामने आती है। (जैसा कि यह अब है, उदाहरण के लिए Union<A, B>एक अधिभार को उजागर करता है Match<T>(Func<A, T> fa)जो एक अपवाद को फेंकने की गारंटी देता है यदि संलग्न मान Aनहीं है, तो ऐसा नहीं होना चाहिए।)
स्टैक्स - अब

3
आपको मेरी लाइब्रेरी OneOf उपयोगी लग सकती है, यह इसे कम या ज्यादा करता है, लेकिन Nuget पर है :) github.com/mcintyre321/OneOf
mcintyre321

20

मैंने इस विषय पर कुछ ब्लॉग पोस्ट लिखे जो उपयोगी हो सकते हैं:

मान लीजिए कि आपके पास तीन राज्यों के साथ शॉपिंग कार्ट का परिदृश्य है: "खाली", "सक्रिय" और "अदा", प्रत्येक अलग व्यवहार के साथ ।

  • आप बनाएँ एक है ICartState इंटरफ़ेस है जो सभी राज्यों में सामान्य है (और यह सिर्फ एक खाली मार्कर इंटरफ़ेस हो सकता है)
  • आप उस इंटरफ़ेस को लागू करने वाले तीन वर्ग बनाते हैं। (वर्गों को विरासत में नहीं होना चाहिए)
  • इंटरफ़ेस में एक "तह" विधि होती है, जिसके तहत आप प्रत्येक राज्य या मामले के लिए एक लंबो पास करते हैं जिसे आपको संभालना होगा।

आप सी # से एफ # रनटाइम का उपयोग कर सकते हैं लेकिन एक हल्के वजन विकल्प के रूप में, मैंने इस तरह कोड उत्पन्न करने के लिए थोड़ा टी 4 टेम्पलेट लिखा है।

यहाँ इंटरफ़ेस है:

partial interface ICartState
{
  ICartState Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        );
}

और यहाँ कार्यान्वयन है:

class CartStateEmpty : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the empty state, so invoke cartStateEmpty 
      return cartStateEmpty(this);
  }
}

class CartStateActive : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the active state, so invoke cartStateActive
      return cartStateActive(this);
  }
}

class CartStatePaid : ICartState
{
  ICartState ICartState.Transition(
        Func<CartStateEmpty, ICartState> cartStateEmpty,
        Func<CartStateActive, ICartState> cartStateActive,
        Func<CartStatePaid, ICartState> cartStatePaid
        )
  {
        // I'm the paid state, so invoke cartStatePaid
      return cartStatePaid(this);
  }
}

अब मान लेते हैं कि आप एक विधि के साथ विस्तार करते हैं CartStateEmptyऔर जिसे लागू नहीं किया जाता हैCartStateActiveAddItemCartStatePaid

और यह भी बताते चलें कि CartStateActivePay विधि है कि अन्य राज्यों में नहीं है।

फिर यहाँ कुछ कोड है जो इसे उपयोग में दिखाता है - दो आइटम जोड़ना और फिर गाड़ी के लिए भुगतान करना:

public ICartState AddProduct(ICartState currentState, Product product)
{
    return currentState.Transition(
        cartStateEmpty => cartStateEmpty.AddItem(product),
        cartStateActive => cartStateActive.AddItem(product),
        cartStatePaid => cartStatePaid // not allowed in this case
        );

}

public void Example()
{
    var currentState = new CartStateEmpty() as ICartState;

    //add some products 
    currentState = AddProduct(currentState, Product.ProductX);
    currentState = AddProduct(currentState, Product.ProductY);

    //pay 
    const decimal paidAmount = 12.34m;
    currentState = currentState.Transition(
        cartStateEmpty => cartStateEmpty,  // not allowed in this case
        cartStateActive => cartStateActive.Pay(paidAmount),
        cartStatePaid => cartStatePaid     // not allowed in this case
        );
}    

ध्यान दें कि यह कोड पूरी तरह से असुरक्षित है - कहीं भी कोई कास्टिंग या सशर्त नहीं, और कंपाइलर की त्रुटियां यदि आप खाली गाड़ी के लिए भुगतान करने का प्रयास करते हैं, तो कहें।


दिलचस्प उपयोग मामला। मेरे लिए, वस्तुओं पर भेदभाव किए गए यूनियनों को लागू करने से खुद को काफी क्रियात्मक हो जाता है। यहां एक कार्यात्मक शैली विकल्प है जो आपके मॉडल के आधार पर स्विच अभिव्यक्तियों का उपयोग करता है: gist.github.com/dcuccia/4029f1cddd7914dc1ae676d8c4af7866 । आप देख सकते हैं कि डीयू वास्तव में आवश्यक नहीं हैं यदि केवल एक "खुश" पथ है, लेकिन वे बहुत उपयोगी हो जाते हैं जब एक विधि एक या किसी अन्य प्रकार से वापस आ सकती है, जो व्यापारिक तर्क नियमों पर निर्भर करती है।
डेविड क्यूकेया

13

मैंने https://github.com/mcintyre321/OneOf पर ऐसा करने के लिए एक पुस्तकालय लिखा है

स्थापित-पैकेज OneOf

इसमें डीयूएस जैसे OneOf<T0, T1>सभी तरह से करने के लिए इसमें सामान्य प्रकार हैं OneOf<T0, ..., T9>। उनमें से प्रत्येक में एक .Match, और एक .Switchबयान है जो आप संकलक सुरक्षित टाइप किए गए व्यवहार के लिए उपयोग कर सकते हैं, उदाहरण के लिए:

`` `

OneOf<string, ColorName, Color> backgroundColor = getBackground(); 
Color c = backgroundColor.Match(
    str => CssHelper.GetColorFromString(str),
    name => new Color(name),
    col => col
);

`` `


7

मुझे यकीन नहीं है कि मैं आपके लक्ष्य को पूरी तरह से समझ पा रहा हूं। सी में, एक संघ एक संरचना है जो एक से अधिक फ़ील्ड के लिए समान मेमोरी स्थानों का उपयोग करता है। उदाहरण के लिए:

typedef union
{
    float real;
    int scalar;
} floatOrScalar;

floatOrScalarसंघ एक नाव, या एक पूर्णांक के रूप में इस्तेमाल किया जा सकता है, लेकिन वे दोनों एक ही स्मृति स्थान खपत करते हैं। एक को बदलने से दूसरा बदल जाता है। आप सी # में एक संरचना के साथ एक ही चीज प्राप्त कर सकते हैं:

[StructLayout(LayoutKind.Explicit)]
struct FloatOrScalar
{
    [FieldOffset(0)]
    public float Real;
    [FieldOffset(0)]
    public int Scalar;
}

उपरोक्त संरचना 64 बिट्स के बजाय 32 बिट्स का उपयोग करती है। यह केवल एक संरचना के साथ संभव है। ऊपर आपका उदाहरण एक वर्ग है, और सीएलआर की प्रकृति को देखते हुए, स्मृति दक्षता के बारे में कोई गारंटी नहीं देता है। यदि आप Union<A, B, C>एक प्रकार से दूसरे में बदलते हैं, तो आप जरूरी नहीं कि स्मृति का पुन: उपयोग कर रहे हैं ... सबसे अधिक संभावना है, आप ढेर पर एक नया प्रकार आवंटित कर रहे हैं और बैकिंग objectक्षेत्र में एक अलग सूचक को छोड़ रहे हैं । एक वास्तविक संघ के विपरीत , आपका दृष्टिकोण वास्तव में आपके मुकाबले अधिक ढेर जोर का कारण हो सकता है अन्यथा यदि आप अपने संघ प्रकार का उपयोग नहीं करते हैं।


जैसा कि मैंने अपने प्रश्न में उल्लेख किया है, मेरी प्रेरणा बेहतर स्मृति दक्षता नहीं थी। मैंने प्रश्न शीर्षक को बेहतर तरीके से प्रतिबिंबित करने के लिए बदल दिया है कि मेरा लक्ष्य क्या है - "सी (ईश) संघ" का मूल शीर्षक
हिंडलाइट

एक भेदभावपूर्ण संघ, जो आप करने की कोशिश कर रहे हैं, उसके लिए पूरी तरह से अधिक समझ में आता है। जैसा कि इसे संकलित करने के लिए समय-समय पर जाँच की गई ... मैं .NET 4 और कोड कॉन्ट्रैक्ट में देखूंगा। कोड अनुबंधों के साथ, यह एक संकलन-समय अनुबंध लागू करने के लिए संभव हो सकता है। असमानता जो .Is <T> ऑपरेटर पर आपकी आवश्यकताओं को लागू करती है।
jrista

मुझे लगता है कि मुझे अभी भी सामान्य व्यवहार में, संघ के उपयोग पर सवाल उठाना है। यहां तक ​​कि सी / सी ++ में, यूनियन एक जोखिम वाली चीज है, और अत्यधिक सावधानी के साथ इसका उपयोग किया जाना चाहिए। मैं उत्सुक हूं कि आपको इस तरह के निर्माण को C # में लाने की आवश्यकता क्यों है ... आपको इससे बाहर निकलने का क्या मूल्य लगता है?
jrista

2
char foo = 'B';

bool bar = foo is int;

यह एक चेतावनी में परिणाम देता है, त्रुटि नहीं। यदि आप C # ऑपरेटर्स के लिए अपने Isऔर Asफ़ंक्शंस की तलाश कर रहे हैं , तो आपको उन्हें किसी भी तरह से प्रतिबंधित नहीं करना चाहिए।


2

यदि आप कई प्रकार की अनुमति देते हैं, तो आप प्रकार की सुरक्षा प्राप्त नहीं कर सकते (जब तक कि प्रकार संबंधित नहीं हैं)।

आप किसी भी प्रकार की सुरक्षा प्राप्त नहीं कर सकते हैं, आप केवल FieldOffset का उपयोग करके बाइट-मूल्य-सुरक्षा प्राप्त कर सकते हैं।

यह बहुत अधिक समझ में आता है के ValueWrapper<T1, T2>साथ एक सामान्य है T1 ValueAऔरT2 ValueB ...

PS: जब टाइप-सेफ्टी के बारे में बात की जाती है तो मेरा मतलब है कि कंपाइल-टाइम टाइप-सेफ्टी।

यदि आपको एक कोड आवरण की आवश्यकता होती है, तो संशोधनों के आधार पर आप कुछ का उपयोग कर सकते हैं:

public class Wrapper
{
    public ValueHolder<int> v1 = 5;
    public ValueHolder<byte> v2 = 8;
}

public struct ValueHolder<T>
    where T : struct
{
    private T value;

    public ValueHolder(T value) { this.value = value; }

    public static implicit operator T(ValueHolder<T> valueHolder) { return valueHolder.value; }
    public static implicit operator ValueHolder<T>(T value) { return new ValueHolder<T>(value); }
}

आपके द्वारा उपयोग किए जा सकने वाले आसान तरीके के लिए (इसमें प्रदर्शन समस्याएं हैं, लेकिन यह बहुत सरल है):

public class Wrapper
{
    private object v1;
    private object v2;

    public T GetValue1<T>() { if (v1.GetType() != typeof(T)) throw new InvalidCastException(); return (T)v1; }
    public void SetValue1<T>(T value) { v1 = value; }

    public T GetValue2<T>() { if (v2.GetType() != typeof(T)) throw new InvalidCastException(); return (T)v2; }
    public void SetValue2<T>(T value) { v2 = value; }
}

//usage:
Wrapper wrapper = new Wrapper();
wrapper.SetValue1("aaaa");
wrapper.SetValue2(456);

string s = wrapper.GetValue1<string>();
DateTime dt = wrapper.GetValue1<DateTime>();//InvalidCastException

ValueWrapper को सामान्य बनाने का आपका सुझाव स्पष्ट उत्तर की तरह लगता है, लेकिन इससे मुझे समस्या होती है कि मैं क्या कर रहा हूं। अनिवार्य रूप से, मेरा कोड कुछ टेक्स्ट लाइन को पार्स करके इन रैपर ऑब्जेक्ट्स को बना रहा है। तो मेरे पास एक तरीका है जैसे ValueWrapper MakeValueWrapper (स्ट्रिंग टेक्स्ट)। अगर मैं रैपर को जेनेरिक बनाता हूं तो मुझे जेनेरिक होने के लिए MakeValueWrapper के हस्ताक्षर को बदलने की आवश्यकता है और फिर इसका मतलब यह है कि कॉलिंग कोड को यह जानने की जरूरत है कि किस प्रकार की उम्मीद की जाती है और मुझे पाठ को पार्स करने से पहले अभी यह पहले से पता नहीं है। ...
क्रिस फेवरेट

... लेकिन जैसा कि मैं आखिरी टिप्पणी लिख रहा था, ऐसा महसूस हुआ कि मैंने शायद कुछ याद किया है (या कुछ गड़बड़ कर दिया है) क्योंकि मैं जो करने की कोशिश कर रहा हूं वह महसूस नहीं करता है क्योंकि यह उतना मुश्किल होना चाहिए जितना मैं इसे बना रहा हूं। मुझे लगता है कि मैं वापस जाऊंगा और कुछ मिनट एक जेनरेट किए गए रैपर पर काम करूंगा और देखूंगा कि क्या मैं इसके आसपास के पार्सिंग कोड को अनुकूलित कर सकता हूं।
क्रिस फेवरेट

मैंने जो कोड प्रदान किया है, वह केवल बिजनेस लॉजिक के लिए होना चाहिए। आपके दृष्टिकोण के साथ समस्या यह है कि आप कभी नहीं जानते हैं कि संघ में संकलन-समय पर क्या मूल्य संग्रहीत किया जाता है। इसका मतलब है कि जब भी आप यूनियन ऑब्जेक्ट को एक्सेस करते हैं तो उन स्टेटमेंट्स को स्विच या स्विच करना होगा, क्योंकि वे ऑब्जेक्ट्स एक सामान्य कार्यक्षमता साझा नहीं करते हैं! आप अपने कोड में रैपर वस्तुओं का उपयोग कैसे करेंगे? इसके अलावा, आप सामान्य वस्तुओं का निर्माण रनटाइम (धीमी, लेकिन संभव) पर कर सकते हैं। के साथ एक और आसान विकल्प मेरी संपादित पोस्ट में है।
जारोस्लाव जंडेक

आपके पास अभी अपने कोड में मूल रूप से कोई सार्थक संकलन-समय प्रकार की जांच नहीं है - आप गतिशील वस्तुओं (रनटाइम पर गतिशील प्रकार की जाँच) भी आज़मा सकते हैं।
जारोस्लाव जंडेक

2

यहाँ मेरा प्रयास है। यह सामान्य प्रकार की बाधाओं का उपयोग करते हुए, प्रकारों की जाँच का संकलन करता है।

class Union {
    public interface AllowedType<T> { };

    internal object val;

    internal System.Type type;
}

static class UnionEx {
    public static T As<U,T>(this U x) where U : Union, Union.AllowedType<T> {
        return x.type == typeof(T) ?(T)x.val : default(T);
    }

    public static void Set<U,T>(this U x, T newval) where U : Union, Union.AllowedType<T> {
        x.val = newval;
        x.type = typeof(T);
    }

    public static bool Is<U,T>(this U x) where U : Union, Union.AllowedType<T> {
        return x.type == typeof(T);
    }
}

class MyType : Union, Union.AllowedType<int>, Union.AllowedType<string> {}

class TestIt
{
    static void Main()
    {
        MyType bla = new MyType();
        bla.Set(234);
        System.Console.WriteLine(bla.As<MyType,int>());
        System.Console.WriteLine(bla.Is<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,int>());

        bla.Set("test");
        System.Console.WriteLine(bla.As<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,string>());
        System.Console.WriteLine(bla.Is<MyType,int>());

        // compile time errors!
        // bla.Set('a'); 
        // bla.Is<MyType,char>()
    }
}

यह कुछ सुंदर का उपयोग कर सकता है। विशेष रूप से, मैं यह पता नहीं लगा सका कि As / Is / Set के प्रकार के मापदंडों से कैसे छुटकारा पाया जा सकता है (क्या एक प्रकार का पैरामीटर निर्दिष्ट करने का कोई तरीका नहीं है और C # दूसरे को एक आंकड़ा दें?)


2

इसलिए मैंने इसी समस्या को कई बार मारा है, और मैं सिर्फ एक समाधान के साथ आया हूं जो मुझे (यूनियन प्रकार के कार्यान्वयन में कुछ कुरूपता की कीमत पर) सिंटैक्स मिलता है।

पुनर्कथन करने के लिए: हम कॉल साइट पर इस तरह का उपयोग चाहते हैं।

Union<int, string> u;

u = 1492;
int yearColumbusDiscoveredAmerica = u;

u = "hello world";
string traditionalGreeting = u;

var answers = new SortedList<string, Union<int, string, DateTime>>();
answers["life, the universe, and everything"] = 42;
answers["D-Day"] = new DateTime(1944, 6, 6);
answers["C#"] = "is awesome";

हम चाहते हैं कि निम्नलिखित उदाहरण संकलन में विफल हों, ताकि हमें एक प्रकार की सुरक्षा मिल जाए।

DateTime dateTimeColumbusDiscoveredAmerica = u;
Foo fooInstance = u;

अतिरिक्त क्रेडिट के लिए, आइए हम पूरी तरह से ज़रूरत से ज़्यादा जगह भी न लें।

उस सब के साथ, दो सामान्य प्रकार के मापदंडों के लिए यहां मेरा कार्यान्वयन है। तीन, चार और इसी प्रकार के मापदंडों के लिए कार्यान्वयन सीधे-आगे है।

public abstract class Union<T1, T2>
{
    public abstract int TypeSlot
    {
        get;
    }

    public virtual T1 AsT1()
    {
        throw new TypeAccessException(string.Format(
            "Cannot treat this instance as a {0} instance.", typeof(T1).Name));
    }

    public virtual T2 AsT2()
    {
        throw new TypeAccessException(string.Format(
            "Cannot treat this instance as a {0} instance.", typeof(T2).Name));
    }

    public static implicit operator Union<T1, T2>(T1 data)
    {
        return new FromT1(data);
    }

    public static implicit operator Union<T1, T2>(T2 data)
    {
        return new FromT2(data);
    }

    public static implicit operator Union<T1, T2>(Tuple<T1, T2> data)
    {
        return new FromTuple(data);
    }

    public static implicit operator T1(Union<T1, T2> source)
    {
        return source.AsT1();
    }

    public static implicit operator T2(Union<T1, T2> source)
    {
        return source.AsT2();
    }

    private class FromT1 : Union<T1, T2>
    {
        private readonly T1 data;

        public FromT1(T1 data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 1; } 
        }

        public override T1 AsT1()
        { 
            return this.data;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }

    private class FromT2 : Union<T1, T2>
    {
        private readonly T2 data;

        public FromT2(T2 data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 2; } 
        }

        public override T2 AsT2()
        { 
            return this.data;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }

    private class FromTuple : Union<T1, T2>
    {
        private readonly Tuple<T1, T2> data;

        public FromTuple(Tuple<T1, T2> data)
        {
            this.data = data;
        }

        public override int TypeSlot 
        { 
            get { return 0; } 
        }

        public override T1 AsT1()
        { 
            return this.data.Item1;
        }

        public override T2 AsT2()
        { 
            return this.data.Item2;
        }

        public override string ToString()
        {
            return this.data.ToString();
        }

        public override int GetHashCode()
        {
            return this.data.GetHashCode();
        }
    }
}

2

और संघ / या तो प्रकार के घोंसले का उपयोग कर कम से कम अभी तक एक्स्टेंसिबल समाधान पर मेरा प्रयास । मैच विधि में डिफ़ॉल्ट मापदंडों का उपयोग भी स्वाभाविक रूप से "या तो एक्स या डिफ़ॉल्ट" परिदृश्य को सक्षम करता है।

using System;
using System.Reflection;
using NUnit.Framework;

namespace Playground
{
    [TestFixture]
    public class EitherTests
    {
        [Test]
        public void Test_Either_of_Property_or_FieldInfo()
        {
            var some = new Some(false);
            var field = some.GetType().GetField("X");
            var property = some.GetType().GetProperty("Y");
            Assert.NotNull(field);
            Assert.NotNull(property);

            var info = Either<PropertyInfo, FieldInfo>.Of(field);
            var infoType = info.Match(p => p.PropertyType, f => f.FieldType);

            Assert.That(infoType, Is.EqualTo(typeof(bool)));
        }

        [Test]
        public void Either_of_three_cases_using_nesting()
        {
            var some = new Some(false);
            var field = some.GetType().GetField("X");
            var parameter = some.GetType().GetConstructors()[0].GetParameters()[0];
            Assert.NotNull(field);
            Assert.NotNull(parameter);

            var info = Either<ParameterInfo, Either<PropertyInfo, FieldInfo>>.Of(parameter);
            var name = info.Match(_ => _.Name, _ => _.Name, _ => _.Name);

            Assert.That(name, Is.EqualTo("a"));
        }

        public class Some
        {
            public bool X;
            public string Y { get; set; }

            public Some(bool a)
            {
                X = a;
            }
        }
    }

    public static class Either
    {
        public static T Match<A, B, C, T>(
            this Either<A, Either<B, C>> source,
            Func<A, T> a = null, Func<B, T> b = null, Func<C, T> c = null)
        {
            return source.Match(a, bc => bc.Match(b, c));
        }
    }

    public abstract class Either<A, B>
    {
        public static Either<A, B> Of(A a)
        {
            return new CaseA(a);
        }

        public static Either<A, B> Of(B b)
        {
            return new CaseB(b);
        }

        public abstract T Match<T>(Func<A, T> a = null, Func<B, T> b = null);

        private sealed class CaseA : Either<A, B>
        {
            private readonly A _item;
            public CaseA(A item) { _item = item; }

            public override T Match<T>(Func<A, T> a = null, Func<B, T> b = null)
            {
                return a == null ? default(T) : a(_item);
            }
        }

        private sealed class CaseB : Either<A, B>
        {
            private readonly B _item;
            public CaseB(B item) { _item = item; }

            public override T Match<T>(Func<A, T> a = null, Func<B, T> b = null)
            {
                return b == null ? default(T) : b(_item);
            }
        }
    }
}

1

आप अपवादों को एक बार फेंकने का प्रयास कर सकते हैं, जो कि वैरिएबल तक पहुँचने का प्रयास नहीं किया गया है, अर्थात यदि यह A पैरामीटर के साथ बनाया गया है और बाद में B या C तक पहुँचने का प्रयास है, तो यह कह सकता है, UnsupportedOperationException। हालांकि इसे काम करने के लिए आपको एक गटर की आवश्यकता होगी।


हां - मैंने जो पहला संस्करण लिखा था, वह एज़ मेथड में अपवाद था - लेकिन जब यह निश्चित रूप से कोड में समस्या को उजागर करता है, तो मैं रनटाइम की तुलना में संकलन समय पर इसके बारे में बताया जाना पसंद करता हूं।
क्रिस फेवरेट

1

C # भाषा डिज़ाइन टीम ने जनवरी 2017 में भेदभाव किए गए यूनियनों पर चर्चा की। https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-01-10.md#discriminated-unions-via-closed-types

आप फीचर अनुरोध के लिए https://github.com/dotnet/csharplang/issues/113 पर वोट कर सकते हैं


0

आप एक छद्म पैटर्न मिलान समारोह का निर्यात कर सकते हैं, जैसे मैं अपने सासा पुस्तकालय में किसी भी प्रकार के लिए उपयोग करता हूं । वर्तमान में रनवे ओवरहेड है, लेकिन मैं अंततः सभी प्रतिनिधियों को एक सच्चे मामले के बयान में एक CIL विश्लेषण जोड़ने की योजना बना रहा हूं।


0

आपके द्वारा उपयोग किए गए सिंटैक्स के साथ ऐसा करना संभव नहीं है, लेकिन थोड़ी अधिक वाचालता और कॉपी / पेस्ट के साथ ओवरलोड रिज़ॉल्यूशन करना आपके लिए आसान है:


// this code is ok
var u = new Union("");
if (u.Value(Is.OfType()))
{
    u.Value(Get.ForType());
}

// and this one will not compile
if (u.Value(Is.OfType()))
{
    u.Value(Get.ForType());
}

अब तक यह स्पष्ट होना चाहिए कि इसे कैसे लागू किया जाए:


    public class Union
    {
        private readonly Type type;
        public readonly A a;
        public readonly B b;
        public readonly C c;

        public Union(A a)
        {
            type = typeof(A);
            this.a = a;
        }

        public Union(B b)
        {
            type = typeof(B);
            this.b = b;
        }

        public Union(C c)
        {
            type = typeof(C);
            this.c = c;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(A) == type;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(B) == type;
        }

        public bool Value(TypeTestSelector _)
        {
            return typeof(C) == type;
        }

        public A Value(GetValueTypeSelector _)
        {
            return a;
        }

        public B Value(GetValueTypeSelector _)
        {
            return b;
        }

        public C Value(GetValueTypeSelector _)
        {
            return c;
        }
    }

    public static class Is
    {
        public static TypeTestSelector OfType()
        {
            return null;
        }
    }

    public class TypeTestSelector
    {
    }

    public static class Get
    {
        public static GetValueTypeSelector ForType()
        {
            return null;
        }
    }

    public class GetValueTypeSelector
    {
    }

गलत प्रकार का मान निकालने के लिए कोई चेक नहीं है, उदाहरण के लिए:


var u = Union(10);
string s = u.Value(Get.ForType());

इसलिए आप ऐसे मामलों में आवश्यक जांच और अपवादों को जोड़ने पर विचार कर सकते हैं।


0

मैं खुद यूनियन टाइप का इस्तेमाल करता हूं।

इसे स्पष्ट करने के लिए एक उदाहरण पर विचार करें।

कल्पना कीजिए कि हमारे पास संपर्क वर्ग है:

public class Contact 
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
    public string PostalAdrress { get; set; }
}

इन सभी को सरल तार के रूप में परिभाषित किया गया है, लेकिन क्या वास्तव में ये केवल तार हैं? बिलकूल नही। नाम में प्रथम नाम और अंतिम नाम शामिल हो सकते हैं। या एक ईमेल सिर्फ प्रतीकों का एक सेट है? मुझे पता है कि कम से कम इसमें @ होना चाहिए और यह आवश्यक है।

आइए हम डोमेन मॉडल में सुधार करें

public class PersonalName 
{
    public PersonalName(string firstName, string lastName) { ... }
    public string Name() { return _fistName + " " _lastName; }
}

public class EmailAddress 
{
    public EmailAddress(string email) { ... } 
}

public class PostalAdrress 
{
    public PostalAdrress(string address, string city, int zip) { ... } 
}

इसमें कक्षाएं बनाने के दौरान सत्यापन होंगे और हमारे पास अंततः वैध मॉडल होंगे। PersonaName वर्ग में कॉन्सट्रक्टर को उसी समय FirstName और LastName की आवश्यकता होती है। इसका मतलब है कि निर्माण के बाद, इसमें अमान्य स्थिति नहीं हो सकती है।

और क्रमशः क्लास से संपर्क करें

public class Contact 
{
    public PersonalName Name { get; set; }
    public EmailAdress EmailAddress { get; set; }
    public PostalAddress PostalAddress { get; set; }
}

इस मामले में हमारे पास एक ही समस्या है, संपर्क वर्ग की वस्तु अमान्य स्थिति में हो सकती है। मेरा मतलब है कि यह EmailAddress हो सकता है लेकिन नाम नहीं है

var contact = new Contact { EmailAddress = new EmailAddress("foo@bar.com") };

चलिए इसे ठीक करते हैं और कन्स्ट्रक्टर के साथ संपर्क वर्ग बनाते हैं जिसके लिए PersonalName, EmailAddress और PostalAddress की आवश्यकता होती है:

public class Contact 
{
    public Contact(
               PersonalName personalName, 
               EmailAddress emailAddress,
               PostalAddress postalAddress
           ) 
    { 
         ... 
    }
}

लेकिन यहां हमें एक और समस्या है। क्या होगा यदि व्यक्ति के पास केवल EmailAdress है और PostalAddress नहीं है?

अगर हम इसके बारे में सोचते हैं तो हमें पता चलता है कि संपर्क वर्ग वस्तु की वैधता की तीन संभावनाएँ हैं:

  1. एक संपर्क में केवल एक ईमेल पता होता है
  2. एक संपर्क में केवल एक डाक पता होता है
  3. संपर्क में एक ईमेल पता और एक डाक पता दोनों होते हैं

आइए डोमेन मॉडल लिखें। शुरुआत के लिए हम संपर्क जानकारी वर्ग बनाएंगे जो राज्य उपरोक्त मामलों के अनुरूप होगा।

public class ContactInfo 
{
    public ContactInfo(EmailAddress emailAddress) { ... }
    public ContactInfo(PostalAddress postalAddress) { ... }
    public ContactInfo(Tuple<EmailAddress,PostalAddress> emailAndPostalAddress) { ... }
}

और संपर्क वर्ग:

public class Contact 
{
    public Contact(
              PersonalName personalName,
              ContactInfo contactInfo
           )
    {
        ...
    }
}

आइए इसका उपयोग करने का प्रयास करें:

var contact = new Contact(
                  new PersonalName("James", "Bond"),
                  new ContactInfo(
                      new EmailAddress("agent@007.com")
                  )
               );
Console.WriteLine(contact.PersonalName()); // James Bond
Console.WriteLine(contact.ContactInfo().???) // here we have problem, because ContactInfo have three possible state and if we want print it we would write `if` cases

ContactInfo वर्ग में मैच विधि जोड़ते हैं

public class ContactInfo 
{
   // constructor 
   public TResult Match<TResult>(
                      Func<EmailAddress,TResult> f1,
                      Func<PostalAddress,TResult> f2,
                      Func<Tuple<EmailAddress,PostalAddress>> f3
                  )
   {
        if (_emailAddress != null) 
        {
             return f1(_emailAddress);
        } 
        else if(_postalAddress != null)
        {
             ...
        } 
        ...
   }
}

मैच विधि में, हम इस कोड को लिख सकते हैं, क्योंकि संपर्क वर्ग की स्थिति को निर्माणकर्ताओं के साथ नियंत्रित किया जाता है और इसमें केवल एक संभावित राज्य हो सकता है।

आइए एक सहायक वर्ग बनाएं, ताकि हर बार कई कोड के रूप में न लिखें।

public abstract class Union<T1,T2,T3>
    where T1 : class
    where T2 : class
    where T3 : class
{
    private readonly T1 _t1;
    private readonly T2 _t2;
    private readonly T3 _t3;
    public Union(T1 t1) { _t1 = t1; }
    public Union(T2 t2) { _t2 = t2; }
    public Union(T3 t3) { _t3 = t3; }

    public TResult Match<TResult>(
            Func<T1, TResult> f1,
            Func<T2, TResult> f2,
            Func<T3, TResult> f3
        )
    {
        if (_t1 != null)
        {
            return f1(_t1);
        }
        else if (_t2 != null)
        {
            return f2(_t2);
        }
        else if (_t3 != null)
        {
            return f3(_t3);
        }
        throw new Exception("can't match");
    }
}

हमारे पास कई प्रकारों के लिए अग्रिम में ऐसी कक्षा हो सकती है, जैसा कि प्रतिनिधियों फंक, एक्शन के साथ किया जाता है। यूनियन क्लास के लिए 4-6 सामान्य प्रकार के पैरामीटर पूरे होंगे।

आइए फिर से लिखने के ContactInfoवर्ग:

public sealed class ContactInfo : Union<
                                     EmailAddress,
                                     PostalAddress,
                                     Tuple<EmaiAddress,PostalAddress>
                                  >
{
    public Contact(EmailAddress emailAddress) : base(emailAddress) { }
    public Contact(PostalAddress postalAddress) : base(postalAddress) { }
    public Contact(Tuple<EmaiAddress, PostalAddress> emailAndPostalAddress) : base(emailAndPostalAddress) { }
}

यहां कंपाइलर कम से कम एक कंस्ट्रक्टर के लिए ओवरराइड करेगा। यदि हम बाकी कंस्ट्रक्टर्स को ओवरराइड करना भूल जाते हैं तो हम किसी अन्य राज्य के साथ ContactInfo क्लास की वस्तु नहीं बना सकते हैं। यह हमें मिलान के दौरान रनटाइम अपवादों से बचाएगा।

var contact = new Contact(
                  new PersonalName("James", "Bond"),
                  new ContactInfo(
                      new EmailAddress("agent@007.com")
                  )
               );
Console.WriteLine(contact.PersonalName()); // James Bond
Console
    .WriteLine(
        contact
            .ContactInfo()
            .Match(
                (emailAddress) => emailAddress.Address,
                (postalAddress) => postalAddress.City + " " postalAddress.Zip.ToString(),
                (emailAndPostalAddress) => emailAndPostalAddress.Item1.Name + emailAndPostalAddress.Item2.City + " " emailAndPostalAddress.Item2.Zip.ToString()
            )
    );

बस इतना ही। उम्मीद है की आपको मज़ा आया।

मस्ती और लाभ के लिए F # साइट से लिया गया उदाहरण

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