C # में सामान्य तर्क की अशक्त या डिफ़ॉल्ट तुलना


288

मेरे पास इस तरह परिभाषित एक सामान्य विधि है:

public void MyMethod<T>(T myArgument)

पहली चीज जो मैं करना चाहता हूं वह यह है कि अगर myArgument का मान उस प्रकार के लिए डिफ़ॉल्ट मान है, तो कुछ इस तरह है:

if (myArgument == default(T))

लेकिन यह संकलित नहीं करता है क्योंकि मैंने गारंटी नहीं दी है कि टी == ऑपरेटर को लागू करेगा। इसलिए मैंने इस पर कोड स्विच किया:

if (myArgument.Equals(default(T)))

अब यह संकलन करता है, लेकिन विफल रहेगा यदि myArgument अशक्त है, जो कि मैं इसके लिए परीक्षण कर रहा हूं, का हिस्सा है। मैं इस तरह एक स्पष्ट अशक्त जाँच जोड़ सकते हैं:

if (myArgument == null || myArgument.Equals(default(T)))

अब यह मुझे बेमानी लगता है। ReSharper यहां तक ​​कि सुझाव दे रहा है कि मैं myArgument == null भाग को myArgument == डिफ़ॉल्ट (T) में बदल दूं, जहां मैंने शुरू किया है। क्या इस समस्या को हल करने का एक बेहतर तरीका है?

मुझे संदर्भ प्रकार और मूल्य प्रकार दोनों का समर्थन करने की आवश्यकता है ।


C # अब नल कंडिशनल ऑपरेटर्स का समर्थन करता है , जो आपके द्वारा दिए गए अंतिम उदाहरण के लिए सिंटैटिक शुगर है। आपका कोड बन जाएगा if (myArgument?.Equals( default(T) ) != null )
जादूगर .07KSU

1
@ Wizard07KSU जो मान प्रकारों के लिए काम नहीं करता है, अर्थात trueकिसी भी मामले में मूल्यांकन करता है क्योंकि Equalswil को हमेशा मूल्य प्रकारों के लिए कहा जाता है क्योंकि इस मामले में myArgumentनहीं हो सकता nullहै और Equals(बूलियन) का परिणाम कभी नहीं होगा null
जैस्पर

समान रूप से मूल्यवान लगभग-डुप्लिकेट (इसलिए बंद करने के लिए मतदान नहीं): क्या ऑपरेटर == को C # में सामान्य प्रकारों पर लागू नहीं किया जा सकता है?
17

जवाबों:


585

मुक्केबाजी से बचने के लिए, समानता के लिए जेनरिक की तुलना करने का सबसे अच्छा तरीका है EqualityComparer<T>.Default। यह सम्मान IEquatable<T>(बॉक्सिंग के बिना) के रूप में अच्छी तरह से object.Equals, और सभी Nullable<T>"उठा" बारीकियों को संभालता है । अत:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

यह मैच होगा:

  • कक्षाओं के लिए अशक्त
  • null (खाली) के लिए Nullable<T>
  • अन्य संरचनाओं के लिए शून्य / गलत / आदि

29
वाह, कितनी खुशी से अस्पष्ट! यह निश्चित रूप से हालांकि, kudos जाने का रास्ता है।
निक फरिना

1
निश्चित रूप से सबसे अच्छा जवाब। इस समाधान का उपयोग करने के लिए पुनर्लेखन के बाद मेरे कोड में कोई भी अस्पष्ट रेखाएं नहीं हैं।
नाथन रिडले

14
बहुत बढ़िया जवाब! इससे भी बेहतर कोड की इस लाइन के लिए एक एक्सटेंशन विधि जोड़ रहा है ताकि आप obj.IsDefaultForType ()
rikoe

