सी # जेनरिक विधि चयन


9

मैं सी # में जेनेरिक एल्गोरिदम लिखने की कोशिश कर रहा हूं जो विभिन्न आयामों की ज्यामितीय संस्थाओं के साथ काम कर सकता है।

निम्नलिखित आकस्मिक उदाहरण में मेरे पास है Point2और Point3, दोनों एक सरल IPointइंटरफ़ेस को लागू कर रहे हैं ।

अब मेरे पास एक फ़ंक्शन है GenericAlgorithmजो एक फ़ंक्शन को कॉल करता है GetDim। प्रकार के आधार पर इस फ़ंक्शन की कई परिभाषाएं हैं। एक फॉल-बैक फ़ंक्शन भी है जो किसी भी चीज के लिए परिभाषित किया गया है जो लागू होता है IPoint

मुझे शुरू में उम्मीद थी कि निम्न प्रोग्राम का आउटपुट 2, 3 होगा। हालाँकि, यह 0, 0 है।

interface IPoint {
    public int NumDims { get; } 
}

public struct Point2 : IPoint {
    public int NumDims => 2;
}

public struct Point3 : IPoint {
    public int NumDims => 3;
}

class Program
{
    static int GetDim<T>(T point) where T: IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 0 !!
        Console.WriteLine("{0:d}", d2);        // returns 0 !!
    }
}

ठीक है, इसलिए किसी कारण से ठोस प्रकार की जानकारी खो जाती है GenericAlgorithm। मुझे पूरी तरह से समझ में नहीं आता कि ऐसा क्यों होता है, लेकिन ठीक है। अगर मैं इसे इस तरह से नहीं कर सकता, तो मेरे पास और क्या विकल्प हैं?


2
"एक फॉल-बैक फ़ंक्शन भी है" इसका उद्देश्य क्या है, बिल्कुल? इंटरफ़ेस को लागू करने का पूरा बिंदु यह गारंटी देना है कि NumDimsसंपत्ति उपलब्ध है। आप कुछ मामलों में इसकी अनदेखी क्यों कर रहे हैं?
जॉन वू

