यह एक दिलचस्प सवाल है, लेकिन दुर्भाग्य से, सिस्टम को "धोखा" के लिए आपका दृष्टिकोण आपके कार्यक्रम की दक्षता में सुधार नहीं करेगा। यदि यह हो सकता है, तो संकलक हमारे लिए सापेक्ष आसानी से कर सकता है!
आप सही हैं कि इंटरफ़ेस संदर्भ के माध्यम से IList<T>
पर कॉल करते समय, विधियों को रनटाइम पर प्रेषित किया जाता है और इसलिए इनलाइन नहीं किया जा सकता है। इसलिए IList<T>
पर कॉल Count
जैसी विधियों और सूचकांक को इंटरफ़ेस के माध्यम से बुलाया जाएगा।
दूसरी तरफ, यह सच नहीं है कि आप किसी भी सामान्य लाभ के रूप में इसे फिर से लिखकर किसी भी प्रदर्शन लाभ (कम से कम वर्तमान सी # कंपाइलर और .NET4 सीएलआर के साथ नहीं) प्राप्त कर सकते हैं।
क्यों नहीं? पहले कुछ पृष्ठभूमि।सी # जेनेरिक काम यह है कि संकलक आपकी जेनेरिक विधि को संकलित करता है जिसमें प्रतिस्थापन योग्य पैरामीटर होते हैं और फिर वास्तविक पैरामीटर के साथ रन-टाइम पर उन्हें बदल देते हैं। यह आप पहले ही जानते थे।
लेकिन विधि का पैरामीटर संस्करण आपके से परिवर्तनीय प्रकारों के बारे में और नहीं जानता है और मैं संकलन समय पर करता हूं। इस मामले में, सभी संकलक Foo2
के बारे में जानते हैं कि list
एक IList<int>
है। हमारे पास जेनेरिक Foo2
में एक ही जानकारी है जो हम गैर-जेनेरिक Foo1
में करते हैं।
वास्तव में, कोड-ब्लोट से बचने के लिए, जेआईटी कंपाइलर केवल सभी संदर्भ प्रकार के लिए जेनेरिक विधि का एक ही तत्कालता उत्पन्न करता है। यहाँ Microsoft documentation कि इस प्रतिस्थापन और इन्स्टेन्शियशन का वर्णन करता है:
ग्राहक, एक संदर्भ प्रकार निर्दिष्ट करता तो JIT कम्पाइलर वस्तु के साथ सर्वर आईएल में सामान्य मापदंडों को बदल देता है, और मूल कोड में यह संकलित करता है। उस कोड का उपयोग सामान्य प्रकार पैरामीटर के बजाय संदर्भ प्रकार के लिए किसी और अनुरोध में किया जाएगा। ध्यान दें कि इस तरह जेआईटी कंपाइलर केवल वास्तविक कोड का उपयोग करता है। प्रबंधित ढेर से उनके आकार के अनुसार अभी भी आवंटित किए गए हैं, और कोई कास्टिंग नहीं है।
इसका मतलब यह है विधि (संदर्भ प्रकार के लिए) की JIT कम्पाइलर के संस्करण है कि टाइप सुरक्षित नहीं लेकिन यह कोई फर्क नहीं पड़ता क्योंकि संकलक संकलन समय पर सभी प्रकार के सुरक्षा को सुनिश्चित किया है। लेकिन आपके प्रश्न के लिए अधिक महत्वपूर्ण बात यह है कि इनलाइनिंग करने और प्रदर्शन को बढ़ावा देने के लिए कोई एवेन्यू नहीं है।
संपादित करें: अंत में, अनुभव, मैं सिर्फ दोनों Foo1
और Foo2
के बेंचमार्क किया है और वे समान प्रदर्शन परिणाम। दूसरे शब्दों में, Foo2
Foo1
से कोई भी तेज़ नहीं है।
int Foo0(List<int> list)
{
int sum = 0;
for (int i = 0; i < list.Count; ++i)
sum += list[i];
return sum;
}
यहाँ प्रदर्शन की तुलना है:
की तुलना के लिए एक "inlinable" संस्करण Foo0
जोड़ें
Foo0 = 1719
Foo1 = 7299
Foo2 = 7472
Foo0 = 1671
Foo1 = 7470
Foo2 = 7756
तो आप देख सकते है कि Foo0
, जो inlined जा सकता है, नाटकीय रूप से तेजी से होता है अन्य दो की तुलना में। आप यह भी देख सकते हैं कि Foo2
Foo0
जितनी जल्दी हो सके कहीं भी धीमा है।
बहुत अच्छा। LINQPad में 'Foo1' और' Foo0' के लिए आईएल को देखते हुए मतभेद अच्छी तरह से दिखाते हैं। 'Foo1' में' callvirt' ऑप्स 'IList के लिए कॉल दिखाने get_Item' और' ICollection .get_Count' जबकि 'Foo0' के' callvirt' ऑप्स हैं 'सूची .get_Item' और 'सूची .get_Count'। 'Ldarga.s' को निष्पादित करने की आवश्यकता से' Foo2' की धीमाता को समझाया जा सकता है और फिर 'ldarg.1' निष्पादित करने के बजाय 'curvirted' के साथ 'callvirt' को उपसर्ग कर सकता है। –
arcain
क्या इसका मतलब यह नहीं है कि आप अपने इंटरफेस को लागू करने वाले structs बनाकर सिस्टम को धोखा दे सकते हैं, फिर अपने जेनेरिक विधि/कक्षा में उन structs का उपयोग करें? चूंकि वे मूल्य प्रकार हैं, रनटाइम प्रत्येक संरचना प्रकार के लिए कई ठोस कार्यान्वयन बनाएगा ... – Cesar