c# - संरचना का उपयोग कब करें?




struct (19)

आपको सी # में स्ट्रक्चर का उपयोग क्यों करना चाहिए और कक्षा में नहीं होना चाहिए? मेरा वैचारिक मॉडल यह है कि समय-समय पर structs का उपयोग किया जाता है जब आइटम केवल मूल्य प्रकारों का संग्रह होता है । तर्कसंगत रूप से उन सभी को एक साथ जोड़कर पकड़ने का एक तरीका।

मैं इन नियमों में here आया था:

  • एक संरचना एक एकल मूल्य का प्रतिनिधित्व करना चाहिए।
  • एक संरचना में 16 बाइट से कम मेमोरी पदचिह्न होना चाहिए।
  • सृजन के बाद एक संरचना नहीं बदला जाना चाहिए।

क्या ये नियम काम करते हैं? संरचना का अर्थ अर्थात् क्या है?


"यह एक मान है" उत्तर के अलावा, structs का उपयोग करने के लिए एक विशिष्ट परिदृश्य तब होता है जब आप जानते हैं कि आपके पास डेटा का एक सेट है जो कचरा संग्रहण समस्याएं पैदा कर रहा है, और आपके पास बहुत सारी वस्तुएं हैं। उदाहरण के लिए, व्यक्ति के उदाहरणों की एक बड़ी सूची / सरणी। यहां प्राकृतिक रूपक एक वर्ग है, लेकिन यदि आपके पास लंबे समय तक रहने वाले व्यक्ति के उदाहरण हैं, तो वे जीएन -2 को क्लोजिंग कर सकते हैं और जीसी स्टालों का कारण बन सकते हैं। यदि परिदृश्य इसे वारंट करता है, तो यहां एक संभावित दृष्टिकोण व्यक्ति structs , यानी Person[] सरणी (सूची नहीं) का उपयोग करना है। अब, GEN-2 में लाखों ऑब्जेक्ट्स रखने के बजाय, आपके पास LOH पर एक सिंगल हिस्सा है (मैं यहां कोई स्ट्रिंग इत्यादि नहीं मान रहा हूं - यानी किसी भी संदर्भ के बिना शुद्ध मूल्य)। इसका बहुत कम जीसी प्रभाव है।

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

int index = ...
int id = peopleArray[index].Id;

ध्यान दें कि मूल्यों को खुद को अपरिवर्तनीय रखने से यहां सहायता मिलेगी। अधिक जटिल तर्क के लिए, एक बाय-रेफ पैरामीटर के साथ एक विधि का उपयोग करें:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

फिर, यह जगह में है - हमने मूल्य की प्रतिलिपि नहीं बनाई है।

बहुत विशिष्ट परिदृश्यों में, यह रणनीति बहुत सफल हो सकती है; हालांकि, यह एक काफी उन्नत स्नेरारियो है जिसे केवल तभी किया जाना चाहिए जब आप जानते हों कि आप क्या कर रहे हैं और क्यों। यहां डिफ़ॉल्ट एक वर्ग होगा।


.NET value types और reference types का समर्थन करता है (जावा में, आप केवल संदर्भ प्रकार परिभाषित कर सकते हैं)। reference types उदाहरण प्रबंधित ढेर में आवंटित किए जाते हैं और कचरा इकट्ठा होते हैं जब उनके लिए कोई उत्कृष्ट संदर्भ नहीं होता है। दूसरी ओर, value types उदाहरण, stack में आवंटित किए जाते हैं, और इसलिए आवंटित स्मृति को उनके दायरे के समाप्त होने के तुरंत बाद पुनः प्राप्त किया जाता है। और निश्चित रूप से, value types से पारित value types हैं, और reference types द्वारा संदर्भ reference types । System.String को छोड़कर सभी सी # आदिम डेटा प्रकार, मान प्रकार हैं।

वर्ग पर संरचना का उपयोग कब करें,

