c# - متى تستخدم البنية؟




struct (19)

أستخدم أشكالًا لتعبئة أو تفريغ أي نوع من تنسيق الاتصال الثنائي. يتضمن ذلك القراءة أو الكتابة على القرص أو قوائم DirectX vertex أو بروتوكولات الشبكة أو التعامل مع البيانات المشفرة / المضغوطة.

لم تكن الإرشادات الثلاثة التي تدرجها مفيدة لي في هذا السياق. عندما أحتاج إلى كتابة 400 وحدة من الأشياء في أمر معين ، سأقوم بتعريف بنية من أربع مائة بايت ، وسأقوم بملئها بأي قيم غير ذات صلة من المفترض أن يكون لها ، وسأقوم لإعداده بأي طريقة أكثر منطقية في ذلك الوقت. (حسنا ، أربع مئة بايت ستكون غريبة جدا - ولكن عندما كنت أكتب ملفات Excel من أجل لقمة العيش ، كنت أتعامل مع هياكل تصل إلى حوالي أربعين بايت في كل مكان ، لأن هذا هو حجم بعض سجلات BIFF.)

متى يجب أن تستخدم البنية وليس الطبقة في C #؟ نموذجي المفاهيمي هو أن البُنى تُستخدم في الأوقات التي يكون فيها العنصر مجرد مجموعة من أنواع القيم . طريقة لاحتجازهم منطقياً جميعاً في كتلة متماسكة.

جئت عبر هذه القواعد here :

  • يجب أن تمثل البنية قيمة واحدة.
  • يجب أن يكون للبنية بصمة ذاكرة أقل من 16 بايت.
  • لا يجب تغيير البنية بعد الإنشاء.

هل تعمل هذه القواعد؟ ماذا يعني بنية لغويا؟


استخدم بنية عندما تريد دلالات قيمة بدلاً من دلالات مرجعية.

تصحيح

لست متأكدا لماذا الناس يسقطون هذا ولكن هذا هو نقطة صحيحة ، وقدم قبل توضيح OP سؤاله ، وهذا هو السبب الأساسي الأكثر الأساسية للبنية.

إذا كنت بحاجة إلى دلالات مرجعية ، فأنت تحتاج إلى فصل وليس بنية.


المصدر المشار إليه من قبل OP لديه بعض المصداقية ... ولكن ماذا عن مايكروسوفت - ما هو موقف استخدام البنية؟ لقد طلبت بعض التعلم الإضافي من Microsoft ، وهنا ما وجدته:

ضع في اعتبارك تعريف بنية بدلاً من فئة إذا كانت الأمثلة من النوع صغيرة أو قصيرة العمر عادةً أو تكون مدمجة عادةً في كائنات أخرى.

لا تحدد بنية ما إلا إذا كان للنوع كل الخصائص التالية:

  1. يمثل منطقياً قيمة مفردة مشابهة للأنواع البدائية (عدد صحيح ، مزدوج ، وهكذا).
  2. له حجم مثيل أصغر من 16 بايت.
  3. إنه غير قابل للتغيير.
  4. لن يكون من الضروري أن تكون محاصر بشكل متكرر.

تنتهك Microsoft هذه القواعد باستمرار

حسنا ، # 2 و 3 على أي حال. يحتوي قاموسنا المحبوب على هيكلين داخليين:

[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' على 3 من أصل 4 - يمكن التسامح تماما لأن # 4 ربما لن تكون مشكلة. إذا وجدت نفسك ملاكمة بنية ، فقم بإعادة النظر في العمارة الخاصة بك.

لنلق نظرة على سبب استخدام Microsoft لهذه البُنى:

  1. تمثل كل بنية و Entry و Enumerator قيمًا مفردة.
  2. سرعة
  3. لا يتم تمرير Entry أبداً كمعلمة خارج فئة القاموس. يظهر مزيد من التحقيق أنه من أجل تلبية تنفيذ IEnumerable ، يستخدم القاموس بنية Enumerator التي تنسخها في كل مرة يتم فيها طلب من العداد ... من المنطقي.
  4. الداخلية لفئة القاموس. يكون Enumerator عامًا لأن القاموس قابل للتعداد ويجب أن يكون لديه إمكانية وصول متساوية إلى تطبيق واجهة IEnumerator - على سبيل المثال برنامج getm IEnumerator.

تحديث - بالإضافة إلى ذلك ، إدراك أنه عندما تقوم البنية بتنفيذ واجهة - كما يفعل Enumerator - ويتم إرسالها إلى هذا النوع المنفذ ، فإن البنية تصبح نوع مرجع ويتم نقلها إلى الكومة. داخلي إلى فئة القاموس ، لا يزال Enumerator نوع قيمة. ومع ذلك ، بمجرد GetEnumerator() أسلوب GetEnumerator() ، يتم إرجاع IEnumerator نوع مرجع.

ما لا نراه هنا هو أي محاولة أو دليل على ضرورة إبقاء الهياكل غير قابلة للتغيير أو الحفاظ على حجم مثيل يبلغ 16 بايت فقط أو أقل:

  1. لا شيء في الهياكل المذكورة أعلاه هو معلن readonly - وليس ثابتا
  2. يمكن أن يكون حجم هذه البنية أكثر من 16 بايت
  3. يحتوي Entry على عمر غير محدد (من Add() ، إلى Remove() أو Clear() أو مجموعة البيانات المهملة) ؛

و ... 4. كلا البنى يخزنان TKey و TValue ، وكلنا نعرف أنهما قادران تماما على أن يكونا أنواع مرجعية (معلومات إضافية مضافة)

بالرغم من ذلك ، فإن القواميس تكون سريعة في جزء منها لأن عملية إنشاء بنية أسرع من نوع المرجع. هنا ، لدي Dictionary<int, int> يقوم بتخزين 300.000 رقم صحيح عشوائي مع مفاتيح متتالية.

السعة: 312874
ميمزاي: 2660827 بايت
اكتمل تغيير حجم: 5 مللي ثانية
الوقت الإجمالي لملء: 889ms

السعة : عدد العناصر المتاحة قبل أن يتم تغيير حجم المصفوفة الداخلية.

MemSize : يتم تحديده من خلال تحويل القاموس إلى ذاكرة MemoryStream والحصول على طول بايت (دقيق بما يكفي لأغراضنا).

تغيير حجم مكتمل : الوقت المستغرق لتغيير حجم المصفوفة الداخلية من عناصر 150862 إلى عناصر 312874. عندما ترى أن كل عنصر يتم نسخه بشكل تسلسلي عبر Array.CopyTo() ، فهذا ليس Array.CopyTo() جدًا.

إجمالي الوقت المطلوب تعبئته : تم OnResize بسبب التسجيل OnResize الذي أضفته إلى المصدر ؛ ومع ذلك ، لا تزال مثيرة للإعجاب لملء الأعداد الصحيحة 300 كيلو بايت بينما يتم تغيير حجمها 15 مرة خلال العملية. فقط من الفضول ، ماذا سيكون الوقت الإجمالي لملء إذا كنت بالفعل على علم القدرة؟ 13ms و

إذن ، الآن ، ماذا لو كان Entry من فئة؟ هل تختلف هذه الأوقات أو المقاييس كثيرًا؟

السعة: 312874
ميمزاي: 2660827 بايت
اكتمل التغيير: 26ms
الوقت الإجمالي لملء: 964ms

من الواضح أن الفرق الكبير في تغيير الحجم. أي اختلاف إذا تمت تهيئة القاموس بالسعة؟ لا يكفي أن تهتم ... 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 كنوع مرجعي في MSDN: Structure Design . باختصار:

لا توفر منشئ افتراضي للهيكل.

في حالة تعريف بنية مُنشئ افتراضي ، عند إنشاء صفائف بنية ، يقوم وقت تشغيل اللغة العامة تلقائيًا بتنفيذ المُنشئ الافتراضي على كل عنصر من عناصر الصفيف.

بعض compilers ، مثل برنامج التحويل البرمجي C # ، لا تسمح للهياكل أن يكون لها منشئات افتراضية.

إنه في الواقع بسيط للغاية وسنستعير من قوانين آسيموف الثلاثة للروبوتات :

  1. يجب أن يكون الهيكل آمنًا للاستخدام
  2. يجب أن يؤدي الهيكل وظيفته بكفاءة ، ما لم يكن ذلك انتهاكًا للقاعدة رقم 1
  3. يجب أن تظل البنية سليمة أثناء استخدامها ما لم يكن تدميرها مطلوبًا لتلبية القاعدة رقم 1

... ما الذي نأخذه عن هذا : باختصار ، كن مسؤولاً عن استخدام أنواع القيم. فهي سريعة وفعالة ، ولكن لديها القدرة على التسبب في العديد من السلوكيات غير المتوقعة إذا لم يتم الحفاظ عليها بشكل صحيح (أي نسخ غير مقصودة).


باستثناء أنواع القيم التي يتم استخدامها بشكل مباشر من خلال وقت التشغيل والعديد من الأغراض الأخرى لأغراض PInvoke ، يجب عليك استخدام أنواع القيم فقط في سيناريوهين.

  1. عندما تحتاج إلى نسخ الدلالات.
  2. عندما تحتاج إلى التهيئة التلقائية ، عادةً في صفائف من هذه الأنواع.

تحتاج إلى استخدام "struct" في الحالات حيث تريد تحديد تخطيط الذاكرة بشكل صريح باستخدام StructLayoutAttribute - عادة لـ PInvoke.

تحرير: يشير التعليق إلى أنه يمكنك استخدام الفئة أو البنية مع StructLayoutAttribute وهذا صحيح بالتأكيد. في الممارسة العملية ، عادةً ما تستخدم بنية - يتم تخصيصها على المكدس مقابل كومة الذاكرة المؤقتة التي تكون منطقية إذا كنت فقط تمرير وسيطة إلى استدعاء أسلوب غير مُدار.


تعتبر التركيبات جيدة للتمثيل الذري للبيانات ، حيث يمكن نسخ البيانات المذكورة عدة مرات بواسطة الشفرة. يكون استنساخ كائن بشكل عام أكثر تكلفة من نسخ بنية ، لأنه ينطوي على تخصيص الذاكرة وتشغيل المُنشئ وإلغاء تخصيص / تجميع البيانات المهملة عند القيام به.


لا أوافق على القواعد الواردة في المشاركة الأصلية. فيما يلي القواعد الخاصة بي:

1) تستخدم تركيبات للأداء عند تخزينها في المصفوفات. (انظر أيضا متى تكون البنيات هي الحل؟ )

