C # में आंशिक रूप से IList <T> को कैसे लागू किया जाए?


99

इसलिए जैसा कि आप जानते हैं, IList<T>अन्य इंटरफेस के बीच, C # लागू करने में सरणियाँ हो सकती हैं । किसी तरह, हालांकि, वे सार्वजनिक रूप से गिनती की संपत्ति को लागू किए बिना ऐसा करते हैं IList<T>! Arrays में केवल एक लंबाई की संपत्ति होती है।

क्या यह C # /। नेट का एक स्पष्ट उदाहरण है। इंटरफ़ेस कार्यान्वयन के बारे में अपने स्वयं के नियमों को तोड़ना या क्या मुझे कुछ याद आ रहा है?


2
किसी ने नहीं कहा कि Arrayक्लास को C # लिखा जाना था!
user541686

Arrayएक "जादू" वर्ग है, जिसे C # या किसी अन्य भाषा लक्ष्यीकरण .net में लागू नहीं किया जा सकता है। लेकिन यह विशिष्ट सुविधा C # में उपलब्ध है।
कोडइंचाक्स

जवाबों:


81

हंस के जवाब के आलोक में नया उत्तर

हंस द्वारा दिए गए उत्तर के लिए धन्यवाद, हम देख सकते हैं कि कार्यान्वयन कुछ अधिक जटिल है जितना हम सोच सकते हैं। कंपाइलर और सीएलआर दोनों ही यह धारणा देने की बहुत कोशिश करते हैं कि एक सरणी प्रकार लागू होता है IList<T>- लेकिन सरणी विचरण इस चाल को बनाता है। हंस के उत्तर के विपरीत, सरणी प्रकार (एकल-आयामी, शून्य-आधारित वैसे भी) सामान्य संग्रह को सीधे लागू करते हैं, क्योंकि किसी भी विशिष्ट सरणी का प्रकार नहीं हैSystem.Array - यह केवल सरणी का आधार प्रकार है। यदि आप किसी सरणी प्रकार से पूछते हैं कि यह किस इंटरफेस का समर्थन करता है, तो इसमें सामान्य प्रकार शामिल हैं:

foreach (var type in typeof(int[]).GetInterfaces())
{
    Console.WriteLine(type);
}

आउटपुट:

System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]

एकल-आयामी, शून्य-आधारित सरणियों के लिए, जहां तक भाषा का संबंध है, सरणी वास्तव में लागू होती IList<T>है। C # विनिर्देश की धारा 12.1.2 ऐसा कहती है। इसलिए जो कुछ भी अंतर्निहित कार्यान्वयन करता है, भाषा को उस तरह का व्यवहार करना पड़ता है जैसे किसी अन्य इंटरफ़ेस के साथ T[]लागू होता IList<T>है। इस दृष्टिकोण से, इंटरफ़ेस को कुछ सदस्यों द्वारा स्पष्ट रूप से लागू किया गया है (जैसे कि Count)। जो हो रहा है, उसके लिए भाषा के स्तर पर यह सबसे अच्छा स्पष्टीकरण है ।

ध्यान दें कि यह केवल एकल-आयामी सरणियों (और शून्य-आधारित सरणियों के लिए है, न कि C # जैसा कि एक भाषा गैर-शून्य-आधारित सरणियों के बारे में कुछ भी कहती है)। लागू T[,] नहीं करता है IList<T>

सीएलआर के नजरिए से, कुछ फनीयर चल रहा है। आप सामान्य इंटरफ़ेस प्रकारों के लिए इंटरफ़ेस मैपिंग प्राप्त नहीं कर सकते। उदाहरण के लिए:

typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))

इसका एक अपवाद देता है:

Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.

तो विचित्रता क्यों? खैर, मुझे विश्वास है कि यह वास्तव में सरणी कोवरियन के कारण है, जो कि टाइप सिस्टम में एक मस्सा है, IMO। भले ही IList<T>है नहीं (और सुरक्षित रूप से नहीं किया जा सकता) covariant, सरणी सहप्रसरण काम करने के लिए इस अनुमति देता है:

string[] strings = { "a", "b", "c" };
IList<object> objects = strings;

... जो इसे लागू करने की तरह दिखताtypeof(string[]) है IList<object>, जब यह वास्तव में नहीं होता है।

CLI कल्पना (ECMA-335) विभाजन 1, खंड 8.7.1, में यह है:

एक हस्ताक्षर प्रकार टी संगत है-एक हस्ताक्षर प्रकार यू के साथ और यदि केवल निम्न में से कम से कम एक धारण करता है

...

टी एक शून्य-आधारित रैंक -1 सरणी है V[], और Uहै IList<W>, और वी सरणी-तत्व-संगत-डब्ल्यू के साथ है।

(यह वास्तव में उल्लेख नहीं करता है ICollection<W>या IEnumerable<W>जो मुझे लगता है कि कल्पना में एक बग है।)

गैर-विचरण के लिए, CLI युक्ति सीधे भाषा युक्ति के साथ जाती है। विभाजन 1 की धारा 8.9.1 से:

इसके अतिरिक्त, तत्व प्रकार T के साथ एक निर्मित वेक्टर, इंटरफ़ेस को लागू करता है System.Collections.Generic.IList<U>, जहां U: = T. (.78.7)

(एक वेक्टर एक शून्य-आधार वाला एकल-आयामी सरणी है।)

अब के मामले में कार्यान्वयन विवरण , स्पष्ट रूप से CLR कुछ अजीब मानचित्रण काम अनुकूलता यहाँ रखने के लिए कर रही है: जब एक string[]के कार्यान्वयन के लिए कहा जाता है ICollection<object>.Count, यह उस में नहीं संभाल सकता है काफी सामान्य तरीके से। क्या यह स्पष्ट इंटरफ़ेस कार्यान्वयन के रूप में गिना जाता है? मुझे लगता है कि इस तरह से व्यवहार करना उचित है, जब तक कि आप सीधे इंटरफ़ेस मैपिंग के लिए नहीं पूछते हैं, यह हमेशा भाषा के दृष्टिकोण से उस तरह का व्यवहार करता है।

किस बारे में ICollection.Count?

अब तक मैंने जेनेरिक इंटरफेस के बारे में बात की है, लेकिन फिर ICollectionइसकी Countसंपत्ति के साथ गैर-जेनेरिक है । इस बार हम इंटरफ़ेस मैपिंग प्राप्त कर सकते हैं, और वास्तव में इंटरफ़ेस सीधे द्वारा कार्यान्वित किया जाता है System.ArrayICollection.Countसंपत्ति के कार्यान्वयन के लिए दस्तावेज Arrayजो स्पष्ट इंटरफ़ेस कार्यान्वयन के साथ लागू होता है।

अगर कोई इस तरह के स्पष्ट इंटरफ़ेस कार्यान्वयन के बारे में सोच सकता है जो "सामान्य" स्पष्ट इंटरफ़ेस कार्यान्वयन से अलग है, तो मैं इसे आगे देखने के लिए खुश हूं।

स्पष्ट इंटरफ़ेस कार्यान्वयन के आसपास पुराना उत्तर

उपरोक्त के बावजूद, जो सरणियों के ज्ञान के कारण अधिक जटिल है, आप अभी भी स्पष्ट इंटरफ़ेस कार्यान्वयन के माध्यम से समान दृश्य प्रभावों के साथ कुछ कर सकते हैं ।

यहाँ एक सरल स्टैंडअलोन उदाहरण दिया गया है:

public interface IFoo
{
    void M1();
    void M2();
}

public class Foo : IFoo
{
    // Explicit interface implementation
    void IFoo.M1() {}

    // Implicit interface implementation
    public void M2() {}
}

class Test    
{
    static void Main()
    {
        Foo foo = new Foo();

        foo.M1(); // Compile-time failure
        foo.M2(); // Fine

        IFoo ifoo = foo;
        ifoo.M1(); // Fine
        ifoo.M2(); // Fine
    }
}

5
मुझे लगता है कि आपको foo.M1 () पर एक संकलन-समय की विफलता मिलेगी; foo.M2 नहीं ();
केविन एंमी

यहां चुनौती गैर-जेनेरिक वर्ग की है, एक सरणी की तरह, एक सामान्य इंटरफ़ेस प्रकार लागू करें, जैसे IList <>। आपका स्निपेट ऐसा नहीं करता है।
हंस पसंत

@ हंसपैंट: गैर-जेनेरिक क्लास को जेनेरिक इंटरफ़ेस प्रकार लागू करना बहुत आसान है। तुच्छ। मुझे ऐसा कोई संकेत नहीं दिख रहा है कि ओपी किसके बारे में पूछ रहा था।
जॉन स्कीट

