क्या “समूह” की दुश्मनी के लिए झंडे का इस्तेमाल करना गलत है?


12

मेरी समझ यह है कि [Flag]एनम आमतौर पर उन चीजों के लिए उपयोग किया जाता है जिन्हें संयुक्त किया जा सकता है, जहां व्यक्तिगत मूल्य परस्पर अनन्य नहीं हैं

उदाहरण के लिए:

[Flags]
public enum SomeAttributes
{
    Foo = 1 << 0,
    Bar = 1 << 1,
    Baz = 1 << 2,
}

जहां किसी भी SomeAttributesमूल्य का एक संयोजन हो सकता है Foo, Barऔर Baz

अधिक जटिल, वास्तविक जीवन के परिदृश्य में , मैं एक का वर्णन करने के लिए एक एनम का उपयोग करता हूं DeclarationType:

[Flags]
public enum DeclarationType
{
    Project = 1 << 0,
    Module = 1 << 1,
    ProceduralModule = 1 << 2 | Module,
    ClassModule = 1 << 3 | Module,
    UserForm = 1 << 4 | ClassModule,
    Document = 1 << 5 | ClassModule,
    ModuleOption = 1 << 6,
    Member = 1 << 7,
    Procedure = 1 << 8 | Member,
    Function = 1 << 9 | Member,
    Property = 1 << 10 | Member,
    PropertyGet = 1 << 11 | Property | Function,
    PropertyLet = 1 << 12 | Property | Procedure,
    PropertySet = 1 << 13 | Property | Procedure,
    Parameter = 1 << 14,
    Variable = 1 << 15,
    Control = 1 << 16 | Variable,
    Constant = 1 << 17,
    Enumeration = 1 << 18,
    EnumerationMember = 1 << 19,
    Event = 1 << 20,
    UserDefinedType = 1 << 21,
    UserDefinedTypeMember = 1 << 22,
    LibraryFunction = 1 << 23 | Function,
    LibraryProcedure = 1 << 24 | Procedure,
    LineLabel = 1 << 25,
    UnresolvedMember = 1 << 26,
    BracketedExpression = 1 << 27,
    ComAlias = 1 << 28
}

जाहिर है कि कोई भी Declarationएक Variableऔर दोनों नहीं हो सकता है LibraryProcedure- दो व्यक्तिगत मूल्यों को संयुक्त नहीं किया जा सकता है .. और वे नहीं हैं।

हालांकि ये झंडे अत्यंत उपयोगी होते हैं (यह सत्यापित करना बहुत आसान है कि दिए गए DeclarationTypea Propertyया a Module), यह "गलत" लगता है क्योंकि झंडे वास्तव में मूल्यों के संयोजन के लिए उपयोग नहीं किए जाते हैं , बल्कि उन्हें "उप-प्रकार" में समूहित करने के लिए उपयोग किया जाता है ।

तो मुझे बताया गया है कि यह एनम झंडे का दुरुपयोग कर रहा है - यह उत्तर अनिवार्य रूप से कहता है कि अगर मेरे पास सेब पर लागू मानों का एक सेट है और संतरे के लिए लागू एक और सेट है, तो मुझे सेब के लिए एक अलग एनुम प्रकार और संतरे के लिए एक और एक की आवश्यकता है - उस के साथ समस्या यह है कि मुझे DeclarationTypeआधार Declarationवर्ग में उजागर होने के साथ एक सामान्य इंटरफ़ेस के लिए सभी घोषणाओं की आवश्यकता है : एक PropertyTypeएनुम का होना बिल्कुल भी उपयोगी नहीं होगा।

यह एक मैला / आश्चर्यजनक / अपमानजनक डिजाइन है? यदि हां, तो उस समस्या को आम तौर पर कैसे हल किया जाता है?


विचार करें कि लोग किस प्रकार की वस्तुओं का उपयोग करने जा रहे हैं DeclarationType। अगर मैं यह निर्धारित करना चाहता हूं कि क्या xइसका कोई उपप्रकार है या नहीं y, तो मैं शायद यह लिखना चाहता हूं कि जैसा है x.IsSubtypeOf(y), वैसा नहीं x && y == y
टान्नर स्विट

1
@TannerSwett बिलकुल यही x.HasFlag(DeclarationType.Member)करता है ....
मैथ्यू गुइंडन

