2010-06-25 9 views
18

मैंने किसी अन्य धागे में चर्चा की, और पाया कि वर्ग विधियों को समान नाम और पैरामीटर के साथ विस्तार विधियों पर प्राथमिकता दी गई है। इस विस्तार के तरीकों के तरीकों का अपहरण नहीं होगा के रूप में अच्छा है, लेकिन यह मान आप किसी तृतीय पक्ष पुस्तकालय के लिए कुछ विस्तार तरीकों को शामिल किया है:वर्ग द्वारा ओवरराइड किए गए एक्सटेंशन विधियों को कोई चेतावनी नहीं देता

public class ThirdParty 
{ 
} 

public static class ThirdPartyExtensions 
{ 
    public static void MyMethod(this ThirdParty test) 
    { 
     Console.WriteLine("My extension method"); 
    } 
} 

वर्क्स के रूप में उम्मीद: ThirdParty.MyMethod -> "मेरे विस्तार विधि"

लेकिन फिर तीसरे पक्ष के अपडेट यह पुस्तकालय है और वास्तव में अपने विस्तार विधि की तरह एक विधि कहते हैं:

public class ThirdParty 
{ 
    public void MyMethod() 
    { 
     Console.WriteLine("Third party method"); 
    } 
} 

public static class ThirdPartyExtensions 
{ 
    public static void MyMethod(this ThirdParty test) 
    { 
     Console.WriteLine("My extension method"); 
    } 
} 

ThirdPart.MyMethod -> "तृतीय पक्ष विधि"

अब अचानक कोड रनटाइम पर अलग व्यवहार करेगा क्योंकि तीसरे पक्ष की विधि ने आपकी विस्तार विधि को "अपहृत" कर दिया है! कंपाइलर कोई चेतावनी नहीं देता है।

क्या ऐसी चेतावनियां सक्षम करने का कोई तरीका है या अन्यथा इससे बचें?

+0

मुझे इससे भी नफरत है .... :( – leppie

उत्तर

10

नहीं - यह विस्तार विधियों का एक ज्ञात नकारात्मक हिस्सा है, और कुछ बहुत सावधान रहना है। व्यक्तिगत रूप से मैं चाहता हूं कि सी # कंपाइलर आपको चेतावनी देगा यदि आपने एक विस्तार विधि घोषित की है जिसे सामान्य स्थिर मार्ग (ExtensionClassName.MethodName(target, ...)) के माध्यम से कभी भी नहीं कहा जाएगा।

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

संपादित करें: ठीक है ... यहां एक बहुत क्रूड उपकरण कम से कम एक प्रारंभिक बिंदु देने के लिए है। ऐसा लगता है कि कम से कम कुछ हद तक सामान्य प्रकार के साथ काम करता है - लेकिन यह पैरामीटर प्रकारों या नामों के साथ कुछ भी करने की कोशिश नहीं कर रहा है ... आंशिक रूप से क्योंकि यह पैरामीटर सरणी के साथ मुश्किल हो जाता है। यह केवल प्रतिबिंब के बजाय असेंबली को "पूरी तरह से" लोड करता है, जो कि अच्छा होगा - मैंने "उचित" मार्ग की कोशिश की, लेकिन कुछ समस्याओं में भाग गया जो तुरंत हल करने के लिए तुच्छ नहीं थे, इसलिए त्वरित और गंदे मार्ग पर वापस आ गए:)

वैसे भी, उम्मीद है कि यह किसी के लिए कहीं उपयोगी होगा।

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Runtime.CompilerServices; 

public class ExtensionCollisionDetector 
{ 
    private static void Main(string[] args) 
    { 
     if (args.Length == 0) 
     { 
      Console.WriteLine 
       ("Usage: ExtensionCollisionDetector <assembly file> [...]"); 
      return; 
     } 
     foreach (string file in args) 
     { 
      Console.WriteLine("Testing {0}...", file); 
      DetectCollisions(file); 
     } 
    } 

    private static void DetectCollisions(string file) 
    { 
     try 
     { 
      Assembly assembly = Assembly.LoadFrom(file); 
      foreach (var method in FindExtensionMethods(assembly)) 
      { 
       DetectCollisions(method); 
      } 
     } 
     catch (Exception e) 
     { 
      // Yes, I know catching exception is generally bad. But hey, 
      // "something's" gone wrong. It's not going to do any harm to 
      // just go onto the next file. 
      Console.WriteLine("Error detecting collisions: {0}", e.Message); 
     } 
    } 

    private static IEnumerable<MethodBase> FindExtensionMethods 
     (Assembly assembly) 
    { 
     return from type in assembly.GetTypes() 
       from method in type.GetMethods(BindingFlags.Static | 
               BindingFlags.Public | 
               BindingFlags.NonPublic) 
       where method.IsDefined(typeof(ExtensionAttribute), false) 
       select method; 
    } 


    private static void DetectCollisions(MethodBase method) 
    { 
     Console.WriteLine(" Testing {0}.{1}", 
          method.DeclaringType.Name, method.Name); 
     Type extendedType = method.GetParameters()[0].ParameterType; 
     foreach (var type in GetTypeAndAncestors(extendedType).Distinct()) 
     { 
      foreach (var collision in DetectCollidingMethods(method, type)) 
      { 
       Console.WriteLine(" Possible collision in {0}: {1}", 
            collision.DeclaringType.Name, collision); 
      } 
     } 
    } 