4
@ जॉनसनर्स: वास्तव में, मुझे विश्वास नहीं है कि यह पहले कोई गलत था। मैंने इसे बहुत विस्तार दिया है, और बताया कि सीएलआर अजीब तरह से व्यवहार क्यों करता है - लेकिन मेरा मानना ​​है कि स्पष्ट इंटरफ़ेस कार्यान्वयन का मेरा उत्तर पहले बहुत सही था। आप किस तरीके से असहमत हैं? फिर से, विवरण उपयोगी होगा (संभवतः आपके स्वयं के उत्तर में, यदि उपयुक्त हो)।
जॉन स्कीट

1
@ आरबीटी: हां, हालांकि इसमें एक अंतर है कि इसका उपयोग Countकरना ठीक है - लेकिन Addहमेशा फेंक देंगे, क्योंकि एरे तय-आकार के होते हैं।
जॉन स्कीट

86

इसलिए जैसा कि आप जानते हैं, IList<T>अन्य इंटरफेस के बीच, C # लागू करने में सरणियाँ हो सकती हैं

खैर, हाँ, erm नहीं, वास्तव में नहीं। यह .NET 4 ढांचे में ऐरे वर्ग के लिए घोषणा है:

[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable, 
                              IStructuralComparable, IStructuralEquatable
{
    // etc..
}

यह System.Collections.IList, लागू करता है नहीं System.Collections.Generic.IList <>। यह नहीं हो सकता, ऐरे सामान्य नहीं है। वही जेनेरिक IEnumerable <> और ICollection <> इंटरफेस के लिए जाता है।

लेकिन सीएलआर मक्खी पर ठोस सरणी प्रकार बनाता है, इसलिए यह तकनीकी रूप से एक ऐसा निर्माण कर सकता है जो इन इंटरफेस को लागू करता है। हालांकि मामला यह नहीं है। इस कोड को उदाहरण के लिए आज़माएँ:

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>));
        var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>));  // Kaboom
    }
}
abstract class Base { }
class Derived : Base, IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() { return null; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

GetInterfaceMap () कॉल "इंटरफ़ेस नहीं मिला" के साथ एक ठोस सरणी प्रकार के लिए विफल रहता है। फिर भी IEnumerable <> कास्ट बिना किसी समस्या के काम करता है।

यह quacks-like-a-duck टाइपिंग है। यह उसी प्रकार का टाइपिंग है जो भ्रम पैदा करता है कि हर मूल्य प्रकार ValueType से प्राप्त होता है जो ऑब्जेक्ट से प्राप्त होता है। कंपाइलर और सीएलआर दोनों को सरणी प्रकारों का विशेष ज्ञान है, जैसे वे मूल्य प्रकारों का करते हैं। कंपाइलर IList <> के लिए कास्टिंग का आपका प्रयास देखता है और कहता है "ठीक है, मुझे पता है कि यह कैसे करना है!"। और कास्टक्लास आईएल निर्देश का उत्सर्जन करता है। सीएलआर को इससे कोई परेशानी नहीं है, यह जानता है कि IList <> का कार्यान्वयन कैसे प्रदान किया जाए जो अंतर्निहित सरणी ऑब्जेक्ट पर काम करता है। यह अन्यथा छिपे हुए System.SZArrayHelper वर्ग के ज्ञान में निर्मित है, एक आवरण जो वास्तव में इन इंटरफेस को लागू करता है।

यह स्पष्ट रूप से हर किसी के दावों की तरह नहीं करता है, आपके द्वारा पूछे गए गणना गुण इस तरह दिखते हैं:

    internal int get_Count<T>() {
        //! Warning: "this" is an array, not an SZArrayHelper. See comments above
        //! or you may introduce a security hole!
        T[] _this = JitHelpers.UnsafeCast<T[]>(this);
        return _this.Length;
    }

हां, आप निश्चित रूप से उस टिप्पणी को "नियम तोड़ना" कह सकते हैं :) यह अन्यथा बहुत आसान है। और बहुत अच्छी तरह से छिपा हुआ है, आप इसे SSCLI20 में CLR के लिए साझा स्रोत वितरण की जांच कर सकते हैं। "IList" के लिए खोजें यह देखने के लिए कि प्रकार प्रतिस्थापन कहाँ होता है। इसे एक्शन में देखने के लिए सबसे अच्छी जगह clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod () विधि है।

