2012-10-14 23 views
8

में सामान्य जेनेरिक प्रकार पैरामीटर के साथ प्रतिनिधियों को अनुकरण करना भाषा डिजाइन, पैटर्न और अर्थशास्त्र के बारे में यह एक कठिन सवाल है। कृपया, नीचे मत छोड़ो क्योंकि आप व्यावहारिक मूल्य नहीं देखते हैं।सी #

सबसे पहले, चलो फ़ंक्शंस और उनके पैरामीटर के बारे में सोचें। फिर हम अपने पैरामीटर/तर्कों और जेनेरिक कक्षाओं/कार्यों के साथ उनके प्रकार-पैरामीटर/प्रकार-तर्कों के साथ कार्यों के बीच समानताएं देखेंगे।

कार्य कुछ अनिर्दिष्ट मूल्यों "मापदंडों" कहा जाता है के साथ कोड के ब्लॉक कर रहे हैं। आप तर्क प्रदान करते हैं और परिणाम प्राप्त करते हैं।

जेनेरिक कक्षाएं कुछ अनिर्दिष्ट "प्रकार-पैरामीटर" के साथ वर्ग हैं। आप टाइप-तर्क की आपूर्ति करते हैं और फिर आप कक्षा के साथ काम कर सकते हैं - निर्माता को कॉल करें या स्थैतिक तरीकों का आह्वान करें।

जेनेरिक कार्यों गैर सामान्य कक्षाओं में कुछ अनिर्दिष्ट "प्रकार-पैरामीटर" और कुछ अनिर्दिष्ट "मूल्य-मानकों" के साथ कार्य हैं। परिणाम प्राप्त करने के लिए आप प्रकार-तर्क और मूल्य-तर्क प्रदान करते हैं।

प्रतिनिधियों विशिष्ट कार्यों के लिए पॉइंटर्स हैं। जब आप प्रतिनिधि बनाते हैं तो आप तर्क फ़ंक्शन निर्दिष्ट नहीं करते हैं, लेकिन बाद में उन्हें आपूर्ति करते हैं।

समस्या यह है कि .NET के पास सामान्य जेनेरिक टाइप-पैरामीटर के साथ जेनेरिक फ़ंक्शंस के लिए प्रतिनिधि नहीं हैं। टाइप-पैरामीटर के बाद आप टाइप-वैल्यू की आपूर्ति नहीं कर सकते हैं। हम उन प्रतिनिधियों की कल्पना कर सकते हैं जिनमें न केवल मूल्य पैरामीटर है, बल्कि टाइप-पैरामीटर भी निःशुल्क हैं।

static class SomeClass { 
    //generic function 
    public static T GetValue<T>() { 
     return default(T); 
    } 
} 

//creating delegate to generic function or method group 
Func{TFree}<TFree> valueFactory = SomeClass.GetValue; 

//creating delegate to anonymous generic function 
Func{TFree}<int, List<TFree>> listFactory = {TFree}(int capacity) => new List<TFree>(capacity); 

नीचे एक प्रोग्राम है जो मैं सी # में लिखने के लिए चाहते हैं के लिए [छद्म] कोड है। मैं जानना चाहता हूं कि कोई सही सी # प्रोग्राम में समान व्यवहार कैसे प्राप्त कर सकता है।

हम सी # में मुफ्त सामान्य प्रकार-पैरामीटर के साथ प्रतिनिधियों का अनुकरण कैसे कर सकते हैं?

गैर-जेनेरिक कोड के माध्यम से अभी तक अज्ञात जेनेरिक पैरामीटर के साथ जेनेरिक फ़ंक्शन [ओं] के संदर्भ/लिंक को कैसे पास कर सकते हैं?

public static class Factory { //Everything compiles fine here 
    public delegate ICollection<T> FactoryDelegate<T>(IEnumerable<T> values); 

    public static ICollection<T> CreateList<T>(IEnumerable<T> values) { 
     return new List<T>(values); 
    } 

    public static ICollection<T> CreateSet<T>(IEnumerable<T> values) { 
     return new HashSet<T>(values); 
    } 
} 

public class Worker { //non-generic class 
    Func{TFree}<FactoryDelegate<TFree>> _factory; //TFree is a "free" generic type paramenter 

    public Worker(Func{TFree}<FactoryDelegate<TFree>> factory) { 
     _factory = factory; 
    } 