सी # में, structs value types , कक्षा reference types । आप enum कीवर्ड और struct कीवर्ड का उपयोग करके, सी # में मूल्य प्रकार बना सकते हैं। किसी reference type value type बजाय value type का उपयोग करने से प्रबंधित ढेर पर कम ऑब्जेक्ट्स का परिणाम होगा, जिसके परिणामस्वरूप कचरा कलेक्टर (जीसी), कम लगातार जीसी चक्र, और इसके परिणामस्वरूप बेहतर प्रदर्शन होता है। हालांकि, value types भी उनके डाउनसाइड्स होते हैं। एक संदर्भ पारित करने से एक बड़ी struct आसपास गुजरना निश्चित रूप से महंगा है, यह एक स्पष्ट समस्या है। दूसरी समस्या boxing/unboxing जुड़ी ओवरहेड है। यदि आप सोच रहे हैं कि boxing/unboxing मतलब क्या है, तो boxing और unboxing पर एक अच्छी व्याख्या के लिए इन लिंक का पालन करें। प्रदर्शन के अलावा, ऐसे समय होते हैं जब आपको मूल्य अर्थशास्त्र के लिए केवल प्रकार की आवश्यकता होती है, जो reference types आपके पास होने पर लागू करने के लिए बहुत मुश्किल (या बदसूरत) होगा। आपको केवल value types उपयोग करना चाहिए, जब आपको कॉपी सेमेन्टिक्स की आवश्यकता होती है या सामान्य रूप से इन प्रकार के arrays में स्वचालित प्रारंभिक आवश्यकता होती है।


ओपी द्वारा संदर्भित स्रोत में कुछ विश्वसनीयता है ... लेकिन माइक्रोसॉफ्ट के बारे में क्या - संरचना उपयोग पर रुख क्या है? मैंने माइक्रोसॉफ्ट से कुछ अतिरिक्त शिक्षा मांगी, और यहां मैंने जो पाया:

यदि वर्ग के उदाहरण छोटे होते हैं और आमतौर पर अल्पकालिक होते हैं या आमतौर पर अन्य वस्तुओं में एम्बेडेड होते हैं तो कक्षा के बजाय संरचना को परिभाषित करने पर विचार करें।

एक संरचना को परिभाषित न करें जब तक कि इस प्रकार में निम्नलिखित सभी विशेषताएं न हों:

  1. यह तार्किक रूप से एक एकल मान का प्रतिनिधित्व करता है, जो प्राचीन प्रकारों (पूर्णांक, डबल, और इसी तरह) के समान होता है।
  2. इसका एक उदाहरण आकार 16 बाइट से छोटा है।
  3. यह अपरिवर्तनीय है।
  4. इसे अक्सर बॉक्सिंग नहीं करना पड़ेगा।

माइक्रोसॉफ्ट लगातार उन नियमों का उल्लंघन करता है

ठीक है, # 2 और # 3 वैसे भी। हमारे प्यारे शब्दकोश में 2 आंतरिक structs हैं:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* संदर्भ स्रोत

'JonnyCantCode.com' स्रोत 4 में से 3 मिला - काफी क्षमा करने योग्य # 4 के बाद से शायद कोई मुद्दा नहीं होगा। यदि आप खुद को एक संरचना मुक्केबाजी पाते हैं, तो अपने वास्तुकला पर पुनर्विचार करें।

चलिए देखते हैं कि माइक्रोसॉफ्ट इन structs का उपयोग क्यों करेगा:

  1. प्रत्येक संरचना, Entry और Enumerator , एकल मानों का प्रतिनिधित्व करते हैं।
  2. गति
  3. Entry को कक्षा वर्ग के बाहर पैरामीटर के रूप में कभी पारित नहीं किया जाता है। आगे की जांच से पता चलता है कि आईन्यूमेरेबल के कार्यान्वयन को पूरा करने के लिए, डिक्शनरी Enumerator स्ट्रक्चर का उपयोग करती है, जो हर बार एक गणनाकर्ता से अनुरोध की जाती है ... समझ में आता है।
  4. शब्दकोश वर्ग के लिए आंतरिक। गणक सार्वजनिक है क्योंकि शब्दकोश गणनात्मक है और आईन्यूमेरेटर इंटरफ़ेस कार्यान्वयन के लिए समान पहुंच होनी चाहिए - उदाहरण के लिए आईएन्यूमेरेटर गेटर।

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

जो हम यहां नहीं देखते हैं, वह स्ट्रक्चर को अपरिवर्तनीय रखने या केवल 16 बाइट्स या उससे कम के आवृत्ति आकार को बनाए रखने के लिए आवश्यक प्रयास या प्रमाण है:

  1. ऊपर दिए गए structs में कुछ भी पढ़ा नहीं जाता है - अपरिवर्तनीय नहीं
  2. इन संरचनाओं का आकार 16 बाइट से अधिक हो सकता है
  3. Entry में अनिश्चित जीवनकाल ( Add() से Remove() , Clear() , या कचरा संग्रह);