सीएलआर में इस तरह का प्रतिस्थापन सीएलआर में भाषा के प्रक्षेपण में जो होता है उसकी तुलना में बहुत हल्का होता है जो कि WinRT (उर्फ मेट्रो) के लिए प्रबंधित कोड लिखने की अनुमति देता है। बस किसी भी कोर .NET प्रकार के बारे में वहाँ प्रतिस्थापित किया जाता है। IList <> IVector <> के नक्शे, उदाहरण के लिए, एक पूरी तरह से अप्रबंधित प्रकार। एक प्रतिस्थापन, COM सामान्य प्रकार का समर्थन नहीं करता है।

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


1
यह Arrayवर्ग के लिए घोषणा है , जो किसी सरणी का प्रकार नहीं है। यह एक प्रकार का आधार है । C # में एकल-आयामी सरणी कार्यान्वित होती है IList<T>। और एक गैर सामान्य प्रकार निश्चित रूप से एक सामान्य इंटरफ़ेस वैसे भी ... जो काम करता है, क्योंकि वहाँ के बहुत सारे हैं लागू कर सकते हैं अलग - प्रकार typeof(int[])! = Typeof (स्ट्रिंग []) , so typeof (int []) `लागू करता IList<int>है, और typeof(string[])लागू करता IList<string>
जॉन स्कीट

2
@HansPassant: कृपया यह न मानें कि मैं कुछ बस की वजह से यह किया जा रहा है downvote था बेचैन । तथ्य यह है कि आपके तर्क के माध्यम से Array(जो आप दिखाते हैं, एक सार वर्ग है, इसलिए संभवतः एक सरणी वस्तु का वास्तविक प्रकार नहीं हो सकता है ) और निष्कर्ष (कि यह लागू नहीं होता है IList<T>) गलत आईएमओ हैं। जिस तरह से यह लागू IList<T>होता है वह असामान्य और दिलचस्प है, मैं सहमत हूं - लेकिन यह विशुद्ध रूप से एक कार्यान्वयन विवरण है। यह दावा करने के लिए कि आईएमओ भ्रामक T[]नहीं IList<T>है। यह कल्पना और सभी मनाया व्यवहार के खिलाफ जाता है।
जॉन स्कीट

6
ठीक है, आपको लगता है कि यह गलत है। आप इसे चश्मे से नहीं पढ़ सकते हैं जो आप चश्मे में पढ़ते हैं। कृपया इसे अपने तरीके से देखने के लिए स्वतंत्र महसूस करें, लेकिन आप कभी भी एक अच्छी व्याख्या के साथ नहीं आएंगे कि GetInterfaceMap () विफल क्यों है। "कुछ कायरतापूर्ण" बहुत अधिक जानकारी नहीं है। मैं कार्यान्वयन चश्मा पहन रहा हूं: बेशक यह विफल रहता है, यह क्वैक-जैसा-ए-बतख टाइपिंग है, एक ठोस सरणी प्रकार वास्तव में ICollection <> लागू नहीं करता है। इसके बारे में कुछ भी कायरता नहीं है। चलो इसे यहाँ रखो, हम कभी सहमत नहीं होंगे।
हंस पैसेंट

4
कम से कम उस स्पष्ट तर्क को हटाने के बारे में जो दावा करता है कि सरणियों को लागू नहीं किया जा सकता IList<T> क्योंकि Array वह नहीं करता है? वह तर्क जो मैं असहमत हूं, उसका एक बड़ा हिस्सा है। इसके अलावा, मुझे लगता है कि हमें एक इंटरफ़ेस को लागू करने के लिए एक प्रकार के लिए इसका क्या अर्थ है, इस पर सहमत होना होगा: मेरे दिमाग में, सरणी प्रकार उन सभी प्रकार की अवलोकन योग्य विशेषताओं को प्रदर्शित करते हैं जो लागू करते हैं IList<T>, के अलावा GetInterfaceMapping। फिर से, यह कैसे प्राप्त किया जाता है, मेरे लिए कम महत्व का है, जैसे मैं यह कहने के साथ ठीक हूं कि System.Stringअपरिवर्तनीय है, हालांकि कार्यान्वयन विवरण अलग-अलग हैं।
जॉन स्कीट

1
सी ++ सीएलआई संकलक के बारे में क्या? वह स्पष्ट रूप से कहता है "मुझे नहीं पता कि यह कैसे करना है!" और एक त्रुटि जारी करता है। IList<T>काम करने के लिए इसे एक स्पष्ट कलाकार की आवश्यकता होती है ।
टोबियास नोज़

21

IList<T>.Countस्पष्ट रूप से लागू किया गया है :

int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);