2) أنت في حاجة إليها في رمز تمرير البيانات المنظمة إلى / من C / C ++

3) لا تستخدم البنى إلا إذا كنت في حاجة إليها:

  • إنهم يتصرفون بشكل مختلف عن "الأشياء العادية" ( الأنواع المرجعية ) تحت الواجب وعند تمريرها كحجج يمكن أن تؤدي إلى سلوك غير متوقع ؛ هذا أمر خطير بشكل خاص إذا كان الشخص الذي ينظر إلى الرمز لا يعرف أنه يتعامل مع بنية.
  • لا يمكن أن تكون موروثة.
  • تمرير البنى كوسيطة أكثر تكلفة من الفئات.

من مواصفات لغة C # :

1.7 الهياكل

مثل الطبقات ، والبنى هي هياكل البيانات التي يمكن أن تحتوي على أعضاء البيانات وأعضاء الوظيفة ، ولكن على عكس الطبقات ، والبنى هي أنواع القيمة ولا تتطلب تخصيص الكومة. يقوم متغير من نوع البنية بتخزين بيانات البنية بشكل مباشر ، بينما يقوم متغير من نوع الفئة بتخزين مرجع إلى كائن تم تخصيصه ديناميكياً. لا تدعم أنواع البنية الوراثة المحددة من قبل المستخدم ، وجميع أنواع البنية ترث ضمنيًا من كائن الكتابة.

تعد الهياكل مفيدة بشكل خاص لهياكل البيانات الصغيرة التي تحتوي على دلالات قيمة. الأرقام المعقدة ، النقاط في نظام إحداثيات ، أو أزواج القيم الرئيسية في القاموس هي كل الأمثلة الجيدة للبنى. يمكن استخدام بنية بدلاً من فئات بنية البيانات الصغيرة إحداث تغيير كبير في عدد عمليات تخصيص الذاكرة التي يقوم التطبيق بتنفيذها. على سبيل المثال ، يقوم البرنامج التالي بإنشاء وتهيئة مصفوفة من 100 نقطة. في حالة تنفيذ Point as a class ، يتم إنشاء مثيل لـ 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);
   }
}