और ... 4. दोनों structs TKey और TValue स्टोर करते हैं, जिसे हम सभी जानते हैं संदर्भ प्रकार होने में काफी सक्षम हैं (बोनस जानकारी जोड़ा गया)

इसके बावजूद हैश की चाबियाँ, शब्दकोश तेजी से भाग में हैं क्योंकि एक संरचना को इंस्टेंस करना एक संदर्भ प्रकार से तेज़ है। यहां, मेरे पास एक Dictionary<int, int> जो अनुक्रमिक रूप से बढ़ी हुई कुंजी के साथ 300,000 यादृच्छिक पूर्णांक संग्रहीत करता है।

क्षमता: 312874
MemSize: 2660827 बाइट्स
पूरा आकार: 5 एमएमएस
भरने के लिए कुल समय: 88 9एमएस

क्षमता : आंतरिक सरणी का आकार बदलने से पहले उपलब्ध तत्वों की संख्या का आकार बदलना चाहिए।

MemSize : डिक्शनरी को मेमोरीस्ट्रीम में क्रमबद्ध करके और बाइट लम्बाई प्राप्त करना (हमारे उद्देश्यों के लिए पर्याप्त सटीक) प्राप्त करना।

पूर्ण आकार : 150862 तत्वों से 312874 तत्वों तक आंतरिक सरणी का आकार बदलने में लगने वाला समय। जब आप समझते हैं कि प्रत्येक तत्व को क्रमशः Array.CopyTo() माध्यम से कॉपी किया Array.CopyTo() , यह बहुत शर्मीली नहीं है।

भरने का कुल समय : लॉगिंग के कारण OnResize रूप से तिरछा हुआ और एक OnResize ईवेंट मैंने स्रोत में जोड़ा; हालांकि, ऑपरेशन के दौरान 15 बार आकार बदलने के दौरान 300k पूर्णांक भरने के लिए अभी भी प्रभावशाली है। जिज्ञासा से बाहर, अगर मैं पहले से ही क्षमता जानता था तो भरने का पूरा समय क्या होगा? 13ms

तो, अब, अगर Entry एक कक्षा थी तो क्या होगा? क्या ये समय या मीट्रिक वास्तव में इतना अलग होंगे?

क्षमता: 312874
MemSize: 2660827 बाइट्स
पूरा आकार: 26 एमएमएस
भरने के लिए कुल समय: 964 एमएमएस

जाहिर है, बड़ा अंतर आकार बदलने में है। यदि कोई क्षमता क्षमता के साथ शुरू की जाती है तो कोई अंतर? इससे संबंधित नहीं है ... 12ms

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

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

मुझे संदर्भ प्रकार के रूप में Entry प्रत्येक सरणी तत्व को प्रारंभ करने का कारण एमएसडीएन: संरचना डिजाइन में पाया जा सकता है। संक्षेप में:

संरचना के लिए एक डिफ़ॉल्ट कन्स्ट्रक्टर प्रदान न करें।

यदि कोई संरचना डिफ़ॉल्ट कन्स्ट्रक्टर को परिभाषित करती है, जब संरचना के सरणी बनती हैं, तो सामान्य भाषा रनटाइम स्वचालित रूप से प्रत्येक सरणी तत्व पर डिफ़ॉल्ट कन्स्ट्रक्टर निष्पादित करता है।

कुछ कंपाइलर, जैसे सी # कंपाइलर, संरचनाओं को डिफ़ॉल्ट कन्स्ट्रक्टर रखने की अनुमति नहीं देते हैं।

यह वास्तव में काफी सरल है और हम असिमोव के तीन कानूनों के रोबोटिक्स से उधार लेंगे:

  1. संरचना का उपयोग करने के लिए सुरक्षित होना चाहिए
  2. संरचना को अपने कार्य को कुशलता से निष्पादित करना चाहिए, जब तक यह नियम # 1 का उल्लंघन नहीं करेगा
  3. जब तक इसके विनाश को नियम # 1 को संतुष्ट करने की आवश्यकता नहीं होती है तब तक संरचना को इसके उपयोग के दौरान बरकरार रहना चाहिए