ऐसा इसलिए किया जाता है कि जब आपके पास एक साधारण सरणी चर होता है, तो आपके पास दोनों Countऔर Lengthसीधे उपलब्ध नहीं होते हैं।

सामान्य तौर पर, स्पष्ट इंटरफ़ेस कार्यान्वयन का उपयोग तब किया जाता है जब आप यह सुनिश्चित करना चाहते हैं कि किसी प्रकार का उपयोग किसी विशेष तरीके से किया जा सकता है, इस प्रकार के सभी उपभोक्ताओं को उस तरह से सोचने के लिए मजबूर किए बिना।

संपादित करें : वूप्स, वहाँ बुरा याद करते हैं। ICollection.Countस्पष्ट रूप से लागू किया गया है। जेनेरिक IList<T>को नीचे हंस हंस के रूप में संभाला जाता है ।


4
मुझे आश्चर्य होता है, हालांकि, उन्होंने संपत्ति को केवल लंबाई के बजाय गणना क्यों नहीं कहा? एरियर एकमात्र सामान्य संग्रह है जिसमें ऐसी संपत्ति होती है (जब तक कि आप गिनती न करें string)।
टिम एस।

5
@TimS एक अच्छा प्रश्न (और जिसका उत्तर मैं नहीं जानता।) मैं अनुमान लगाता हूं कि इसका कारण यह है कि "गणना" से कुछ संख्याओं का पता चलता है, जबकि एक सरणी के आवंटित होते ही एक अपरिवर्तनीय "लंबाई" होती है () परवाह किए बिना कि किन तत्वों का मूल्य है।)
20

1
@TimS मुझे लगता है कि क्योंकि यह काम होने ICollectionकी घोषणा की Countहै, और यह भी हो सकता है और अधिक भ्रमित करता है, तो शब्द "संग्रह" में इसका इस्तेमाल नहीं किया था के साथ एक प्रकार Count:)। इन निर्णयों को बनाने में हमेशा व्यापार बंद होते हैं।
२२'१२ तक

4
@ जॉनसुंडर्स: और फिर से ... बस एक उपयोगी जानकारी के साथ कोई डाउनवोट ।
जॉन स्कीट

5
@ जॉनसनर्स: मैं अभी भी आश्वस्त नहीं हूं। हंस ने SSCLI कार्यान्वयन का उल्लेख किया है, लेकिन यह भी दावा किया है कि सरणी प्रकार भी लागू नहीं होते हैं IList<T>, इसके बावजूद भाषा और सीएलआई विनिर्देशों दोनों इसके विपरीत दिखाई देते हैं। मेरी यह कहने की हिम्मत है कि इंटरफ़ेस कार्यान्वयन कवर के तहत काम करता है जटिल हो सकता है, लेकिन यह कई स्थितियों में है। क्या आप किसी को यह कहते हुए भी अस्वीकार कर देंगे कि System.Stringअपरिवर्तनीय है, सिर्फ इसलिए कि आंतरिक कामकाज परस्पर हैं? के लिए सभी व्यावहारिक उद्देश्यों के - और निश्चित रूप से जहाँ तक सी # भाषा का सवाल है - यह है स्पष्ट impl।
जॉन स्कीट


2

यह IList के स्पष्ट इंटरफ़ेस कार्यान्वयन से अलग नहीं है। सिर्फ इसलिए कि आप इंटरफ़ेस को लागू करते हैं, इसका मतलब यह नहीं है कि इसके सदस्यों को वर्ग के सदस्यों के रूप में प्रदर्शित होने की आवश्यकता है। यह गणना गुण को कार्यान्वित करता है , यह सिर्फ एक्स [] पर इसे उजागर नहीं करता है।


1

संदर्भ-स्रोत उपलब्ध होने के साथ:

//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
// 
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]". 
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
//   ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it. 
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
sealed class SZArrayHelper {
    // It is never legal to instantiate this class.
    private SZArrayHelper() {
        Contract.Assert(false, "Hey! How'd I get here?");
    }

    /* ... snip ... */
}

विशेष रूप से यह हिस्सा:

इंटरफ़ेस स्टब डिस्पैचर इसे एक विशेष मामले के रूप में मानता है, SZArrayHelper को लोड करता है, इसी जेनेरिक विधि (बस नाम विधि द्वारा मिलान किया गया) को ढूंढता है, इसे टाइप के लिए त्वरित करता है और इसे निष्पादित करता है।

(जोर मेरा)

स्रोत (स्क्रॉल करें)।

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