    public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method 
     return _factory{T}(values); //supplying T as the argument for type parameter TFree 
    } 
} 

public static class Program { 
    public static void Main() { 
     string[] values1 = new string[] { "a", "b", "c" }; 
     int[] values2 = new int[] { 1, 2, 2, 2 }; 

     Worker listWorker = new Worker(Factory.CreateList); //passing reference to generic function 
     Worker setWorker = new Worker(Factory.CreateSet); //passing reference to generic function 

     ICollection<string> result1 = listWorker.DoWork(values1); 
     ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 
     ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 
    } 
} 

कैसे हम प्रकार तर्क निर्दिष्ट किए बिना कार्यकर्ता वर्ग निर्माता के लिए सामान्य कार्य (Factory.CreateList और Factory.CreateSet) के लिए संदर्भ पारित देखें?तर्क टाइप करें बाद में आपूर्ति की जाती है जब सामान्य DoWork फ़ंक्शन को ठोस-टाइप किए गए सरणी के साथ बुलाया जाता है। सही कार्य का चयन करने के लिए DoWork प्रकार-तर्क का उपयोग करता है, मान-तर्क पास करता है और प्राप्त मान देता है।

अंतिम समाधान:Emulating delegates with free generic type parameters in C#

+0

'ओपन टाइप' और विशेष रूप से 'टाइप कन्स्ट्रक्टर' शब्द देखें। – SLaks

+0

मुझे नहीं लगता कि सी # का प्रतिबिंब या वर्कर जेनेरिक बनाने के बिना ऐसा करने का कोई तरीका है। मुझे संदेह है कि _higher- kinded type_, जो .Net समर्थन नहीं करता है, मदद करेगा। – SLaks

+1

मुझे यकीन नहीं है, लेकिन क्या आपने इसके बजाय F # देखा है? सी # को अधिक कार्यात्मक होने के लिए मजबूर करना शायद दर्दनाक निष्पादन होगा। –

उत्तर

7

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

यहाँ एक वैध सी # कार्यक्रम में अपने उदाहरण फिर से काम किया है:

public interface IWorker 
{ 
    ICollection<T> DoWork<T>(IEnumerable<T> values); 
} 

public class ListCreationWorker : IWorker 
{ 
    public ICollection<T> DoWork<T>(IEnumerable<T> values) 
    { 
     return Factory.CreateList<T>(values); 
    } 
} 

public class SetCreationWorker : IWorker 
{ 
    public ICollection<T> DoWork<T>(IEnumerable<T> values) 
    { 
     return Factory.CreateSet<T>(values); 
    } 
} 

public static class Program { 
    public static void Main(string[] args) { 
     string[] values1 = new string[] { "a", "b", "c" }; 
     int[] values2 = new int[] { 1, 2, 2, 2 }; 

     IWorker listWorker = new ListCreationWorker(); 
     IWorker setWorker = new SetCreationWorker(); 

     ICollection<string> result1 = listWorker.DoWork(values1); 
     ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 
     ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 
    } 
} 

public static class Factory 
{ 
    public static ICollection<T> CreateSet<T>(IEnumerable<T> values) 
    { 
     return new HashSet<T>(values); 
    } 

    public static ICollection<T> CreateList<T>(IEnumerable<T> values) 
    { 
     return new List<T>(values); 
    } 
} 

तुम अब भी जो विधि के लिए निर्णय को अलग करने का महत्वपूर्ण विशेषता प्राप्त कहा विधि के निष्पादन से कॉल करें।

एक चीज जो आप नहीं कर सकते हैं, हालांकि, किसी भी सामान्य स्थिति में IWorker कार्यान्वयन में किसी भी राज्य को स्टोर किया जाता है। मुझे यकीन नहीं है कि यह कैसे उपयोगी हो सकता है क्योंकि DoWork विधि को हर बार विभिन्न प्रकार के तर्कों के साथ बुलाया जा सकता है।

+0

एफवाईआई, मुझे लगता है कि आपने अपनी फैक्ट्री विधि कॉल फिसल दी है। 'ListCreationWorker'' फैक्ट्री। क्रिएटसेट 'को कॉल कर रहा है, और' SetCreationWorker' 'फैक्ट्री। क्रिएटलिस्ट' कह रहा है। –

+0

@ChrisSinclair, हाँ, अभी तय किया गया है। –

+0

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

2