2
@nawfal के मामले में Person, p1.Equals(p2)यह इस बात पर निर्भर करेगा कि यह IEquatable<Person>सार्वजनिक एपीआई पर लागू होता है, या स्पष्ट कार्यान्वयन के माध्यम से - अर्थात कंपाइलर एक सार्वजनिक Equals(Person other)विधि देख सकता है । तथापि; में जेनरिक , एक ही आईएल सभी के लिए प्रयोग किया जाता है T; एक T1है कि लागू करने के लिए होता है IEquatable<T1>की जरूरत है एक के समान व्यवहार किया जाना T2तो नहीं, यह - कि नहीं करता है नहीं एक जगह Equals(T1 other)विधि, भले ही यह क्रम पर मौजूद है। दोनों मामलों में, null(या तो वस्तु) के बारे में भी सोचना है। इसलिए जेनेरिक के साथ, मैं अपने द्वारा पोस्ट किए गए कोड का उपयोग करूंगा।
मार्क Gravell

5
मैं तय नहीं कर सकता कि यह जवाब मुझे पागलपन से दूर या करीब ले जाए। +1
स्टीवन लाइकेन्स

118

इस बारे में कैसा है:

if (object.Equals(myArgument, default(T)))
{
    //...
}

static object.Equals()विधि का उपयोग करने से आपको nullस्वयं जांच करने की आवश्यकता से बचा जाता है । स्पष्ट रूप से object.आपके संदर्भ के आधार पर कॉल को योग्य बनाना संभव नहीं है, लेकिन मैं आमतौर staticपर कोड को अधिक घुलनशील बनाने के लिए केवल प्रकार के नाम के साथ उपसर्ग कॉल करता हूं ।


2
आप "ऑब्जेक्ट" भी छोड़ सकते हैं। क्योंकि यह बेमानी है। अगर (बराबर (myArgument, डिफ़ॉल्ट (T)))
स्टीफन मोजर

13
सच है, यह सामान्य रूप से है, लेकिन संदर्भ के आधार पर नहीं हो सकता है। एक उदाहरण हो सकता है समान () विधि जो दो तर्क लेती है। मैं स्पष्ट रूप से वर्ग नाम के साथ सभी स्थिर कॉलों को उपसर्ग करता हूं, यदि केवल कोड को पढ़ने के लिए आसान बनाने के लिए।
कैंट बूगार्ट

8
यह ध्यान देने की आवश्यकता है कि यह मुक्केबाजी का कारण बनेगा और कुछ मामलों में यह महत्वपूर्ण हो सकता है
नाइटकॉडर

2
मेरे लिए यह काम नहीं करता है जब पहले से ही बॉक्सिंग वाले पूर्णांक का उपयोग किया जाता है। क्योंकि यह तब एक वस्तु होगी और वस्तु के लिए डिफ़ॉल्ट 0. के बजाय अशक्त है
riezebosch

28

मैं एक Microsoft कनेक्ट आलेख का पता लगाने में सक्षम था जो इस मुद्दे पर कुछ विस्तार से चर्चा करता है:

दुर्भाग्यवश, यह व्यवहार डिजाइन द्वारा है और प्रकार के मापदंडों के उपयोग को सक्षम करने के लिए एक आसान समाधान नहीं है जिसमें मूल्य प्रकार शामिल हो सकते हैं।

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

public class Test<T> where T : Exception

यदि प्रकारों को मूल्य प्रकारों के लिए जाना जाता है, तो उपयोग किए गए सटीक प्रकारों के आधार पर विशिष्ट मूल्य समानता परीक्षण करता है। यहां कोई भी "डिफ़ॉल्ट" तुलना नहीं है क्योंकि संदर्भ तुलना मूल्य प्रकारों पर सार्थक नहीं है और संकलक यह नहीं जान सकता कि उत्सर्जन की तुलना में कौन सा विशिष्ट मूल्य है। कंपाइलर ValueType.Equals (ऑब्जेक्ट) पर कॉल का उत्सर्जन कर सकता है, लेकिन यह विधि प्रतिबिंब का उपयोग करती है और विशिष्ट मूल्य तुलना की तुलना में काफी अक्षम है। इसलिए, भले ही आप टी पर एक मूल्य-प्रकार की बाधा निर्दिष्ट करने के लिए थे, यहां संकलक के लिए कुछ भी उचित नहीं है:

public class Test<T> where T : struct

आपके द्वारा प्रस्तुत किए गए मामले में, जहां कंपाइलर को यह भी पता नहीं है कि टी एक मूल्य या संदर्भ प्रकार है, तो समान रूप से उत्पन्न करने के लिए कुछ भी नहीं है जो सभी संभव प्रकारों के लिए मान्य होगा। एक संदर्भ तुलना मूल्य प्रकारों के लिए मान्य नहीं होगी और कुछ प्रकार के मूल्य तुलना संदर्भ प्रकारों के लिए अप्रत्याशित होगी जो ओवरलोड नहीं करते हैं।

यहाँ आप क्या कर सकते हैं ...

मैंने पुष्टि की है कि ये दोनों विधियाँ संदर्भ और मूल्य प्रकारों की एक सामान्य तुलना के लिए काम करती हैं:

object.Equals(param, default(T))

या

EqualityComparer<T>.Default.Equals(param, default(T))

"==" ऑपरेटर के साथ तुलना करने के लिए आपको इनमें से किसी एक विधि का उपयोग करना होगा:

यदि किसी ज्ञात आधार वर्ग से टी के सभी मामले आप संकलक को सामान्य प्रकार के प्रतिबंधों का उपयोग करने दे सकते हैं।

public void MyMethod<T>(T myArgument) where T : MyBase

कंपाइलर तब पहचानता है कि ऑपरेशन कैसे करना है MyBaseऔर "ऑपरेटर" को नहीं फेंकेंगे == '' टी 'और' टी 'की त्रुटि वाले ऑपरेंड पर लागू नहीं किया जा सकता है जो अब आप देख रहे हैं।

एक अन्य विकल्प टी को किसी भी प्रकार से लागू करने के लिए प्रतिबंधित करना होगा IComparable

public void MyMethod<T>(T myArgument) where T : IComparable

और फिर IComparable इंटरफ़ेसCompareTo द्वारा परिभाषित विधि का उपयोग करें


4
"यह व्यवहार डिज़ाइन के अनुसार है और इसमें प्रकार के मापदंडों के उपयोग को सक्षम करने के लिए एक आसान समाधान नहीं है जिसमें मूल्य प्रकार शामिल हो सकते हैं।" दरअसल Microsoft गलत है। एक आसान समाधान है: एमएस को बिटकॉइन ऑपरेटर के रूप में मूल्य प्रकारों पर कार्य करने के लिए सीक्यू ओपोड का विस्तार करना चाहिए। तब वे एक आंतरिक प्रदान कर सकते हैं जो बस इस opcode का उपयोग करता है, उदाहरण के लिए object.BitwiseOrReferenceEquals <T> (मान, डिफ़ॉल्ट (T)) जो बस Ceq का उपयोग करता है। मूल्य और संदर्भ दोनों प्रकार के लिए यह
बिटवाइज

1
मुझे लगता है कि आपके द्वारा चाहा गया माइक्रोसॉफ्ट कनेक्ट लिंक connect.microsoft.com/VisualStudio/feedback/details/304501/…
Qwertie

18

इसे इस्तेमाल करे:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

उसे संकलित करना चाहिए, और वही करना चाहिए जो आप चाहते हैं।


क्या <code> डिफ़ॉल्ट (T) </ code> निरर्थक नहीं है? <code> EqualityComparer <T> .Default.Equals (myArgument) </ code> ट्रिक करना चाहिए।
जोशकोड्स

