C # में बयानों का उपयोग करते हुए


315

मैं एक प्रोजेक्ट पर काम कर रहा हूं। मुझे दो फ़ाइलों की सामग्री की तुलना करनी होगी और देखना होगा कि क्या वे एक दूसरे से ठीक मेल खाती हैं।

बहुत सारे त्रुटि-जाँच और सत्यापन से पहले, मेरा पहला मसौदा है:

  DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory + "\\TestArea\\");
  FileInfo[] files = di.GetFiles(filename + ".*");

  FileInfo outputFile = files.Where(f => f.Extension == ".out").Single<FileInfo>();
  FileInfo expectedFile = files.Where(f => f.Extension == ".exp").Single <FileInfo>();

  using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  {
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        {
          return false;
        }
      }
      return (outFile.EndOfStream && expFile.EndOfStream);
    }
  }

घोंसले वाले usingबयान देना थोड़ा अजीब लगता है ।

क्या ऐसा करने के लिए इससे अच्छा तरीका है?


मुझे लगता है कि मुझे इस कथन का उपयोग करके घोषणा करने का एक सिन्थेटिक रूप से क्लीनर तरीका मिल गया होगा, और यह मेरे लिए काम करता है? आईडी का उपयोग करने के बजाए आईडीसिस के उपयोग के विवरण में अपने प्रकार के रूप में var का उपयोग करने से मुझे लगता है कि मैं अपनी दोनों वस्तुओं को तत्काल उपयोग करने की अनुमति देता हूं और उनके गुणों और विधियों को उनके साथ आवंटित किया जाता है, जैसे कि (var uow = UnitOfWorkType1 (), uow2 = UnitOfWorkType2 का उपयोग करके ()) {}
कालेब

जवाबों:


556

ऐसा करने का पसंदीदा तरीका केवल {अंतिम usingविवरण के बाद शुरुआती ब्रेस लगाना है , जैसे:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead())) 
{
    ///...
}

10
सफाई वाला? और आपको उसी प्रकार का उपयोग करने के लिए मजबूर नहीं करता है .. मैं हमेशा इसे इस तरह से करता हूं भले ही प्रकार पठनीयता और स्थिरता के लिए मेल खाते हों।
मेन्डमाइकोड

7
@ हर्ड्रिज: विजुअल स्टूडियो का ऑटो-फॉर्मेट इसे हटा देता है। विचार चर घोषणाओं की सूची की तरह दिखता है।
SLaks 20

41
यकीन नहीं होता है कि अगर मुझे लगता है कि अधिक पठनीय है। अगर कुछ भी यह नेस्टेड कोड के देखो टूट जाता है। और ऐसा लगता है जैसे कि पहला उपयोग करने वाला बयान खाली और अप्रयुक्त है। लेकिन, मुझे लगता है कि क्या कभी काम करता है ...: /
जोनाथन वाटनी

10
@ ब्रायन वत्स, "विरोधाभासी" वास्तविक प्राथमिकताएं व्यक्त कर सकते हैं। यह बहुत संभावना है कि देवों के एक अलग समूह को भंग कर दिया गया था नेस्टिंग की सिफारिश की गई थी। यह जानने का एकमात्र तरीका एक समानांतर ब्रह्मांड में फिर से प्रयोग को चलाना है।
दान रोसेनस्टार्क

6
@fmuecke: यह बहुत सच नहीं है; यह काम करेगा। IDisposableराज्य के लिए नियम कि Dispose()दो बार कॉल करने से कुछ नहीं होना चाहिए। यह नियम केवल खराब लिखे गए डिस्पोजल के मामले में है।
स्लाक्स

138

यदि ऑब्जेक्ट एक ही प्रकार के हैं, तो आप निम्न कार्य कर सकते हैं

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
                    expFile = new StreamReader(expectedFile.OpenRead()))
{
    // ...
}

1
वैसे वे सभी एक ही प्रकार के होते हैं यदि वे सभी आईडीसॉपील हैं तो शायद एक कास्ट काम करेगा?
जिपरसन

8
@jpierson जो काम करता है, हाँ, लेकिन तब जब आप IDisposableउपयोग करने वाले ब्लॉक के अंदर से ऑब्जेक्ट्स को कॉल कर रहे हैं , तो हम क्लास के किसी भी सदस्य को कॉल नहीं कर सकते हैं (बिना कास्ट के, जो बिंदु imo को हरा देता है)।
कोनेल

आईडीसोपायरी एक प्रकार है इसलिए इसे केवल मिश्रित प्रकारों की सूची के रूप में उपयोग करें, जैसा कि कुछ अन्य उत्तरों में देखा गया है।
क्रिस रोलिंस

33