तो यह संकलन करता है, मूल रूप से। शुरू में, मुझे लगा कि अगर JIT कंपाइलर के लिए एक विशेष कार्यान्वयन नहीं मिल सकता है GetDim(यानी मैं पास नहीं होता है, Point4लेकिन फॉल बैक फंक्शन आवश्यक है GetDim<Point4>। हालाँकि, ऐसा प्रतीत नहीं होता है कि संकलक किसी विशेष कार्यान्वयन के लिए परेशान हैं।
मोहम्मदसॉ

1
@woggy: आप कहते हैं कि "यह किसी विशेष कार्यान्वयन की तलाश करने के लिए संकलक को परेशान नहीं करता है" जैसे कि यह डिजाइनरों और कार्यान्वयनकर्ताओं की ओर से आलस्य का मामला था। यह। यह एक बात है कि कैसे .NET में जेनरिक का प्रतिनिधित्व किया जाता है। यह सी ++ में अस्थायी रूप में विशेषज्ञता का एक ही प्रकार नहीं है। प्रत्येक प्रकार के तर्क के लिए एक सामान्य विधि अलग से संकलित नहीं की जाती है - यह एक बार संकलित की जाती है। इस के पेशेवरों और विपक्ष हैं, निश्चित रूप से, लेकिन यह "परेशान" करने की बात नहीं है।
जॉन स्कीट

@jonskeet माफी यदि मेरी भाषा की पसंद खराब थी, तो मुझे यकीन है कि यहां जटिलताएं हैं जिन्हें मैंने नहीं माना है। मेरी समझ थी कि संकलक संदर्भ प्रकारों के लिए अलग-अलग फ़ंक्शन संकलित नहीं करता है, लेकिन यह मूल्य प्रकार / संरचना के लिए करता है, क्या यह सही है?
मुहम्मदमुसा

@woggy: यह JIT -compiler है, जो C # कंपाइलर से पूरी तरह से अलग मामला है - और यह C # कंपाइलर है जो ओवरलोड रिज़ॉल्यूशन करता है। जेनेरिक विधि के लिए आईएल केवल एक बार उत्पन्न होता है - विशेषज्ञता के अनुसार एक बार नहीं।
जॉन स्कीट

जवाबों:


10

यह विधि:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

... हमेशा फोन करेंगे GetDim<T>(T point)। अधिभार संकल्प संकलन-समय पर किया जाता है , और उस स्तर पर कोई अन्य लागू विधि नहीं है।

यदि आप चाहते हैं कि निष्पादन समय पर अधिभार संकल्प को बुलाया जाए , तो आपको डायनामिक टाइपिंग, उदाहरण के लिए उपयोग करना होगा

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim((dynamic) point);

लेकिन आम तौर पर इसके लिए विरासत का उपयोग करना एक बेहतर विचार है - आपके उदाहरण में, जाहिर है कि आपके पास केवल एक ही तरीका हो सकता है और वापस आ सकता है point.NumDims। मुझे लगता है कि आपके वास्तविक कोड में कुछ कारण है जो करने के लिए समान चालबाज है, लेकिन अधिक संदर्भ के बिना हम यह सलाह नहीं दे सकते कि विशेषज्ञता का प्रदर्शन करने के लिए विरासत का उपयोग कैसे करें। हालांकि ये आपके विकल्प हैं:

  • लक्ष्य के निष्पादन-समय के प्रकार के आधार पर विशेषज्ञता के लिए विरासत (पसंदीदा)
  • निष्पादन-समय अधिभार संकल्प के लिए गतिशील टाइपिंग

वास्तविक स्थिति मैं एक है AxisAlignedBoundingBox2और AxisAlignedBoundingBox3। मेरे पास एक Containsस्थिर विधि है जो यह निर्धारित करने के लिए उपयोग की जाती है कि क्या बक्सों के संग्रह में एक Line2या Line3(जो एक बक्से के प्रकार पर निर्भर करता है) है। दोनों प्रकारों के बीच एल्गोरिथ्म तर्क बिल्कुल समान है, सिवाय इसके कि आयामों की संख्या अलग है। Intersectआंतरिक रूप से कॉल भी हैं जिन्हें सही प्रकार से विशेष करने की आवश्यकता है। मैं वर्चुअल फ़ंक्शन कॉल / डायनामिक से बचना चाहता हूं, यही वजह है कि मैं जेनरिक का उपयोग कर रहा हूं ... बेशक, मैं कोड को कॉपी / पेस्ट कर सकता हूं और आगे बढ़ सकता हूं।
mohamedmoussa

1
@ डॉग: यह केवल एक विवरण से कल्पना करना काफी कठिन है। यदि आप वंशानुक्रम का उपयोग करके ऐसा करने में मदद करना चाहते हैं, तो मेरा सुझाव है कि आप एक न्यूनतम लेकिन पूर्ण उदाहरण के साथ एक नया प्रश्न बनाएं।
जॉन स्कीट

ठीक है, करूँगा, मैं इस उत्तर को अभी के लिए स्वीकार करूँगा क्योंकि ऐसा लगता है कि मैंने एक अच्छा उदाहरण नहीं दिया है।
मुहम्मदमुसा

6

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

interface IPoint {
    int NumDims { get => 0; }
}

एक सामान्य विधि को लागू करने और प्रति IPointकार्यान्वयन ओवरलोड भी Liskov प्रतिस्थापन सिद्धांत (SOLID में L) का उल्लंघन करता है। एल्गोरिदम को प्रत्येक IPointकार्यान्वयन में धकेलना बेहतर होगा , जिसका अर्थ है कि आपको केवल एकल विधि कॉल की आवश्यकता होनी चाहिए:

static int GetDim(IPoint point) => point.NumDims;

3

आगंतुक पैटर्न

dynamicउपयोग के विकल्प के रूप में , आप नीचे दिए गए विज़िटर पैटर्न का उपयोग करना चाह सकते हैं :

interface IPoint
{
    public int NumDims { get; }
    public int Accept(IVisitor visitor);
}

public struct Point2 : IPoint
{
    public int NumDims => 2;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public struct Point3 : IPoint
{
    public int NumDims => 3;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class Visitor : IVisitor
{
    public int Visit(Point2 toVisit)
    {
        return toVisit.NumDims;
    }

    public int Visit(Point3 toVisit)
    {
        return toVisit.NumDims;
    }
}

public interface IVisitor<T>
{
    int Visit(T toVisit);
}

public interface IVisitor : IVisitor<Point2>, IVisitor<Point3> { }

class Program
{
    static int GetDim<T>(T point) where T : IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => point.Accept(new Visitor());

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 2
        Console.WriteLine("{0:d}", d2);        // returns 3
    }
}

1

आप क्लास और इंटरफ़ेस में गेटडिम फ़ंक्शन को परिभाषित क्यों नहीं करते हैं you वास्तव में, आपको गेटडीम फ़ंक्शन को परिभाषित करने की आवश्यकता नहीं है, बस संपत्ति न्यूमेडिम्स का उपयोग करें।

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