... हम इससे क्या दूर लेते हैं : संक्षेप में, मूल्य प्रकारों के उपयोग से ज़िम्मेदार रहें । वे त्वरित और कुशल हैं, लेकिन यदि कई उचित तरीके से बनाए रखा नहीं जाता है (यानी अनजाने प्रतियां) तो कई अप्रत्याशित व्यवहार करने की क्षमता है।


जब आप संदर्भ semantics के विपरीत मूल्य semantics चाहते हैं एक संरचना का प्रयोग करें।

संपादित करें

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

यदि आपको संदर्भ अर्थशास्त्र की आवश्यकता है तो आपको कक्षा की आवश्यकता नहीं है।


पहला: इंटरऑप परिदृश्य या जब आपको मेमोरी लेआउट निर्दिष्ट करने की आवश्यकता होती है

दूसरा: जब डेटा संदर्भ सूचक के रूप में लगभग समान आकार होता है।


मैं किसी भी तरह के बाइनरी संचार प्रारूप पैकिंग या अनपॅकिंग के लिए structs का उपयोग करता हूं। इसमें डिस्क को पढ़ने या लिखना, डायरेक्टएक्स वर्टेक्स सूचियां, नेटवर्क प्रोटोकॉल, या एन्क्रिप्टेड / संपीड़ित डेटा से निपटना शामिल है।

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


यहां एक मूल नियम है।

  • यदि सभी सदस्य फ़ील्ड मूल्य प्रकार हैं तो एक संरचना बनाएं।

  • यदि कोई भी सदस्य फ़ील्ड संदर्भ प्रकार है, तो कक्षा बनाएं। ऐसा इसलिए है क्योंकि संदर्भ प्रकार फ़ील्ड को वैसे भी ढेर आवंटन की आवश्यकता होगी।

exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

वैल्यूटाइप के अपवाद के साथ जो सीधे रनटाइम और पीईवीवोक प्रयोजनों के लिए विभिन्न अन्य लोगों द्वारा उपयोग किया जाता है, आपको केवल 2 परिदृश्यों में वैल्यूएटिप्प्स का उपयोग करना चाहिए।

  1. जब आपको कॉपी अर्थशास्त्र की आवश्यकता होती है।
  2. जब आपको स्वचालित रूप से इन प्रकार के सरणी में स्वचालित प्रारंभिकरण की आवश्यकता होती है।

सी # भाषा विनिर्देश से :

1.7 संरचनाएं

कक्षाओं की तरह, structs डेटा संरचनाएं हैं जिनमें डेटा सदस्यों और फ़ंक्शन सदस्यों को शामिल किया जा सकता है, लेकिन कक्षाओं के विपरीत, structs मूल्य प्रकार हैं और ढेर आवंटन की आवश्यकता नहीं है। एक स्ट्रक्चर प्रकार का एक चर सीधे संरचना के डेटा को संग्रहीत करता है, जबकि कक्षा प्रकार का एक चर गतिशील आवंटित ऑब्जेक्ट का संदर्भ संग्रहीत करता है। संरचना प्रकार उपयोगकर्ता द्वारा निर्दिष्ट विरासत का समर्थन नहीं करते हैं, और सभी संरचना प्रकारों को प्रकार वस्तु से निहित रूप से प्राप्त होता है।

स्ट्रक्चर विशेष रूप से छोटे डेटा संरचनाओं के लिए उपयोगी होते हैं जिनमें मूल्य अर्थशास्त्र होता है। कॉम्प्लेक्स नंबर, एक समन्वय प्रणाली में अंक, या शब्दकोश में कुंजी-मूल्य जोड़े structs के सभी अच्छे उदाहरण हैं। छोटे डेटा संरचनाओं के लिए कक्षाओं के बजाय structs का उपयोग एक अनुप्रयोग प्रदर्शन स्मृति आवंटन की संख्या में एक बड़ा अंतर कर सकते हैं। उदाहरण के लिए, निम्न प्रोग्राम 100 अंक की सरणी बनाता है और प्रारंभ करता है। एक वर्ग के रूप में लागू बिंदु के साथ, 101 अलग-अलग वस्तुओं को तत्काल-एक सरणी के लिए एक और 100 तत्वों में से प्रत्येक के लिए तत्काल किया जाता है।

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