بديل هو جعل Point a struct.

struct Point
{
   public int x, y;

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

الآن ، يتم إنشاء مثيل كائن واحد فقط - واحد لصفيف - ويتم تخزين مثيلات نقطة في الخط في الصفيف.

يتم استدعاء منشئي البنية مع المشغل الجديد ، ولكن هذا لا يعني ضمناً أنه يتم تخصيص الذاكرة. بدلاً من تخصيص كائن بشكل حيوي وإرجاع مرجع إليه ، يقوم مُنشئ البنية ببساطة بإرجاع قيمة البنية نفسها (عادةً في موقع مؤقت على المكدس) ، ثم يتم نسخ هذه القيمة عند الضرورة.

مع الفصول ، من الممكن أن يقوم متغيرين بالإشارة إلى نفس الكائن ، وبالتالي يمكن أن تؤثر العمليات على متغير واحد على الكائن المشار إليه بواسطة المتغير الآخر. مع التركيبات ، يكون لكل متغير نسخة خاصة به من البيانات ، وليس من الممكن أن تؤثر العمليات على أحدهما على الآخر. على سبيل المثال ، يعتمد الإخراج الناتج من جزء التعليمات البرمجية التالي على ما إذا كانت Point فئة أو بنية.

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

إذا كانت Point هي فئة ، فإن الناتج هو 20 لأن a و b يشيران إلى نفس الكائن. إذا كانت Point هي بنية ، فسيكون الناتج 10 لأن تخصيص a إلى b ينشئ نسخة من القيمة ، ولا تتأثر هذه النسخة بالتخصيص التالي للفأس.

يوضح المثال السابق اثنين من قيود البنيات. أولاً ، عادةً ما يكون نسخ بنية بأكملها أقل كفاءة من نسخ مرجع كائن ، لذا يمكن أن يكون تمرير المهمة وقيمة المعلمة أكثر تكلفة بالبنى من الأنواع المرجعية. ثانيًا ، باستثناء المعلمات المنقحة والخارجة ، لا يمكن إنشاء إشارات إلى البُنى ، التي تستبعد استخدامها في عدد من المواقف.


يدعم .NET value types reference types (في Java ، يمكنك تحديد أنواع المرجع فقط). يتم تخصيص مثيلات reference types في الكومة التي تمت إدارتها ويتم تجميع البيانات المهملة في حالة عدم وجود مراجع معلقة لها. يتم توزيع مثيلات value types ، من ناحية أخرى ، في stack ، ومن ثم يتم استعادة الذاكرة المخصصة بمجرد انتهاء النطاق الخاص بهم. وبالطبع ، يتم تمرير value types حسب القيمة ، reference types المرجع بالرجوع إليها. جميع أنواع البيانات البدائية C # ، باستثناء System.String ، هي أنواع قيم.

متى تستخدم البنية على الطبقة ،

في C # ، structs هي value types ، والفصول هي reference types . يمكنك إنشاء أنواع قيم ، في C # ، باستخدام الكلمة الرئيسية enum وكلمة أساسية struct . يؤدي استخدام value type بدلاً من reference type إلى ظهور عدد أقل من الكائنات في الكومة التي تتم إدارتها ، مما يؤدي إلى تحميل أقل على مجمّع البيانات المهملة (GC) ، وأقل من دورات GC المتكررة ، وبالتالي أداء أفضل. ومع ذلك ، فإن value types لها جوانبها السلبية أيضًا. إن المرور حول struct كبيرة هو بالتأكيد أكثر كلفة من تمرير مرجع ، هذه مشكلة واضحة. المشكلة الأخرى هي النفقات العامة المرتبطة boxing/unboxing . في حال كنت تتساءل عن معنى boxing/unboxing ، اتبع هذه الروابط للحصول على شرح جيد حول boxing unboxing . بصرف النظر عن الأداء ، هناك أوقات تحتاج فيها ببساطة إلى أنواع ذات دلالات قيمة ، والتي سيكون من الصعب جدًا (أو القبيح) تنفيذها إذا كانت جميع reference types لديك. يجب عليك استخدام value types فقط ، عندما تحتاج إلى نسخ دلالات أو تحتاج إلى تهيئة تلقائية ، عادةً في arrays من هذه الأنواع.


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