Enum पर T प्रतिबंध के साथ जेनेरिक विधि: यह अंतर्गत कैसे काम करता है
\n\nकल्पना करें: आप C# में लिख रहे हैं और एक सार्वभौमिक विधि बनाना चाहते हैं जो किसी भी एनुम (Enum) को स्वीकार करे। आसान लगता है? लेकिन यदि आपने कभी public void DoSomething<T>() where T : Enum लिखने की कोशिश की है, तो आप जानते हैं — कंपाइलर एक त्रुटि देता है। C# 7.3 से पहले यह असंभव था। और बाद में — यह संभव हो गया, लेकिन बारीकियों के साथ। आइए समझते हैं कि यह अंतर्गत कैसे काम करता है, और यह Stack Overflow पर सबसे लोकप्रिय विषयों में से एक क्यों है।
यह लेख केवल दस्तावेज़ीकरण का शुष्क पुनर्कथन नहीं है। हम गहराई में जाएंगे: IL-कोड से लेकर ReSharper अनुशंसाओं तक। चलिए शुरू करते हैं!
\n\nसीधे where T : Enum क्यों नहीं लिख सकते थे?
\n\nलंबे समय तक, C# ने जेनेरिक-पैरामीटर के लिए प्रतिबंध के रूप में Enum या Delegate के उपयोग पर प्रतिबंध लगाया। क्यों? क्योंकि कंपाइलर को यह गारंटी देनी होती थी कि प्रकार T या तो मान प्रकार (struct) है या संदर्भ प्रकार (class) है। Enum एक विशेष संरचनात्मक प्रकार है, जो System.Enum से विरासत प्राप्त करता है। लेकिन C# के प्रकार प्रणाली में, यह सरल नियमों में फिट नहीं बैठता था:
- \n
- Enum एक struct है, लेकिन कोई भी struct नहीं। \n
- Enum ValueType से विरासत प्राप्त करता है, लेकिन कंपाइलर
where T : System.Enumके उपयोग की अनुमति नहीं देता था। \n where T : struct, Enumलिखने का प्रयास भी काम नहीं करता था। \n
डेवलपर्स अस्थायी समाधानों का सहारा लेते थे: runtime में typeof(T).IsEnum के माध्यम से जाँच या रिफ्लेक्शन का उपयोग। लेकिन इससे सारा प्रदर्शन और प्रकार सुरक्षा समाप्त हो जाती थी।
C# 7.3: लंबे समय से प्रतीक्षित समाधान
\n\nC# 7.3 और .NET Core 2.0 / .NET Framework 4.8 से शुरू करके, यह लिखना संभव हो गया:
\n\npublic static string GetEnumName<T>(T value) where T : Enum\n{\n return Enum.GetName(typeof(T), value);\n}\n\n\nअब कंपाइलर समझता है: T एक एनुम है। लेकिन यह वास्तव में कैसे करता है? आइए अंतर्गत देखें।
\n\nIL-कोड में क्या होता है?
\n\nजब आप where T : Enum लिखते हैं, तो कंपाइलर Enum.HasFlag, Enum.GetName आदि जैसी विधियों के लिए एक विशेष constrained call उत्पन्न करता है। बॉक्सिंग या रिफ्लेक्शन का उपयोग करने के बजाय, JIT-कंपाइलर विशिष्ट प्रकार T को जानते हुए एक सीधा कॉल स्थिर विधि में डाल सकता है।
उपरोक्त विधि के लिए IL-कोड का उदाहरण:
\n\n.method public hidebysig static \n string GetEnumName<([mscorlib]System.Enum) T>(!!0 value) cil managed\n{\n // constrained प्रीफिक्स के साथ Enum.GetName का कॉल\n call string [mscorlib]System.Enum::GetName(class [mscorlib]System.Type, object)\n}\n\n\nविशेषता ([mscorlib]System.Enum) T पर ध्यान दें। यही नया प्रतिबंध है। JIT इसे देखता है और जानता है कि T एक enum है, और इसलिए GetName में value पास करते समय बॉक्सिंग से बचा जा सकता है।
व्यवहार में Enum के साथ जेनेरिक विधि का उपयोग कैसे करें?
\n\nआइए एक उपयोगी उदाहरण बनाएं: केस-असंवेदनशीलता के समर्थन के साथ किसी भी एनुम में स्ट्रिंग को पार्स करने की विधि।
\n\npublic static T ParseEnum<T>(string value, bool ignoreCase = true) where T : Enum\n{\n if (string.IsNullOrWhiteSpace(value))\n throw new ArgumentException("Value cannot be null or empty.", nameof(value));\n\n if (!Enum.TryParse(typeof(T), value, ignoreCase, out object? result))\n throw new ArgumentException($"'{value}' is not a valid value for enum {typeof(T).Name}.");\n\n return (T)result;\n}\n\n\nमहत्वपूर्ण: इस तथ्य के बावजूद कि T एक Enum है, कास्टिंग (T)result को अभी भी स्पष्ट कास्टिंग की आवश्यकता है। ऐसा इसलिए है क्योंकि runtime में object प्रकार जेनेरिक-पैरामीटर के बारे में नहीं जानता है। लेकिन कंपाइलर गारंटी देता है कि T एक enum है, इसलिए कास्टिंग सुरक्षित है।
Enum.TryParse<T> के साथ अधिक सुरुचिपूर्ण विकल्प
\n\n.NET 5 से शुरू करके, एक ओवरलोड उपलब्ध हुआ