एक विकल्प बिंदु बनाने के लिए एक विकल्प है।

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

अब, केवल एक ऑब्जेक्ट को तुरंत चालू किया गया है- सरणी के लिए एक- और प्वाइंट इंस्टेंस सरणी में ऑनलाइन संग्रहीत किए जाते हैं।

स्ट्रक्चर कन्स्ट्रक्टर को नए ऑपरेटर के साथ बुलाया जाता है, लेकिन इसका मतलब यह नहीं है कि स्मृति आवंटित की जा रही है। किसी ऑब्जेक्ट को गतिशील रूप से आवंटित करने और इसके संदर्भ को वापस करने के बजाय, एक संरचना निर्माता केवल संरचना मान को लौटाता है (आमतौर पर ढेर पर अस्थायी स्थान में), और यह मान तब आवश्यकतानुसार कॉपी किया जाता है।

कक्षाओं के साथ, एक ही ऑब्जेक्ट को संदर्भित करने के लिए दो चर के लिए संभव है और इस प्रकार एक चर पर संचालन के लिए अन्य चर द्वारा संदर्भित ऑब्जेक्ट को प्रभावित करने के लिए संभव है। Structs के साथ, चर के प्रत्येक डेटा की अपनी प्रति है, और एक दूसरे को प्रभावित करने के लिए संचालन के लिए यह संभव नहीं है। उदाहरण के लिए, निम्नलिखित कोड खंड द्वारा उत्पादित आउटपुट इस बात पर निर्भर करता है कि प्वाइंट एक वर्ग या संरचना है या नहीं।

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

यदि प्वाइंट एक वर्ग है, तो आउटपुट 20 है क्योंकि ए और बी एक ही ऑब्जेक्ट का संदर्भ देता है। यदि प्वाइंट एक स्ट्रक्चर है, तो आउटपुट 10 है क्योंकि बी को असाइनमेंट मूल्य की प्रति बनाता है, और यह प्रति कुल्हाड़ी के बाद के असाइनमेंट से अप्रभावित है

पिछले उदाहरण structs की दो सीमाओं पर प्रकाश डाला गया है। सबसे पहले, एक संपूर्ण संरचना की प्रतिलिपि बनाना ऑब्जेक्ट संदर्भ की प्रतिलिपि बनाने से आम तौर पर कम कुशल होता है, इसलिए असाइनमेंट और मान पैरामीटर पासिंग संदर्भ प्रकारों की तुलना में structs के साथ अधिक महंगा हो सकता है। दूसरा, रेफरी और आउट पैरामीटर को छोड़कर, structs के संदर्भ बनाना संभव नहीं है, जो कई स्थितियों में उनके उपयोग को नियंत्रित करता है।


A struct is a value type. If you assign a struct to a new variable, the new variable will contain a copy of the original.

public struct IntStruct {
    public int Value {get; set;}
}

Excecution of the following results in 5 instances of the struct stored in memory:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

A class is a reference type. When you assign a class to a new variable, the variable contains a reference to the original class object.

public class IntClass {
    public int Value {get; set;}
}

Excecution of the following results in only one instance of the class object in memory.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Struct s may increase the likelihood of a code mistake. If a value object is treated like a mutable reference object, a developer may be surprised when changes made are unexpectedly lost.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

Briefly, use struct if :

1- your object properties/fields do not need to be changed. I mean you just want to give them an initial value and then read them.

2- properties and fields in your object are value type and they are not so large.

If that's the case you can take advantage of structs for a better performance and optimized memory allocation as they use only stacks rather than both stacks and heaps (in classes)


I did a small benchmark with BenchmarkDotNet to get a better understanding of "struct" benefit in numbers. I'm testing looping through array (or list) of structs (or classes). Creating those arrays or lists is out of the benchmark's scope - it is clear that "class" is more heavy will utilize more memory, and will involve GC.

So the conclusion is: be careful with LINQ and hidden structs boxing/unboxing and using structs for microoptimizations strictly stay with arrays.

PS Another benchmark about passing struct/class through call stack is there https://.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

कोड:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

I think a good first approximation is "never".

I think a good second approximation is "never".

If you are desperate for perf, consider them, but then always measure.


I was just dealing with Windows Communication Foundation [WCF] Named Pipe and I did notice that it does make sense to use Structs in order to ensure that exchange of data is of value type instead of reference type .