    private static IEnumerable<Type> GetTypeAndAncestors(Type type) 
    { 
     yield return type; 
     if (type.BaseType != null) 
     { 
      // I want yield foreach! 
      foreach (var t in GetTypeAndAncestors(type.BaseType)) 
      { 
       yield return t; 
      } 
     } 
     foreach (var t in type.GetInterfaces() 
           .SelectMany(iface => GetTypeAndAncestors(iface))) 
     { 
      yield return t; 
     }   
    } 

    private static IEnumerable<MethodBase> 
     DetectCollidingMethods(MethodBase extensionMethod, Type type) 
    { 
     // Very, very crude to start with 
     return type.GetMethods(BindingFlags.Instance | 
           BindingFlags.Public | 
           BindingFlags.NonPublic) 
        .Where(candidate => candidate.Name == extensionMethod.Name); 
    } 
} 
+0

वोट प्राप्त करने के बाद मुझे जवाब मिल जाएगा –

+0

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

+1

@Dan: समस्या यह है कि कंपाइलर कोई चेतावनी नहीं देता है। यह खुशी से आपके कोड को विस्तार विधि – simendsjo

0

इस घटना के मौके को कम करने का एक संभावित तरीका एक्सटेंशन विधि नाम के अंत में संक्षिप्त नाम शामिल करना है। मुझे पता है, यह बहुत बदसूरत है, लेकिन यह इस घटना की संभावनाओं को कम करता है।

MyMethod_Ext 

या

MyMethodExt 
+2

हाँ, काफी बदसूरत। यह विस्तार वर्ग को स्वयं कॉल करने के रूप में लगभग बदसूरत है: थर्डपार्टी एक्सटेंशन। MyMethod (उदाहरण) – simendsjo

+0

हाँ, काफी, काफी बदसूरत। मैंने सोचा कि मैं इसे एक संभावना के रूप में बाहर रखूंगा। पहली बार यह घटित होने की संभावना बहुत कम है। यह सिर्फ आगे की संभावनाओं को कम कर देता है। मुझे जॉन स्कीट के इन संभावनाओं की जांच करने के लिए उपयोगिता के विचार पसंद हैं। –

2

मैं जॉन के जवाब की तरह है, लेकिन वहाँ एक और डैनियल के समान दृष्टिकोण है। यदि आपके पास बहुत सारी एक्सटेंशन विधियां हैं, तो आप एक प्रकार के "नेमस्पेस" को परिभाषित कर सकते हैं। यह सबसे अच्छा काम करता है यदि आपके पास काम करने के लिए एक स्थिर इंटरफेस है (यानी, अगर आपको पता था कि IThirdParty बदलने वाला नहीं था)। हालांकि, आपके मामले में, आपको एक रैपर वर्ग की आवश्यकता होगी।

मैंने इसे पथ पथ के रूप में तारों के इलाज के तरीकों को जोड़ने के लिए किया है।मैंने FileSystemPath प्रकार को परिभाषित किया है जो string को लपेटता है और IsAbsolute और ChangeExtension जैसे गुण और विधियां प्रदान करता है।

जब एक को परिभाषित "विस्तार नाम स्थान," आप इसे दर्ज करने के लिए एक तरह से और यह छोड़ने के लिए एक तरीका प्रदान करने, जैसे की जरूरत है:

// Enter my special namespace 
public static MyThirdParty AsMyThirdParty(this ThirdParty source) { ... } 

// Leave my special namespace 
public static ThirdParty AsThirdParty(this MyThirdParty source) { ... } 

विधि "नाम स्थान" "छोड़" काम कर सकते हैं एक विस्तार विधि के बजाय एक उदाहरण विधि के रूप में बेहतर है। मेरा FileSystemPath सिर्फ string पर एक निहित रूपांतरण है, लेकिन यह सभी मामलों में काम नहीं करता है।

आप MyThirdPartyThirdParty के सभी वर्तमान में परिभाषित सदस्यों के साथ ही विस्तार तरीकों (लेकिन भविष्य से परिभाषित नहीं ThirdParty के सदस्य) करना चाहते हैं, तो आप लिपटे ThirdParty वस्तु को सदस्य कार्यान्वयन अग्रेषित करने के लिए होगा। यह थकाऊ हो सकता है, लेकिन रीशेपर जैसे टूल्स इसे अर्द्ध स्वचालित रूप से कर सकते हैं।

अंतिम नोट: नामस्थान में प्रवेश/छोड़ने पर "जैसा" उपसर्ग एक प्रकार का अस्पष्ट दिशानिर्देश है। LINQ इस प्रणाली का उपयोग करता है (उदा।, AsEnumerable, AsQueryable, AsParallel वर्तमान "नामस्थान" छोड़ें और दूसरा दर्ज करें)।

मैंने इस वर्ष की शुरुआत में a blog post लिखा था जिसे मैं "विस्तार-आधारित प्रकार" कहता हूं। उदाहरण विधियों को ओवरराइड करने में असमर्थता की तुलना में अधिक नुकसान हैं। हालांकि, यह एक बहुत ही रोचक अवधारणा है।

संबंधित मुद्दे