2010-06-04 12 views
39

मैं निम्नलिखित मामलों के बीच अंतर करना चाहते हैं:जेनेरिक की कमी है, जहां टी: struct और जहां टी: वर्ग

  1. एक सादे मान प्रकार (जैसे int)
  2. एक नल मान प्रकार (जैसे int?)
  3. एक संदर्भ प्रकार (जैसे string) - वैकल्पिक रूप से, मुझे परवाह नहीं है अगर यह करने के लिए मैप किया गया (1) या (2)
  4. ऊपर

मैं निम्नलिखित सी के साथ आए हैं स्तोत्र, जो मामलों के लिए ठीक काम करता है (1) और (2):

static void Foo<T>(T a) where T : struct { } // 1 

static void Foo<T>(T? a) where T : struct { } // 2 

हालांकि, अगर मैं मामले का पता लगाने की कोशिश करते हैं (3) इस तरह, यह संकलन नहीं करता है:

static void Foo<T>(T a) where T : class { } // 3 

त्रुटि संदेश टाइप करें 'एक्स' पहले से ही पैरामीटर प्रकार के साथ 'फू' नामक एक सदस्य को परिभाषित करता है। खैर, किसी भी तरह से मैं where T : struct और where T : class के बीच कोई अंतर नहीं बना सकता।

अगर मैं तीसरे समारोह (3) निकालने के लिए, निम्न कोड या तो संकलन नहीं करता है:

int x = 1; 
int? y = 2; 
string z = "a"; 

Foo (x); // OK, calls (1) 
Foo (y); // OK, calls (2) 
Foo (z); // error: the type 'string' must be a non-nullable value type ... 

मैं कैसे प्राप्त कर सकते हैं Foo(z) संकलित करने के लिए, ऊपर कार्यों में से एक (या एक तिहाई से एक के लिए यह मानचित्रण एक और बाधा के साथ, जिसे मैंने नहीं सोचा है)?

+0

संदर्भ प्रकारों के लिए है: नई(), लेकिन इस नल मूल्य प्रकार के साथ अजीब व्यवहार है। –

उत्तर

36

बाधाएं हस्ताक्षर का हिस्सा नहीं हैं, लेकिन पैरामीटर हैं। और पैरामीटर में बाधाओं को ओवरलोड रिज़ॉल्यूशन के दौरान लागू किया जाता है।

तो चलिए एक पैरामीटर में बाधा डालें। यह बदसूरत है, लेकिन यह काम करता है।

class RequireStruct<T> where T : struct { } 
class RequireClass<T> where T : class { } 

static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1 
static void Foo<T>(T? a) where T : struct { } // 2 
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3 

(? बेहतर छह साल देर से कभी नहीं की तुलना में)

+2

हा, अच्छा विचार! असल में, आपको 'ignoo' पैरामीटर को दूसरे' Foo ''T' लेते हुए 'फ़ंक्शन' जोड़ने की आवश्यकता नहीं है। –

+0

इसने मुझे http://code.fitness/post/2016/04/generic-type-resolution.html –

+0

पर विषय के बारे में ब्लॉग करने का मौका दिया, मुझे [एरिक लिपर्ट के ब्लॉग पोस्टों में से एक] (https: //blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/)। मुझे हमेशा शेनानिगन्स पसंद आया है। टी के लिए, जिस स्थिति में मुझे इसकी आवश्यकता थी केवल मामलों में 1 और 3 था, और यदि आवश्यक हो तो मैं परीक्षण भूल गया। – Alcaro

17

दुर्भाग्यवश, आप केवल बाधाओं पर आधारित कॉल करने के लिए विधि के प्रकार को अलग नहीं कर सकते हैं।

तो आपको एक अलग वर्ग में या इसके बजाय एक अलग नाम के साथ एक विधि को परिभाषित करने की आवश्यकता है।

+2

+1। बेशक, पहला और दूसरा काम क्योंकि 'टी' और' टी? 'अलग-अलग तर्क हैं। ('टी' और' न्यूबल योग्य ') – Powerlord

+0

+1 देखें: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-ignature .aspx –

+0

आपके त्वरित उत्तर के लिए धन्यवाद; अगर मैं प्रकारों को अलग नहीं कर सकता, तो क्या कुछ बाधा को आराम करके संकलन करने के लिए मेरा आखिरी उदाहरण प्राप्त करने का कोई तरीका है? –

5

पहली विधि पर संरचना की कमी को छोड़ दें। यदि आपको मूल्य प्रकारों और कक्षाओं के बीच अंतर करने की आवश्यकता है तो आप ऐसा करने के लिए तर्क के प्रकार का उपयोग कर सकते हैं।

 static void Foo(T? a) where T : struct 
     { 
     // nullable stuff here 
     } 

     static void Foo(T a) 
     { 
     if(a is ValueType) 
     { 
      // ValueType stuff here 
     } 
     else 
     { 
      // class stuff 
     } 
     } 
+2

@ मैक्सिम: धन्यवाद। जिस समस्या का सामना कर रहा हूं वह यह है कि गैर-अनुपयोगी विधि में, मुझे उन अन्य कार्यों का आह्वान करने में सक्षम होना चाहिए जो 'टी? 'लेते हैं और वापस लौटते हैं, और यह' टी: संरचना 'बाधा के बिना मान्य नहीं है। –

10

इसके अलावा Marnix's answer पर आपकी टिप्पणी का, आप प्रतिबिंब का एक सा का उपयोग करके प्राप्त कर सकते हैं कि आप क्या चाहते।

नीचे दिए गए उदाहरण में, स्वेच्छापूर्ण Foo<T> विधि प्रतिबिंब का उपयोग करता है उचित विवश विधि के लिए कॉल बाहर खेती के लिए - या तो FooWithStruct<T> या FooWithClass<T>। प्रदर्शन कारणों से हम Foo<T> विधि को हर बार सादे प्रतिबिंब का उपयोग करने के बजाय दृढ़ता से टाइप किए गए प्रतिनिधि को बनाए और कैश करेंगे।

int x = 42; 
MyClass.Foo(x); // displays "Non-Nullable Struct" 

int? y = 123; 
MyClass.Foo(y); // displays "Nullable Struct" 

string z = "Test"; 
MyClass.Foo(z); // displays "Class" 

// ... 

public static class MyClass 
{ 
    public static void Foo<T>(T? a) where T : struct 
    { 
     Console.WriteLine("Nullable Struct"); 
    } 

    public static void Foo<T>(T a) 
    { 
     Type t = typeof(T); 

     Delegate action; 
     if (!FooDelegateCache.TryGetValue(t, out action)) 
     { 
      MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo; 
      action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t)); 
      FooDelegateCache.Add(t, action); 
     } 
     ((Action<T>)action)(a); 
    } 

    private static void FooWithStruct<T>(T a) where T : struct 
    { 
     Console.WriteLine("Non-Nullable Struct"); 
    } 

    private static void FooWithClass<T>(T a) where T : class 
    { 
     Console.WriteLine("Class"); 
    } 

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static); 
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static); 
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>(); 
} 