My rule is

1, Always use class;

2, If there is any performance issue, I try to change some class to struct depending on the rules which @IAbstract mentioned, and then do a test to see if these changes can improve performance.


Nah - I don't entirely agree with the rules. They are good guidelines to consider with performance and standardization, but not in light of the possibilities.

As you can see in the responses, there are a log of creative ways to use them. So, these guidelines need to just be that, always for the sake of performance and efficiency.

In this case, I use classes to represent real world objects in their larger form, I use structs to represent smaller objects that have more exact uses. The way you said it, "a more cohesive whole." The keyword being cohesive. The classes will be more object oriented elements, while structs can have some of those characteristics, their on a smaller scale. IMO.

I use them a lot putting in Treeview and Listview tags where common static attributes can be accessed very quickly. I would struggle to get this info another way. For example, in my database applications, I use a Treeview where I have Tables, SPs, Functions, or any other objects. I create and populate my struct, put it in the tag, pull it out, get the data of the selection and so forth. I wouldn't do this with a class!

I do try and keep them small, use them in single instance situations, and keep them from changing. It's prudent to be aware of memory, allocation, and performance. And testing is so necessary.


Structure or value types can be used in following scenarios -

  1. If you want to prevent the object to be collected by garbage collection.
  2. If it is a simple type and no member function modifies its instance fields
  3. If there is no need to derive from other types or being derived to other types.

You can know more about the value types and values types here on this link


Structure types in C# or other .net languages are generally used to hold things that should behave like fixed-sized groups of values. A useful aspect of structure types is that the fields of a structure-type instance can be modified by modifying the storage location in which it is held, and in no other way. It's possible to code a structure in such a way that the only way to mutate any field is to construct a whole new instance and then use a struct assignment to mutate all the fields of the target by overwriting them with values from the new instance, but unless a struct provides no means of creating an instance where its fields have non-default values, all of its fields will be mutable if and if the struct itself is stored in a mutable location.

Note that it's possible to design a structure type so that it will essentially behave like a class type, if the structure contains a private class-type field, and redirects its own members to that of the wrapped class object. For example, a PersonCollection might offer properties SortedByName and SortedById , both of which hold an "immutable" reference to a PersonCollection (set in their constructor) and implement GetEnumerator by calling either creator.GetNameSortedEnumerator or creator.GetIdSortedEnumerator . Such structs would behave much like a reference to a PersonCollection , except that their GetEnumerator methods would be bound to different methods in the PersonCollection . One could also have a structure wrap a portion of an array (eg one could define an ArrayRange<T> structure which would hold a T[] called Arr , an int Offset , and an int Length , with an indexed property which, for an index idx in the range 0 to Length-1 , would access Arr[idx+Offset] ). Unfortunately, if foo is a read-only instance of such a structure, current compiler versions won't allow operations like foo[3]+=4; because they have no way to determine whether such operations would attempt to write to fields of foo .

It's also possible to design a structure to behave a like a value type which holds a variable-sized collection (which will appear to be copied whenever the struct is) but the only way to make that work is to ensure that no object to which the struct holds a reference will ever be exposed to anything which might mutate it. For example, one could have an array-like struct which holds a private array, and whose indexed "put" method creates a new array whose content is like that of the original except for one changed element. Unfortunately, it can be somewhat difficult to make such structs perform efficiently. While there are times that struct semantics can be convenient (eg being able to pass an array-like collection to a routine, with the caller and callee both knowing that outside code won't modify the collection, may be better than requiring both caller and callee to defensively copy any data they're given), the requirement that class references point to objects that will never be mutated is often a pretty severe constraint.


The C# struct is a lightweight alternative to a class. It can do almost the same as a class, but it's less "expensive" to use a struct rather than a class. The reason for this is a bit technical, but to sum up, new instances of a class is placed on the heap, where newly instantiated structs are placed on the stack. Furthermore, you are not dealing with references to structs, like with classes, but instead you are working directly with the struct instance. This also means that when you pass a struct to a function, it is by value, instead of as a reference. There is more about this in the chapter about function parameters.

So, you should use structs when you wish to represent more simple data structures, and especially if you know that you will be instantiating lots of them. There are lots of examples in the .NET framework, where Microsoft has used structs instead of classes, for instance the Point, Rectangle and Color struct.





struct