हाँ यह सच है। लेकिन अगर आपकी विधि को HasFlagइसके बजाय कहा जाता है IsSubtypeOf, तो मुझे यह पता लगाने के कुछ अन्य तरीके की आवश्यकता है कि इसका वास्तव में मतलब क्या है "उप-प्रकार है"। आप एक एक्सटेंशन विधि बना सकते हैं, लेकिन एक उपयोगकर्ता के रूप में, जो मुझे कम से कम आश्चर्य होगा वह DeclarationTypeसिर्फ एक संरचना के लिए है जो वास्तविक विधि के IsSubtypeOfरूप में है ।
टेनर स्विट

जवाबों:


10

यह निश्चित रूप से दुश्मनी और झंडे का दुरुपयोग है! यह आपके लिए काम कर सकता है, लेकिन कोड को पढ़ने वाला कोई और व्यक्ति भ्रमित हो सकता है।

अगर मैं सही ढंग से समझूं, तो आपके पास घोषणाओं का एक श्रेणीबद्ध वर्गीकरण है। यह वह जगह है जहाँ तक एक भी enum में एन्कोड करने के लिए अधिक से अधिक जानकारी के लिए। लेकिन एक स्पष्ट विकल्प है: वर्गों और विरासत का उपयोग करें! इसलिए से Memberविरासत में मिली DeclarationType, Propertyविरासत से Memberऔर इतने पर।

कुछ विशेष परिस्थितियों में एनम उपयुक्त हैं: यदि मान हमेशा सीमित संख्या में विकल्पों में से एक होता है, या यदि यह सीमित संख्या में विकल्पों (झंडे) का संयोजन होता है। कोई भी जानकारी जो वस्तुओं की तुलना में अधिक जटिल या संरचित है, का प्रतिनिधित्व किया जाना चाहिए।

संपादित करें : आपके "वास्तविक जीवन के परिदृश्य" में ऐसा लगता है कि कई स्थान हैं जहां व्यवहार का चयन एनम के मूल्य के आधार पर किया जाता है। यह वास्तव में एक antipattern है, क्योंकि आप उपयोग कर रहे switch+ enum"गरीब मनुष्य बहुरूपता" के रूप में। घोषणा-विशिष्ट व्यवहार को अलग करने वाले अलग-अलग वर्गों में बस एनम मूल्य को चालू करें, और आपका कोड बहुत अधिक क्लीनर होगा


वंशानुक्रम एक अच्छा विचार है, लेकिन आपका उत्तर प्रति वर्ष एक वर्ग को दर्शाता है, जो ओवरकिल लगता है।
फ्रैंक हिलमैन

@FrankHileman: पदानुक्रम में "लीफ़्स" को कक्षाओं के बजाय एनम मूल्यों के रूप में दर्शाया जा सकता है, लेकिन मुझे यकीन नहीं है कि यह बेहतर होगा। निर्भर करता है कि क्या अलग-अलग व्यवहार अलग-अलग enum मानों से जुड़े होंगे, इस मामले में अलग-अलग कक्षाएं बेहतर होंगी।
जैक्सबी

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

मेरे रेपो के उस लिंक के बारे में: प्रति-प्रकार व्यवहार का ParserRuleContextएनुम के साथ उत्पन्न वर्ग के प्रकार के साथ अधिक करना है। वह कोड टोकन स्थिति प्राप्त करने की कोशिश कर रहा है जहां As {Type}घोषणा में एक खंड सम्मिलित करना है ; इन- ParserRuleContextश्रेणी वाले वर्गों को प्रतिमा नियमों को परिभाषित करने वाले व्याकरण के अनुसार Antr4 द्वारा उत्पन्न किया जाता है - मैं वास्तव में पार्स ट्री नोड्स को नियंत्रित नहीं करता हूं '[बल्कि उथले] वंशानुक्रम पदानुक्रम, हालांकि मैं partialउन्हें लागू करने के लिए उनके इंटरफेस का लाभ उठा सकता हूं , जैसे। उन्हें कुछ AsTypeClauseTokenPositionसंपत्ति को उजागर करें .. बहुत सारे काम।
मैथ्यू गुइंडन

6