2
1) क्या आपने यह कोशिश की, और 2) आप फिर क्या तुलना करने वाले, तुलना करने वाले ऑब्जेक्ट हैं? दो तर्कों की Equalsविधि IEqualityComparer, दो वस्तुओं की तुलना करना, इसलिए नहीं, यह बेमानी नहीं है।
लास वी। कार्लसन

यह स्वीकार किए गए उत्तर IMHO से भी बेहतर है क्योंकि यह बॉक्सिंग / अनबॉक्सिंग और अन्य प्रकारों को संभालता है। इस "बंद के रूप में देखें" सवालों के जवाब: stackoverflow.com/a/864860/210780
ashes999

7

(संपादित)

मार्क ग्रेवेल का सबसे अच्छा जवाब है, लेकिन मैं एक सरल कोड स्निपेट पोस्ट करना चाहता था जिसे मैंने प्रदर्शित करने के लिए काम किया। बस इसे एक साधारण C # कंसोल ऐप में चलाएं:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

एक और बात: VS2008 के साथ कोई व्यक्ति इसे विस्तार विधि के रूप में आज़मा सकता है? मैं 2005 से यहां अटका हुआ हूं और मुझे यह देखने की उत्सुकता है कि क्या इसकी अनुमति होगी।


संपादित करें: यह है कि इसे विस्तार विधि के रूप में कैसे काम किया जाए:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

3
यह एक विस्तार विधि के रूप में "काम" करता है। यह दिलचस्प है क्योंकि यह तब भी काम करता है जब आप कहते हैं कि ओ। डिफॉल्ट <ऑब्जेक्ट> () जब ओ शून्य है। डरावना =)
निक फरीना

6

T के सभी प्रकारों को संभालने के लिए, जहाँ T एक आदिम प्रकार है, आपको तुलना के दोनों तरीकों को संकलित करना होगा:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

1
ध्यान दें कि फंक <T> को स्वीकार करने के लिए फ़ंक्शन को बदल दिया गया है और टी, जो मुझे लगता है कि गलती से प्रश्नकर्ता के कोड से छोड़ दिया गया था।
निक फरिना

लगता है कि रेस्परर मेरे साथ खिलवाड़ कर रहा है। एक मूल्य प्रकार और अशक्त के बीच संभावित तुलना के बारे में इसकी चेतावनी का एहसास नहीं था एक संकलक चेतावनी नहीं थी।
नाथन रिडले

2
FYI करें: यदि T एक वैल्यू टाइप है, तो null के खिलाफ तुलना को हमेशा गलत माना जाएगा।
एरिक लिपर्ट

समझ में आता है - रनटाइम एक पॉइंटर की तुलना वैल्यू टाइप से करेगा। समतुल्य () चेक उस मामले में काम करता है (हालांकि, दिलचस्प है, क्योंकि यह 5.Equals (4) जो संकलन करता है) कहने के लिए बहुत गतिशील-भाषा लगता है।
निक फरिना

2
EqualityComparer <टी> एक विकल्प के लिए जवाब है कि मुक्केबाजी एट शामिल नहीं करता है देखें
मार्क Gravell

2

यहाँ एक समस्या होने जा रही है -

यदि आप इसे किसी भी प्रकार के लिए काम करने की अनुमति देने जा रहे हैं, तो डिफॉल्ट प्रकारों के लिए डिफॉल्ट (टी) हमेशा शून्य रहेगा, और मान प्रकारों के लिए 0 (या संरचना पूर्ण)।

यह संभवतः आपके द्वारा किए गए व्यवहार के बाद नहीं है। यदि आप चाहते हैं कि यह एक सामान्य तरीके से काम करे, तो आपको संभवतः T के प्रकार की जांच करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता है, और संदर्भ प्रकारों से भिन्न मानों को संभालना है।

वैकल्पिक रूप से, आप इस पर एक इंटरफ़ेस बाधा डाल सकते हैं, और इंटरफ़ेस वर्ग / संरचना के डिफ़ॉल्ट के खिलाफ जांच करने का एक तरीका प्रदान कर सकता है।