(ध्यान दें कि इस उदाहरण threadsafe नहीं है आप धागे की सुरक्षा की आवश्यकता होती है, तो आप या तो कैश शब्दकोश की असीमित एक्सेस के आसपास ताला लगा के कुछ प्रकार का उपयोग करना होगा, या -। यदि आप ' .NET4 को लक्षित करने में सक्षम हैं - इसके बजाय ConcurrentDictionary<K,V> का उपयोग करें।)

+1

'तुलनाकर्ता डीफॉल्ट' जैसे किसी दृष्टिकोण का उपयोग करके चीजों को बेहतर बना सकता है, उदा। एक निजी स्थैतिक जेनेरिक क्लास 'FooInvoker ' सार्वजनिक क्षेत्र 'FooMethod' प्रकार' एक्शन 'के साथ '(चूंकि' FooInvoker '' MyClass' के बाहर पहुंच योग्य नहीं होगा, सार्वजनिक क्षेत्र का दुरुपयोग करने वाले बाहरी कोड का कोई खतरा नहीं होगा)? यदि 'FooInvoker ' के लिए क्लास कन्स्ट्रक्टर उचित रूप से 'FooMethod' सेट करता है, तो मुझे लगता है कि रनटाइम पर एक शब्दकोश लुकअप की आवश्यकता से बच सकता है (मुझे नहीं पता कि क्या।नेट को प्रत्येक बार 'Foo ' कहा जाता था) को आंतरिक रूप से करने की आवश्यकता होगी। – supercat

+1

मेरे पोस्ट किए गए उत्तर को एक रूपरेखा के लिए देखें कि कोई स्थिर वर्ग का उपयोग कैसे करेगा। मैंने शायद कुछ वाक्यविन्यास त्रुटियां की हैं, क्योंकि मैं स्मृति से टाइप कर रहा हूं (और अधिकतर vb.net में प्रोग्राम), लेकिन आपको जाने के लिए पर्याप्त रूपरेखा होनी चाहिए। – supercat

2

ल्यूकएच को मेरी टिप्पणी में वृद्धि, एक उपयोगी पैटर्न अगर किसी को एक प्रकार पैरामीटर (जैसा ऑब्जेक्ट इंस्टेंस के प्रकार से अलग है) के आधार पर अलग-अलग कार्रवाइयों का आह्वान करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता होगी, तो एक निजी सामान्य स्थिर वर्ग बनाना निम्नलिखित की तरह (इस सटीक कोड अपरीक्षित है, लेकिन मैं बात की इस तरह किया है से पहले):

 
static class FooInvoker<T> 
{ 
    public Action<Foo> theAction = configureAction; 
    void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T 
    { 
    ... 
    } 
    void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T 
    { 
    ... 
    } 
    void configureAction(T param) 
    { 
    ... Determine which kind of thing T is, and set `theAction` to one of the 
    ... above methods. Then end with ... 
    theAction(param); 
    } 
} 

ध्यान दें कि प्रतिबिंब अगर एक ActionForOneKindOfThing<TT>(TT param) के लिए एक प्रतिनिधि बनाने के लिए जब TT पालन नहीं करता है प्रयास करता है एक अपवाद फेंक होगा उस विधि की बाधाओं के साथ। चूंकि प्रतिनिधि ने TT के प्रकार को सत्यापित किया था जब प्रतिनिधि बनाया गया था, तो कोई भी आगे टाइप-चेकिंग के बिना सुरक्षित रूप से theAction का आह्वान कर सकता है। ध्यान दें कि यदि बाहरी कोड करता है:

 
    FooInvoker<T>.theAction(param); 

केवल पहले कॉल को किसी भी प्रतिबिंब की आवश्यकता होगी। बाद की कॉल सीधे प्रतिनिधि को सीधे आमंत्रित करेंगे।

1

यदि आपको जेनेरिक पैरामीटर की आवश्यकता नहीं है और केवल इन 3 मामलों के बीच संकलन समय पर अंतर करना चाहते हैं तो आप निम्न कोड का उपयोग कर सकते हैं।

static void Foo(object a) { } // reference type 
static void Foo<T>(T? a) where T : struct { } // nullable 
static void Foo(ValueType a) { } // valuetype 
संबंधित मुद्दे