यह वास्तव में नेट के प्रकार प्रणाली के तहत मतलब नहीं है।

क्या आप का वर्णन कर रहे हैं एक प्रकार निर्माता – एक "समारोह" जो एक या अधिक प्रकार लेता है और एक ठोस (पैरामिट्रीकृत, या बंद) टाइप रिटर्न है।

समस्या यह है कि प्रकार के निर्माता स्वयं प्रकार नहीं हैं। आपके पास खुले प्रकार का ऑब्जेक्ट या वैरिएबल नहीं हो सकता है; प्रकार कन्स्ट्रक्टर का उपयोग केवल ठोस प्रकार उत्पन्न करने के लिए किया जा सकता है।

दूसरे शब्दों में, नेट के प्रकार सिस्टम के भीतर एक खुले फ़ंक्शन के संदर्भ का प्रतिनिधित्व करने का कोई तरीका नहीं है।


सबसे अच्छा आप प्रतिबिंब का उपयोग करना कर सकते हैं; एक MethodInfo एक खुली जेनेरिक विधि का वर्णन कर सकता है।
आप एक सामान्य विधि है कि एक नकली जेनेरिक पैरामीटर के साथ एक अभिव्यक्ति पेड़ लेता लिख ​​कर एक खुला MethodInfo को एक संकलन समय प्रकार- सुरक्षित संदर्भ प्राप्त कर सकते हैं:

public MethodInfo GetMethod<TPlaceholder>(Expression<Action> method) { 
    //Find the MethodInfo and remove all TPlaceholder parameters 
} 

GetMethod<string>(() => SomeMethod<string>(...)); 

TPlaceholder पैरामीटर मामले में आवश्यक है कि आप करना चाहते हैं उस पैरामीटर पर एक बाधा के साथ एक खुली जेनेरिक विधि का संदर्भ लें; आप एक प्लेसहोल्डर प्रकार चुन सकते हैं जो बाधा को पूरा करता है।

+0

ठीक है, मैं खुले प्रकारों का उपयोग कर सकता हूं 'टाइप ओपनटाइप = टाइपऑफ (सूची <>) '। यह एक खुले प्रकार की तरह दिखता है। मैं खुले प्रकारों से कंक्रीट प्रकार भी बना सकता हूं: 'टाइप टाइप टाइप टाइप करें (फनक <,>)। मैकजेनरिक टाइप (टाइपोफ (इंट), टाइपोफ (स्ट्रिंग))' –

+0

@ आर्क-कुन: आप प्रतिबिंब का उपयोग करके खुले प्रकार का प्रतिनिधित्व कर सकते हैं, लेकिन प्रकार प्रणाली के प्रकार के रूप में नहीं। 'Func <>' केवल 'typeof()' में मान्य है। इसी तरह, आप केवल 'विधिInfo' के रूप में खुली विधियों का प्रतिनिधित्व कर सकते हैं। – SLaks

+0

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

2

समाधान इंटरफेस है। जैसा कि @ माइक-जेड लिखा है, इंटरफेस जेनेरिक तरीकों का समर्थन करते हैं। इसलिए, हम जेनेरिक विधि के साथ गैर-जेनेरिक इंटरफ़ेस IFactory बना सकते हैं जो किसी वर्ग में सामान्य विधि के संदर्भ को समाहित करता है। इस तरह के इंटरफ़ेस का उपयोग करके [फैक्टरी] कक्षा की जेनेरिक विधि को बांधने के लिए हमें आमतौर पर IFactory इंटरफेस को लागू करने वाले छोटे वर्गों को बनाने की आवश्यकता होती है। वे लैम्ब्स द्वारा उपयोग किए जाने वाले बंद होने की तरह कार्य करते हैं।

मुझे इस और जेनेरिक विधि प्रतिनिधियों के बीच बड़ा अर्थपूर्ण अंतर दिखाई नहीं देता है जिसे मैंने पूछा है।समाधान लैम्ब्डा के लिए क्या संकलक करता है [यह सिर्फ अन्य तरीकों को कॉल करें] (कॉल करने वाली विधि के साथ बंद करें)।