1

मुझे लगता है कि आपको संभवतः इस तर्क को दो भागों में विभाजित करने और पहले अशक्त होने की जांच करने की आवश्यकता है।

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

IsNull पद्धति में, हम इस तथ्य पर भरोसा कर रहे हैं कि ValueType ऑब्जेक्ट को परिभाषा से शून्य नहीं किया जा सकता है, यदि मान ऐसा होता है जो एक ऐसा वर्ग होता है जो ValueType से प्राप्त होता है, तो हम पहले से ही जानते हैं कि यह शून्य नहीं है। दूसरी ओर, यदि यह एक मूल्य प्रकार नहीं है, तो हम बस मूल्य डाली की तुलना नल के खिलाफ एक वस्तु से कर सकते हैं। हम किसी ऑब्जेक्ट से सीधे किसी कास्ट में जाकर ValueType के खिलाफ चेक से बच सकते हैं, लेकिन इसका मतलब यह होगा कि एक वैल्यू टाइप बॉक्सिंग हो जाएगी, जो कि हम संभवत: उससे बचना चाहते हैं क्योंकि यह मतलब है कि एक नया ऑब्जेक्ट हीप पर बनाया गया है।

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

इन विधियों का उपयोग करते हुए, निम्न कोड व्यवहार करता है जैसा कि आप उम्मीद कर सकते हैं:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

1

एक्सटेंशन विधि स्वीकृत उत्तर के आधार पर।

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

उपयोग:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

सरल बनाने के लिए नल के साथ वैकल्पिक:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

उपयोग:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

0

मैं उपयोग करता हूं:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

-1

पता नहीं कि यह आपकी आवश्यकताओं के साथ काम करता है या नहीं, लेकिन आप T को एक प्रकार का होने के लिए विवश कर सकते हैं जो एक इंटरफ़ेस को लागू करता है जैसे कि IComparable और फिर उस इंटरफ़ेस से ComparesTo () पद्धति का उपयोग करें (जो IIRC समर्थन / हैंडल करता है) इस तरह :

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

वहाँ शायद अन्य इंटरफेस है कि आप के रूप में अच्छी तरह से IEquitable, आदि का उपयोग कर सकते हैं


ओपी चिंतित है NullReferenceException abt और आप उसे एक ही गारंटी दे रहे हैं।
नवफाल

-2

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

ऑपरेटर '==' को 'T' और 'T' के प्रकार पर लागू नहीं किया जा सकता

मैं स्पष्ट तरीके से शून्य परीक्षण के बिना ऐसा करने के तरीके के बारे में नहीं सोच सकता हूं, जैसा कि ऊपर दिए गए सुझाव के अनुसार इक्वल विधि या ऑब्जेक्ट।

आप System.Comparison का उपयोग करके एक समाधान तैयार कर सकते हैं, लेकिन वास्तव में यह कोड की अधिक पंक्तियों के साथ समाप्त होने वाला है और जटिलता को काफी हद तक बढ़ाता है।


-3

मुझे लगता है कि आप करीब थे।

if (myArgument.Equals(default(T)))

अब यह संकलित करता है, लेकिन यदि myArgumentअशक्त है, जो मैं इसके लिए परीक्षण कर रहा हूँ का एक हिस्सा है, विफल हो जाएगा । मैं इस तरह एक स्पष्ट अशक्त जाँच जोड़ सकते हैं:

आपको बस उस वस्तु को उलटने की जरूरत है जिस पर एक सुरुचिपूर्ण अशक्त-सुरक्षित दृष्टिकोण के लिए बराबर कहा जा रहा है।

default(T).Equals(myArgument);

मेरा विचार भी बिल्कुल यही था।
क्रिस गेसलर

6
डिफ़ॉल्ट (T) एक संदर्भ प्रकार अशक्त है और एक गारंटी NullReferenceException में परिणाम है।
स्टीफन स्टाइनगर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.