मुझे यह दृष्टिकोण पढ़ने और समझने में काफी आसान लगता है। IMHO, यह उलझन में पाने के लिए कुछ नहीं है। कहा जा रहा है, मुझे इस दृष्टिकोण के बारे में कुछ चिंताएं हैं:

  1. मेरा मुख्य आरक्षण यह है कि इसे लागू करने का कोई तरीका नहीं है:

    स्पष्ट रूप से एक दिया घोषणा दोनों एक परिवर्तनीय और एक लाइब्रेरीप्रोसेसर नहीं हो सकता है - दो व्यक्तिगत मूल्यों को जोड़ा नहीं जा सकता है .. और वे नहीं हैं।

    जब आप संयोजन के ऊपर घोषित नहीं करते हैं, तो यह कोड

    var oops = DeclarationType.Variable | DeclarationType.LibraryProcedure;

    पूरी तरह से मान्य है। और संकलन के समय उन त्रुटियों को पकड़ने का कोई तरीका नहीं है।

  2. इस बात की एक सीमा है कि आप बिट झंडे में कितनी जानकारी सांकेतिक शब्दों में लिख सकते हैं, जो कि 64 बिट्स है? अभी के लिए आप खतरनाक रूप से आकार के करीब आ रहे हैं intऔर अगर यह दुश्मनी बढ़ती रही तो आप अंततः बिट्स से बाहर निकल सकते हैं ...

नीचे की रेखा है, मुझे लगता है कि यह एक वैध दृष्टिकोण है, लेकिन मुझे झंडे के बड़े / जटिल पदानुक्रमों के लिए इसका इस्तेमाल करने में संकोच होगा।


तो पूरी तरह से पार्सर इकाई परीक्षण # 1 तब संबोधित करेंगे। FWIW एनम ने कुछ 3 साल पहले एक मानक नो-फ्लैग एनम के रूप में शुरू किया था। यह कुछ विशिष्ट मूल्यों (कोड निरीक्षणों में कहते हैं) के लिए हमेशा थके हुए बढ़ने के बाद है, कि एनम फ्लैग दिखाई दिए। सूची वास्तव में समय के साथ बढ़ने वाली नहीं है, लेकिन हाँ धड़कन intक्षमता एक वास्तविक चिंता है।
मैथ्यू गुइंडन

3

TL; DR बहुत नीचे तक स्क्रॉल करें।


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

इस विशेष स्थिति में, विभिन्न प्रकार के नोड्स के बीच बहुत कम बहुरूपी व्यवहार होते हैं। दूसरे शब्दों में, जबकि पेड़ के लिए बहुत अलग प्रकार (नोड्स) के नोड्स को सक्षम करने के लिए आवश्यक है, इन नोड्स की वास्तविक यात्रा मूल रूप से एक विशाल अगर-फिर-चेन (या instanceof/ isचेक) का सहारा लेगी । ये विशाल जाँच संभवत: परियोजना के कई अलग-अलग स्थानों पर होगी। यही कारण है कि एनम मददगार लग सकते हैं, या, वे कम से कम instanceof/ isचेक के रूप में सहायक होते हैं ।

विज़िटर पैटर्न अभी भी उपयोगी हो सकता है। दूसरे शब्दों में, विभिन्न कोडिंग शैलियाँ हैं जिनका उपयोग विशालकाय श्रृंखला के बजाय किया जा सकता है instanceof। हालाँकि, यदि आप विभिन्न लाभों और कमियों पर चर्चा चाहते हैं, तो आपने instanceofएनम के बारे में समझाने के बजाय परियोजना की सबसे खराब श्रृंखला से एक कोड उदाहरण प्रदर्शित करने के लिए चुना होगा ।

यह वर्गों और विरासत पदानुक्रम उपयोगी नहीं हैं कहने के लिए नहीं है। काफी विपरीत। जबकि कोई भी बहुरूपतापूर्ण व्यवहार नहीं है जो हर घोषणा प्रकार पर काम करता है (इस तथ्य से अलग कि प्रत्येक घोषणा में एक Nameसंपत्ति होनी चाहिए ), पास के भाई-बहनों द्वारा साझा किए गए प्रचुर मात्रा में बहुरंगी व्यवहार हैं। उदाहरण के लिए, Functionऔर Procedureशायद कुछ व्यवहारों को साझा करें (दोनों टाइप करने योग्य हैं और टाइप किए गए इनपुट तर्कों की एक सूची को स्वीकार करते हैं), और PropertyGetनिश्चित रूप से Function(दोनों वाले ReturnType) से व्यवहार का वारिस होगा । आप या तो विशाल या तो-और-श्रृंखला के लिए इनमेट्स या इनहेरिटेंस चेक का उपयोग कर सकते हैं, लेकिन बहुरंगी व्यवहार, हालांकि खंडित, अभी भी कक्षाओं में लागू किया जाना चाहिए।