जब IDisposableएस एक ही प्रकार के होते हैं, तो आप निम्न कार्य कर सकते हैं:

 using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
     expFile = new StreamReader(expectedFile.OpenRead()) {
     // ...
 }

MSDN पृष्ठ पर using पर इस भाषा सुविधा पर प्रलेखन है।

आप निम्नलिखित कर सकते हैं कि क्या IDisposableएस एक ही प्रकार के हैं या नहीं :

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamWriter anotherFile = new StreamReader(anotherFile.OpenRead()))
{ 
     // ...
}

18

यदि आपको ब्लॉक का उपयोग करने से पहले अपने ब्लॉक का उपयोग करने के लिए चर घोषित करने में कोई आपत्ति नहीं है, तो आप उन सभी का उपयोग करके एक ही बयान में घोषित कर सकते हैं।

    Test t; 
    Blah u;
    using (IDisposable x = (t = new Test()), y = (u = new Blah())) {
        // whatever...
    }

इस तरह से, x और y उपयोग करने के लिए ब्लॉक ब्लॉक के लिए आईडीआईडीआरपी के प्रकार के प्लेसहोल्डर चर हैं और आप अपने कोड के अंदर टी और यू का उपयोग करते हैं। बस सोचा था कि मैं उल्लेख करूँगा।


3
मुझे लगता है कि यह आपके कोड को देखने वाले एक नए डेवलपर को भ्रमित करेगा।
जेक

5
यह एक बुरा अभ्यास हो सकता है; इसका एक साइड-इफ़ेक्ट है कि मानव रहित पुनरुत्थान के बाद भी चर मौजूद रहेंगे। Microsoft के C # संदर्भ के अनुसार, "आप संसाधन ऑब्जेक्ट को इंस्टेंट कर सकते हैं और फिर वेरिएबल को स्टेटमेंट यूज करने के लिए पास कर सकते हैं, लेकिन यह एक बेस्ट प्रैक्टिस नहीं है। इस मामले में, कंट्रोल स्कोप का उपयोग करने के बाद भी ऑब्जेक्ट स्कोप में रहता है, फिर भी यह होगा शायद अब इसके मानव रहित संसाधनों तक पहुंच नहीं है। "
रॉबर्ट एल्टमैन

@RobertAltman आप सही हैं, और वास्तविक कोड में मैं एक और दृष्टिकोण (शायद गैविन एच से एक) का उपयोग करूंगा। यह सिर्फ एक कम बेहतर विकल्प है।
बॉटज़ ३०००

आप बस टाइपकास्ट के साथ प्रयोग के अंदर घोषणाओं को आगे बढ़ा सकते हैं। क्या यह बेहतर होगा?
टिमोथी ब्लिसडेल

9

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

आप सभी डेटा को पढ़ने के लिए अपने आप को बचाने के लिए विभिन्न फ़ाइलों का पता लगाने के लिए पहले फ़ाइल आकार जैसी चीजों की तुलना भी कर सकते हैं।


हाँ, फ़ाइल आकार की जाँच करना एक अच्छा विचार है, आपको समय बचाता है या सभी बाइट्स पढ़ता है। (+1)
टिमोथी

9

उपयोग करने का स्टेटमेंट आईडीसोफ़रेबल इंटरफ़ेस से हटकर काम करता है, इसलिए एक अन्य विकल्प हो सकता है कि कुछ प्रकार के समग्र वर्ग का निर्माण किया जाए जो आईडीआईसॉफ़िल को लागू करता है और आपके द्वारा उपयोग किए गए सभी स्टेटमेंट्स में आमतौर पर उपयोग किए जाने वाले आईडीसॉफ़ॉर्म ऑब्जेक्ट्स का संदर्भ होता है। इसका नकारात्मक पक्ष यह है कि आपको अपने चरों के दायरे को पहले और उसके बाहर के दायरे में घोषित करना होगा, ताकि उपयोग करने वाले ब्लॉक के भीतर उपयोगी हो सके, जिसमें कुछ अन्य सुझावों की तुलना में कोड की अधिक लाइनों की आवश्यकता होगी।

Connection c = new ...; 
Transaction t = new ...;

using (new DisposableCollection(c, t))
{
   ...
}

डिस्पोजेबलकोलिशन के लिए कंस्ट्रक्टर इस मामले में एक सर्वोपरि सरणी है ताकि आप जितने चाहें उतने में फ़ीड कर सकें।


7

तुम भी कह सकते हो:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
   ...
}

लेकिन कुछ लोगों को यह पढ़ना मुश्किल लग सकता है। BTW, आपकी समस्या के लिए एक अनुकूलन के रूप में, आप क्यों नहीं जाँचते हैं कि फ़ाइल का आकार पहले एक ही आकार का है, लाइन से जाने से पहले?