हम क्या खो रहे हैं? ज्यादातर सिंटेक्टिक चीनी।

  • बेनामी फ़ंक्शन/लैम्बडास। हम सामान्य लैम्बडा नहीं बना सकते हैं। अज्ञात वर्ग (जैसे जावा में) बनाने में सक्षम होने से समस्या हल हो गई होगी। लेकिन लैम्बडास के साथ शुरू करने में कोई समस्या नहीं है। नेट में सिंटैक्टिक चीनी हैं।

  • विधि समूह (सी # अवधि) से निहित रूप से प्रतिनिधि/लिंक बनाने की क्षमता। यदि हम सामान्य हैं तो हम विधि समूह का किसी भी तरह से उपयोग नहीं कर सकते हैं। यह अर्थशास्त्र को भी प्रभावित नहीं करता है।

  • जेनेरिक प्रतिनिधियों को परिभाषित करने की क्षमता में बाधा आ गई है। हम विधि V<T> Create<T>(U<T> arg) के साथ सामान्य IFactory<U, V> इंटरफ़ेस नहीं बना सकते हैं। यह भी एक समस्या नहीं है।

यह समाधान का कोड है। प्रश्न से Factory वर्ग अपरिवर्तित है।

public interface IFactory { 
    ICollection<T> Create<T>(IEnumerable<T> values); 
} 

public class Worker { //not generic 
    IFactory _factory; 

    public Worker(IFactory factory) { 
     _factory = factory; 
    } 

    public ICollection<T> DoWork<T>(IEnumerable<T> values) { //generic method 
     return _factory.Create<T>(values); 
    } 
} 

public static class Program { 
    class ListFactory : IFactory { 
     public ICollection<T> Create<T>(IEnumerable<T> values) { 
      return Factory.CreateList(values); 
     } 
    } 

    class SetFactory : IFactory { 
     public ICollection<T> Create<T>(IEnumerable<T> values) { 
      return Factory.CreateSet(values); 
     } 
    } 

    public static void Main() { 
     string[] values1 = new string[] { "a", "b", "c" }; 
     int[] values2 = new int[] { 1, 2, 2, 2 }; 

     Worker listWorker = new Worker(new ListFactory()); 
     Worker setWorker = new Worker(new SetFactory()); 

     ICollection<string> result1 = listWorker.DoWork(values1); 
     ICollection<int> result2 = listWorker.DoWork(values2); //.Count == 4 
     ICollection<int> result3 = setWorker.DoWork(values2); //.Count == 2 
    } 
} 
+0

यह बहुत बुरा है कि इस तरह के किसी भी संरचना के लिए कोई वाक्य रचनात्मक चीनी नहीं है, क्योंकि जेनेरिक इंटरफेस किसी प्रतिनिधि और/या बंद में पैरामीटर को लपेटने के बिना, एक अभिनेता ऑब्जेक्ट और उसके उचित पैरामीटर को स्वीकार करने की विधि को अनुमति दे सकता है। वे 'रेफरी पैरामीटर' भी पास कर सकते हैं, जबकि बंद नहीं हो सकते हैं। – supercat

+1

@ बंद करके सुपरस्पैट, आपका मतलब लैम्ब्डा अभिव्यक्ति है? लैम्बडास 'रेफरी' और 'आउट' पैरामीटर का समर्थन करते हैं। हालांकि, आपको इसे एक उचित प्रतिनिधि को असाइन करना होगा, जिसे आपको परिभाषित करना होगा। वे अंतर्निहित 'एक्शन' या 'फंक' प्रतिनिधियों के साथ संगत नहीं हैं। –

+0

@ माइकज़: दिए गए 'एक्टिव एक्टबीरफ (रेफरी टी 1 पी 1)', 'शून्य डूसमिंग (एक्शन प्रो) 'को' शून्य डूसमिंग (आईएक्टबीरफ प्रो, रेफ टीपी पी 1) के साथ बदलकर; 'कोड को अनुमति देगा जो एक क्रिया करना चाहता है स्थानीय वैरिएबल का उपयोग एक वैधानिक विधि में एक प्रतिनिधि को पास करने के लिए एक वैरिएबल को एक रेफरी के साथ पास करने के लिए, उन चरों को पकड़ने के लिए एक ढेर ऑब्जेक्ट बनाने की आवश्यकता से बचने के लिए और दूसरे को एक नए प्रतिनिधि को पकड़ने के लिए (स्थिति के लिए जहां बुलाया गया तरीका प्रतिनिधि और संबंधित चर को जारी रखना नहीं होगा)। वह हिस्सा प्रतिनिधियों के साथ भी काम करता है, लेकिन ... – supercat