instanceof/ isचेक के अति प्रयोग के खिलाफ कई ऑनलाइन सलाह हैं । प्रदर्शन इसका एक कारण नहीं है। बल्कि, इसका कारण प्रोग्रामर को व्यवस्थित रूप से उपयुक्त बहुरूपी व्यवहार की खोज करने से रोकना है, जैसे कि instanceof/ isएक बैसाखी है। लेकिन आपकी स्थिति में, आपके पास कोई अन्य विकल्प नहीं है, क्योंकि ये नोड्स बहुत कम हैं।

अब यहां कुछ ठोस सुझाव दिए गए हैं।


गैर-पत्ती समूहों का प्रतिनिधित्व करने के कई तरीके हैं।


अपने मूल कोड के निम्नलिखित अंश की तुलना करें ...

[Flags]
public enum DeclarationType
{
    Member = 1 << 7,
    Procedure = 1 << 8 | Member,
    Function = 1 << 9 | Member,
    Property = 1 << 10 | Member,
    PropertyGet = 1 << 11 | Property | Function,
    PropertyLet = 1 << 12 | Property | Procedure,
    PropertySet = 1 << 13 | Property | Procedure,
    LibraryFunction = 1 << 23 | Function,
    LibraryProcedure = 1 << 24 | Procedure,
}

इस संशोधित संस्करण में:

[Flags]
public enum DeclarationType
{
    Nothing = 0, // to facilitate bit testing

    // Let's assume Member is not a concrete thing, 
    // which means it doesn't need its own bit
    /* Member = 1 << 7, */

    // Procedure and Function are concrete things; meanwhile 
    // they can still have sub-types.
    Procedure = 1 << 8, 
    Function = 1 << 9, 
    Property = 1 << 10,

    PropertyGet = 1 << 11,
    PropertyLet = 1 << 12,
    PropertySet = 1 << 13,

    LibraryFunction = 1 << 23,
    LibraryProcedure = 1 << 24,

    // new
    Procedures = Procedure | PropertyLet | PropertySet | LibraryProcedure,
    Functions = Function | PropertyGet | LibraryFunction,
    Properties = PropertyGet | PropertyLet | PropertySet,
    Members = Procedures | Functions | Properties,
    LibraryMembers = LibraryFunction | LibraryProcedure 
}

यह संशोधित संस्करण गैर-ठोस घोषणा प्रकारों की ओर बिट्स आवंटित करने से बचता है। इसके बजाय, गैर-ठोस घोषणा प्रकार (घोषणा प्रकारों के सार समूह) में बस सभी बच्चों के बीच बिट-या-बिट्स (बिट्स का संघ) मान होता है।

एक चेतावनी है: यदि एक सार घोषणा प्रकार है जिसमें एक ही बच्चा है, और अगर ठोस एक (बच्चे) से सार एक (माता-पिता) के बीच अंतर करने की आवश्यकता है, तो सार एक को अभी भी अपने स्वयं के बिट की आवश्यकता होगी ।


एक प्रश्न जो इस प्रश्न के लिए विशिष्ट है: एक Propertyशुरू में एक पहचानकर्ता है (जब आप केवल उसका नाम देखते हैं, बिना यह देखे कि कोड में इसका उपयोग कैसे किया जाता है), लेकिन यह जैसे ही आप देखते हैं कि यह कैसे उपयोग किया जा रहा है PropertyGet/ PropertyLet/ PropertySetकोड में। दूसरे शब्दों में, पार्सिंग के विभिन्न चरणों में, आपको या तो एक Propertyपहचानकर्ता को चिह्नित करने की आवश्यकता हो सकती है क्योंकि "यह नाम एक संपत्ति को संदर्भित करता है", और बाद में इसे "कोड की यह रेखा एक निश्चित तरीके से इस संपत्ति तक पहुंच रही है"।

इस चेतावनी को हल करने के लिए, आपको दो सेटों की आवश्यकता हो सकती है; एक enum दर्शाता है कि एक नाम (पहचानकर्ता) क्या है; एक और एनम निरूपित करता है कि कोड क्या करने की कोशिश कर रहा है (जैसे कि किसी चीज़ को शरीर घोषित करना, किसी चीज़ को किसी निश्चित तरीके से उपयोग करने की कोशिश करना)।


इस बात पर विचार करें कि क्या प्रत्येक एनीम मूल्य के बारे में सहायक जानकारी को इसके बजाय किसी सरणी से पढ़ा जा सकता है।

यह सुझाव अन्य सुझावों के साथ पारस्परिक रूप से अनन्य है क्योंकि इसके लिए छोटे गैर-नकारात्मक पूर्णांक मानों में दो-दो मान परिवर्तित करने की आवश्यकता होती है।