6

आप सभी कोष्ठक को छोड़ सकते हैं, लेकिन सबसे अधिक उपयोग करने वाले:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
{
  while (!(outFile.EndOfStream || expFile.EndOfStream))
  {
    if (outFile.ReadLine() != expFile.ReadLine())
    {
      return false;
    }
  }
}

मुझे लगता है कि यह उसी प्रकार के कई प्रकारों का उपयोग करने की तुलना में क्लीनर है, जैसा कि अन्य लोगों ने सुझाव दिया है, लेकिन मुझे यकीन है कि बहुत से लोग सोचेंगे कि वे भ्रामक हैं


6

आप कई उपयोग योग्य वस्तुओं को अल्पविरामों के साथ उपयोग करके एक समूह में रख सकते हैं:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()), 
       expFile = new StreamReader(expectedFile.OpenRead()))
{

}

5

इसके बारे में कुछ भी अजीब नहीं है। usingकोड ब्लॉक समाप्त होने के बाद ऑब्जेक्ट के निपटान को सुनिश्चित करने का एक छोटा तरीका है। यदि आपके पास अपने बाहरी ब्लॉक में एक डिस्पोजेबल वस्तु है जिसे आंतरिक ब्लॉक का उपयोग करने की आवश्यकता है, तो यह पूरी तरह से स्वीकार्य है।

संपादित करें: समेकित कोड उदाहरण दिखाने के लिए टाइपिंग पर बहुत धीमा। अन्य सभी को +1।


5

और केवल स्पष्टता में जोड़ने के लिए, इस मामले में, चूंकि प्रत्येक क्रमिक कथन एक एकल कथन है, (और ब्लॉक नहीं), आप सभी कोष्ठकों को छोड़ सकते हैं:

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    while (!(outFile.EndOfStream || expFile.EndOfStream))  
       if (outFile.ReadLine() != expFile.ReadLine())    
          return false;  

दिलचस्प समाधान; ऐसा करने से / सबसे कम स्तर पर ब्रैकेट के 1 सेट का उपयोग करने पर भी हो सकता है, एक ही लक्ष्य पूरा करता है कि उन्हें बाएं-न्यायोचित (क्लीनर आईएमओ) को स्टैकिंग करते हुए, किसी भी अधीनता को दिखाने के लिए उल्लेखित कॉस्मेटिक नेस्टिंग इच्छा को संबोधित करते हुए।
14:11 पर user1172173

5

C # 8.0 के बाद से आप एक घोषणा पत्र का उपयोग कर सकते हैं ।

using var outFile = new StreamReader(outputFile.OpenRead());
using var expFile = new StreamReader(expectedFile.OpenRead());
while (!(outFile.EndOfStream || expFile.EndOfStream))
{
    if (outFile.ReadLine() != expFile.ReadLine())
    {
         return false;
    }
}
return (outFile.EndOfStream && expFile.EndOfStream);

यह चर के दायरे के अंत में अर्थात विधि के अंत में चर का उपयोग करने का निपटान करेगा।


3

ये समय-समय पर आते हैं जब मैं कोड करता हूं। आप दूसरे फ़ंक्शन में स्टेटमेंट का उपयोग करके दूसरे को स्थानांतरित करने पर विचार कर सकते हैं?


3

क्या आप यह भी पूछ रहे हैं कि क्या फाइलों की तुलना करने का एक बेहतर तरीका है? मैं दोनों फाइलों के लिए एक सीआरसी या एमडी 5 की गणना करना पसंद करता हूं और उन लोगों की तुलना करता हूं।

उदाहरण के लिए आप निम्नलिखित एक्सटेंशन विधि का उपयोग कर सकते हैं:

public static class ByteArrayExtender
    {
        static ushort[] CRC16_TABLE =  { 
                      0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241, 
                      0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440, 
                      0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40, 
                      0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841, 
                      0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40, 
                      0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41, 
                      0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641, 
                      0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040, 
                      0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240, 
                      0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441, 
                      0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41, 
                      0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840, 
                      0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41, 
                      0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40, 
                      0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640, 
                      0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041, 
                      0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240, 
                      0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441, 
                      0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41, 
                      0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840, 
                      0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41, 
                      0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40, 
                      0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640, 
                      0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041, 
                      0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241, 
                      0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440, 
                      0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40, 
                      0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841, 
                      0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40, 
                      0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41, 
                      0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641, 
                      0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };


        public static ushort CalculateCRC16(this byte[] source)
        {
            ushort crc = 0;

            for (int i = 0; i < source.Length; i++)
            {
                crc = (ushort)((crc >> 8) ^ CRC16_TABLE[(crc ^ (ushort)source[i]) & 0xFF]);
            }

            return crc;
        }

एक बार जब आप यह कर लेते हैं कि फाइलों की तुलना करना बहुत आसान है:

public bool filesAreEqual(string outFile, string expFile)
{
    var outFileBytes = File.ReadAllBytes(outFile);
    var expFileBytes = File.ReadAllBytes(expFile);

    return (outFileBytes.CalculateCRC16() == expFileBytes.CalculateCRC16());
}

आप अंतर्निहित System.Security.Cryptography.MD5 वर्ग का उपयोग कर सकते हैं, लेकिन परिकलित हैश एक बाइट है [] तो आपको अभी भी उन दो सरणियों की तुलना करनी होगी।


2
बाइट सरणी लेने के बजाय, विधि को एक Streamऑब्जेक्ट लेना चाहिए और ReadByteविधि को तब तक कॉल करना चाहिए जब तक कि वह वापस न आए। यह बड़ी फ़ाइलों के लिए बड़ी मात्रा में मेमोरी को बचाएगा।
SLKs

फिर आप सभी बाइट्स पर crc की गणना कैसे करेंगे?
टिमोथी 17

ओह, मैंने जो कहा वह कभी बुरा नहीं है: p Thnx, मैं इसे अपने कोड में
बदलूंगा

हर बार जब आप ReadByteएक बाइट द्वारा स्ट्रीम की स्थिति को आगे बढ़ाते हैं। इसलिए, यदि आप इसे तब तक कॉल करते रहते हैं, जब तक कि यह -1 (EOF) वापस नहीं आ जाता है, तो यह आपको फ़ाइल में हर बाइट देगा। msdn.microsoft.com/en-us/library/system.io.stream.readbyte.aspx
SLaks

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

3

इसके अलावा, यदि आप पहले से ही रास्तों को जानते हैं, तो कोई भी बिंदु निर्देशिका को स्कैन नहीं कर रहा है।

इसके बजाय, मैं कुछ इस तरह की सिफारिश करेंगे:

string directory = Path.Combine(Environment.CurrentDirectory, @"TestArea\");

using (StreamReader outFile = File.OpenText(directory + filename + ".out"))
using (StreamReader expFile = File.OpenText(directory + filename + ".exp"))) 
{
    //...

Path.Combine एक पथ में एक फ़ोल्डर या फ़ाइल नाम जोड़ देगा और सुनिश्चित करेगा कि पथ और नाम के बीच एक बैकस्लैश है।

File.OpenTextएक फ़ाइल खोलेंगे और एक बार StreamReaderमें एक बना लेंगे ।

@ के साथ एक स्ट्रिंग को प्रीफ़िक्स करके, आप हर बैकस्लैश से बच निकलने से बच सकते हैं (जैसे, @"a\b\c")


3

मुझे लगता है कि मुझे इस कथन का उपयोग करके घोषणा करने का एक सिन्थेटिक रूप से क्लीनर तरीका मिल गया होगा, और यह मेरे लिए काम करता है? आईडी का उपयोग आईडी के बजाय आईडीसॉपी के बजाय स्टेटमेंट में गतिशील रूप से अनुमान लगाकर दोनों वस्तुओं पर किया गया है और मुझे अपनी दोनों वस्तुओं को तुरंत भेजने की अनुमति देता है और जिस वर्ग के साथ आवंटित किया गया है, उसके गुणों और तरीकों को कॉल करता है, जैसे

using(var uow = new UnitOfWorkType1(), uow2 = new UnitOfWorkType2()){}.

कि किसी को भी पता है कि यह क्यों है सही नहीं है, कृपया मुझे बताएं


1
एक लाइन पर कई काम करता है अगर सभी चीजें एक ही प्रकार की हों। मिश्रित प्रकारों को अलग-अलग () एस का उपयोग करके अलग करना होता है। लेकिन यह var के साथ काम नहीं करता है, आपको एक प्रकार निर्दिष्ट करना होगा (C # 5 विनिर्देश, p237)
क्रिस एफ कैरोल

0

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

पहले से ही इस्तेमाल किया हुआ

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
  {
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        return false;
      }
    }
  }

विकल्प 1

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()))
    using (StreamReader expFile = new StreamReader(expectedFile.OpenRead()))
    {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
      {
        if (outFile.ReadLine() != expFile.ReadLine())
        return false;
      }
    }
  }

विकल्प 2

using (StreamReader outFile = new StreamReader(outputFile.OpenRead()),
                    expFile = new StreamReader(expectedFile.OpenRead()))
   {
      while (!(outFile.EndOfStream || expFile.EndOfStream))
       {
         if (outFile.ReadLine() != expFile.ReadLine())
         return false;
       }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.