public enum DeclarationType
{
    Procedure = 8,
    Function = 9,
    Property = 10,
    PropertyGet = 11,
    PropertyLet = 12,
    PropertySet = 13,
    LibraryFunction = 23,
    LibraryProcedure = 24,
}

static readonly bool[] DeclarationTypeIsMember = new bool[32]
{
    ?, ?, ?, ?, ?, ?, ?, ?,                   // bit[0] ... bit[7]
    true, true, true, true, true, true, ?, ?, // bit[8] ... bit[15]
    ?, ?, ?, ?, ?, ?, ?, true,                // bit[16] ... bit[23]
    true, ...                                 // bit[24] ... 
}

static bool IsMember(DeclarationType dt)
{
    int intValue = (int)dt;
    return (intValue < 0 || intValue >= 32) ? false : DeclarationTypeIsMember[intValue];
    // you can also throw an exception if the enum is outside range.
}

// likewise for IsFunction(dt), IsProcedure(dt), IsProperty(dt), ...

स्थिरता समस्याग्रस्त होने वाली है।


जांचें कि क्या सी # प्रकार (एक वंशानुक्रम पदानुक्रम में कक्षाएं) और आपके एनम मूल्यों के बीच एक-से-एक मानचित्रण है।

(वैकल्पिक रूप से, आप प्रकारों के साथ एक-से-एक मैपिंग सुनिश्चित करने के लिए अपने enum मानों को ट्विक कर सकते हैं।)

सी # में, बहुत सारे पुस्तकालय Type object.GetType()अच्छे या बुरे के लिए निफ्टी विधि का दुरुपयोग करते हैं ।

कहीं भी आप एक मूल्य के रूप में एनम का भंडारण कर रहे हैं, आप अपने आप से पूछ सकते हैं कि क्या आप Typeइसके बजाय एक मूल्य के रूप में संग्रहीत कर सकते हैं ।

इस ट्रिक का उपयोग करने के लिए, आप दो रीड-ओनली हैश टेबल को इनिशियलाइज़ कर सकते हैं, अर्थात्:

// For disambiguation, I'll assume that the actual 
// (behavior-implementing) classes are under the 
// "Lang" namespace.

static readonly Dictionary<Type, DeclarationType> TypeToDeclEnum = ... 
{
    { typeof(Lang.Procedure), DeclarationType.Procedure },
    { typeof(Lang.Function), DeclarationType.Function },
    { typeof(Lang.Property), DeclarationType.Property },
    ...
};

static readonly Dictionary<DeclarationType, Type> DeclEnumToType = ...
{
    // same as the first dictionary; 
    // just swap the key and the value
    ...
};

वर्ग और विरासत पदानुक्रम का सुझाव देने वालों के लिए अंतिम संकेत ...

एक बार जब आप देख सकते हैं कि एनमेज वंशानुक्रम पदानुक्रम के लिए एक सन्निकटन हैं , तो निम्नलिखित सलाह है:

  • अपने वंशानुक्रम पदानुक्रम को पहले डिजाइन (या सुधार),
  • फिर वापस जाओ और अपने वंश को उस वंशानुक्रम पदानुक्रम को अनुमानित करने के लिए डिज़ाइन करें।

यह परियोजना वास्तव में एक VBIDE ऐड-इन है - मैं VBA कोड =) का पार्सिंग और विश्लेषण कर रहा हूं
मैथ्यू गुइंडन

1

मुझे आपके झंडे का उपयोग वास्तव में स्मार्ट, रचनात्मक, सुरुचिपूर्ण और संभावित रूप से सबसे कुशल लगता है। मुझे इसे पढ़ने में बिल्कुल भी कोई परेशानी नहीं है।

झंडे क्वालीफाइंग के संकेत राज्य का एक साधन हैं। अगर मैं जानना चाहता हूं कि क्या कुछ फल है, तो मैं पाता हूं

बातदार और कार्बनिक। भर्ती! = 0

से अधिक पठनीय है

thingy & (Organic.Apple | Organic.Orange | Organic.Pear)! = 0

फ्लैग एनम का बहुत बिंदु आपको कई राज्यों को संयोजित करने की अनुमति देता है। आपने इसे और अधिक उपयोगी और पठनीय बनाया है। आप अपने कोड में फल की अवधारणा को व्यक्त करते हैं, मुझे खुद यह पता लगाने की ज़रूरत नहीं है कि सेब और नारंगी और नाशपाती का मतलब फल है।

इस आदमी को कुछ ब्राउनी पॉइंट दें!


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